プログラミング言語/Ruby


Rubyそぞろ歩き

 というわけでPerlの入門書"Learning Perl"(Randal L.Schwartz)、通称リャマ本から 順当にパクって行きたいと思います。

 これから小規模なプログラムを書きながらRubyの様々な機能に触れていくわけ ですが、Rubyはbeauty Perlたり得てもbeauty Perlそのものではありません。

 入門書として極めて優れたリャマ本にならい、個々の機能に関する説明は 簡単に済ませますし、Perlのソースがベースかつ複雑な機能は使わないという方針の サンプルプログラムにはかなりRuby Wayではない書き方も沢山あります。 このドキュメントに現れた書き方に決して固執しないようにしてください。

1. "Hello, world"プログラム

 さて、とりあえず何かさせてみましょう。 以下はチュートリアル文書の通過儀礼とでも言うべき例のプログラムの Ruby版です。

#!/usr/bin/ruby
print "Hello, world!\n"

 1行目は、このプログラムがRubyプログラムであることをシェルに伝えるものです。 Ruby自身にとってはコメントになっています。 多くのシェルやawk、そしてもちろんPerlと同じように、シャープ記号(#)から行末までが コメントです。

 2行目が、このプログラム中に実行される仕事のすべてです。 あらかじめ用意されているprintという機能を呼び出して、コンソールへの 出力を成し遂げます。

 PerlやCでは、このような単純文は最後にセミコロン(;)で終わるところですが、 Rubyではそれが改行に取って代わります。もちろんセミコロンも同様に機能しますが、 Ruby Wayではありません。

 この文はBASICな道を通ってきた人には命令のように映るかもしれません。しかし print(...)のようにカッコをつけると関数のように見えるでしょう。 正解はどっちでもなくてメソッド呼び出しなのですが、この意味がわからなくても 支障はあまりありません。

2. 質問してその答えを覚えておくこと

今度は、少しだけ凝ったことをしてみましょう。hello, worldという挨拶は、 よそよそしくて他人行儀な感じがします。そこで、相手の名前を呼びかける ようにしてみましょう。これを実現するには、

 入力された名前のような、値を保持する手段として変数があります。 Rubyにはいくつかの種類がありますが、ここではローカル変数を用います。 "name"のように、素直にアルファベッドで変数を命名してください。

 次に名前の入力ですが、コンソールプログラムでは、プロンプトと呼ばれる 入力を要求するような旨の文章や記号を印字して、ユーザーからの入力を待ち受ける のが一般的です。 この仕事のうち、プロンプトの印字については前回のプログラムですでに方法を学んでいる と思います。printを使うのです。あとは入力を受け付ける手段です。

 これはRuby内で標準入力を表すSTDINというモノに関わります。 Rubyにおけるモノ(=オブジェクト)には、それぞれ様々な機能(=メソッド)が備わっていて、 それらを呼び出すことによってRubyのプログラムは進行します。 今回用いるのは、STDINが持つgetsというメソッドです。これは入力を一行読み込みます。 つまり、キーボードからだとエンターキーで入力を終わるということです。

 以上をまとめると、次のようになります。

print "名前は? "
name = STDIN.gets

 この時点では、nameの値の末尾には、改行文字(\n)がくっついています(例えば Vipperと入力したら、nameの値はVipper\nになっています)。 この改行文字を取り除くには、chop!を使います。

 入力され、nameに保持された値は当然文字列なので、Ruby内ではStringという モノの種類(=クラス)から生み出されたものです。 chop!は、Stringオブジェクトが持つメソッドです。自らが表している文字列の 内容の末尾を、1字ぶん削ります*1

name.chop!

 ここまでくれば、あとはHelloと表示して、その後ろに変数nameの内容を表示する だけです。これは、ダブルクォートで囲んだ("...")文字列の中に値を埋め込む 「#{...}」という記法(式展開)を使って行います。 シェルやPerlの似たようなそれとは違い、Rubyでは式なら何でも書けます。

print "Hello, #{name}!\n"

 これらをまとめると次のプログラムが得られます。

#!/usr/bin/ruby
print "名前は? "
name = STDIN.gets
name.chop!
print "Hello, #{name}!\n"

3. 選択処理を追加する

 ここで、Vipperに対しては特別な挨拶をして、それ以外の人には普通の挨拶ですます ようにしましょう。入力された名前と文字列"Vipper"を比較して、同じであれば 特別扱いする、という処理を行います。 それらは(キーワードだけはC風の)ifとかelseとかの構文で分岐と比較を行います。

#!/usr/bin/ruby
print "名前は? "
name = STDIN.gets
name.chop!
if name == "Vipper"
  print "Hello, Vipper! よく来たな。\n"
else
  print "Hello, #{name}!\n" #普通の挨拶
end

 演算子"=="は、ここでは文字列を比較します *2 (仮に右辺が文字列ではない場合、Rubyが出来る限り文字列に変換しようとしてくれます)。 それら文字列が等しければ(内容の比較)、結果は真になります。

 if文は、ifで始まってendで終わります。与えられた式が真ならif直後の部分を実行し、 そうでなければelseの後の部分を実行します。

4. 合言葉を当てる

 さて、名前を入力してもらったところで、プログラムを起動した人に、合言葉を当てて もらうことにしましょう。プログラムは、Vipper以外の人に対しては、正しく 言い当てるまで、合言葉を入力するように繰り返し求めます。まずプログラムを披露して から説明しましょう。

#!/usr/bin/ruby
secretword = "2ch"  #合言葉
print "名前は? "
name = STDIN.gets
name.chop!
if name == "Vipper"
  print "Hello, Vipper! よく来たな。\n"
else
  print "Hello, #{name}!\n" #普通の挨拶
  print "合言葉は? "
  guess = STDIN.gets
  guess.chop!
  while guess != secretword
    print "ちょwwおまwwww違うwwwww 合言葉は? "
    guess = STDIN.gets
    guess.chop!
  end
end

 まず最初に、もう一つのローカル変数secretwordに、合言葉を入れておきます。 挨拶の後に、Vipper以外の人には、(printによって)合言葉を当てるように求めます。 ユーザが当て推量した言葉を入力すると、演算子"!="によってそれを合言葉と 比較します。この場合"!="は文字列同士が違う場合に真を返します。 (この演算子の働きは"=="を用いた場合の論理的反転です) whileは式の結果によって制御される繰り返しで、式が真である限り繰り返しを 続けます。

 もちろん、これは決して安全性の高いプログラムではありません。なぜなら、 当て推量するのに飽きたユーザはCtrl+CでRubyと手を切ることができますし、 あるいはソースのsecretwordの代入式の右辺を見てカンニングすることもできる からです。 しかし、私が書いているのはセキュリティシステムではなくて、単にリャマ本の 猿真似なので、言い訳まで真似だとしても目をつぶってください。

5. 複数の合言葉を扱う

 複数の合言葉のうちどれか1つを当てればよいことにするにはどうするか―― これからそれをお話したいと思います。 これまで学んだ方法を応用するならば、ローカル変数に正解をそれぞれ入れて おいて、ユーザの当て推量と次々比較していくような手になるでしょう。 しかし、そんなやり方だと、合言葉リストを変更したり、それをファイルから 読み込んだり、曜日に応じて合言葉を変えたりするような仕組みを作ることは 困難です。

よりスマートな解決策は、配列(Array)と呼ばれるデータ構造に、 許される答えを全て入れておくというものです。 配列の要素はそれぞれがローカル変数のように値をセットしたり 取り出したりすることができます。配列全体に対して、一挙に値を与える こともできます。 ここでは、次のようにして、配列という種類(クラス)のモノ(オブジェクト)を 作り、3つの合言葉入れて、wordsというローカル変数にしておきます。

words = ["2ch", "vip", "mona"]

 Arrayオブジェクトに入れてしまえば、添え字付けによって、個々の要素に アクセスすることができます。 ですから、words[0]は2ch、words[1]はvip、words[2]はmonaになります。

 添え字(インデックス)には式を使うこともできます。例えば 変数idxに2をセットしておけば、words[idx]はmona になります。

先程の例に戻ると、次のようなプログラムになります。

#!/usr/bin/ruby
words = ["2ch", "vip", "mona"]
print "名前は? "
name = STDIN.gets
name.chop!
if name == "Vipper"
  print "Hello, Vipper! よく来たな。\n"
else
  print "Hello, #{name}!\n" #普通の挨拶
  print "合言葉は? "
  guess = STDIN.gets
  guess.chop!
  i = 0 #最初の合言葉から調べ始める
  correct = "maybe" #合言葉が当たったかどうか?
  while correct == "maybe"  #合言葉を正しく当てるまで繰り返す
    if words[i] == guess  #当たった?
      correct = "yes" #正解!
    elsif i < words.size  #他に調べる合言葉があるか?
      i = i + 1 #次回は次の合言葉を調べる
    else  #これ以上合言葉はない、だから間違い
      print "ちょwwおまwwww違うwwwww 合言葉は? "
      guess = STDIN.gets
      guess.chop!
      i = 0 #最初の合言葉からチェックをやり直す
    end
  end #「while 正しく当てるまで」の終わり
end #「if Vipper 以外」の終わり

 合言葉を調べている最中なのか、あるいはすでに見つかったかを示すのに、 変数correctを使っています。

 このプログラムは、if-else-end文のelsifブロックの使用例にもなっています。 Cやawkには、これに相当するものは存在しません。 elsifブロックは、elseブロックと新しいif条件を合わせたもの省略形ですが、 もう一組のif-endブロックをネストしないで済みます。

 if-elsif-elsif-elseif-elseの連鎖によって、一連の条件を比較するという のは、とてもPerlらしいやり方らしいです。Rubyではそうでもないです *3

6. 1人1人に別の合言葉を割り当てる

 たった今作ったブログラムでは、通りすがりの人間でも、3つの言葉のどれかを 当てれば、成功してしまいます。もし1人1人に別の合言葉を割り当てたいのなら、 人と合言葉を対応づけるテーブルが必要になります。

合言葉
Hiroyukiumai-bou
Boomfugashi
Yaruoonani

 このようなテーブルを表現するのに最も適した方法は、連想配列という データ構造を使うことです。RubyではHashクラスから作ることができます。

Hashオブジェクトの各要素は、普通の配列と同じように、1個1個が値を 持っています。ただ、Hashの個々の要素にアクセスするには インデックスではなくキー(key)を使います。このキーには、Rubyの値 *4ならなんでも用いることができます。

上のようなテーブルを表現するのに、人名を表すStringオフジェクトを キーにして、合言葉のStringオブジェクトを格納するには次のように します。

words = {
  "Hiroyuki" => "umai-bou",
  "Boom"     => "fugashi",
  "Yaruo"    => "onani",
}

 カンマで区切られたリストのそれぞれの、「=>」で区切られた左右の値が、 Hashのキー1個とそれに対応するオブジェクトを表しています。 この代入文が、継続文字のたぐいを使わずに、数行にまたがっていることに 注目しましょう。このような書き方ができるのは、Rubyプログラムにおいては、 一般の空白文字は意味を持たないからです。

 Boomの合言葉を取り出すには、Boomをキーとして、 words["Boom"]といった式によってHashオブジェクトwordsの要素に アクセスする必要があります。先程の配列と同じように、この参照によって 得られる値はfugashiになります。

 また、普通の配列(Array)と同様に、キーには任意の式を用いることができます。 personに"Boom"をセットしてから、words[person]を評価すると、 同様にfugashiが得られます。

 これらを1つにまとめると次のプログラムが得られます。

#!/usr/bin/ruby
words = {
  "Hiroyuki" => "umai-bou",
  "Boom"     => "fugashi",
  "Yaruo"    => "onani",
}
print "名前は? "
name = STDIN.gets
name.chop!
if name == "Vipper"
  print "Hello, Vipper! よく来たな。\n"
else
  print "Hello, #{name}!\n" #普通の挨拶
  secretword = words[name]  #合言葉を得る
  print "合言葉は? "
  guess = STDIN.gets
  guess.chop!
  while guess != secretword
    print "ちょwwおまwwww違うwwwww 合言葉は? "
    guess = STDIN.gets
    guess.chop!
  end
end

 合言葉を調べる部分に注目しましょう。合言葉が見つからなければ、 secretwordの値はnilという値になります。なのでnilであるかを チェックすれば、その他全員に対するデフォルトの合言葉を設定することも できます。この処理は次のようになります。

[... プログラムの前の部分を省略 ...]
  secretword = words[name]  #合言葉を得る
  if secretword == nil  #おや、見つからない
    secretword = "fushianasan"  #いいかお前ら、絶対に名前欄には入れるなよ! 絶対だぞ!
  end
  print "合言葉は? "
[... プログラムの残りの部分を省略 ...]

7. いろいろな書き方を受け付けるようにする

作成中…


*1 1字、というのは正確ではなく、実際は1バイト
*2 左辺のオブジェクトが動作を決めてます
*3 Rubyにはcase文という、Cのswicth文を幾分高機能にしたような構文があります
*4 つまりオブジェクト

トップ   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS