おもこん

おもこんは「思いつくままにコンピュターの話し」の省略形です

徒然Ruby(9)実数、ファイル

実数

実数はそれほど使わないオブジェクトだと思います。 しかし、どんな言語にも実装されているものですし、全然使わないこともないので、ここで取り上げようと思います。 Rubyのドキュメントでは実数を「浮動小数点のクラス」と呼んでいます。 ここでは「実数のクラス」と「浮動小数点(数)のクラス」を同じ意味で使います。

実数のリテラルは小数点のついた数字です。

1.2
-3.5
3.4e3
1.0

これらはすべて実数です。 最後の1.0は実数オブジェクトであって、整数オブジェクトではないので注意してください。 3.4e33.4x10^3(3.4かける10の3乗)という意味です。 eはexponent(指数)の意味です。

print 1.0.class, "\n"
print 1.0, "\n"
print -2.3, "\n"
print 3.4e5, "\n"
print 3.4e-3, "\n"

これを実行すると

Float
1.0
-2.3
340000.0
0.0034

となります。 実数のクラス名は「Float」です。 .classメソッドはそのオブジェクトのクラス名を返します。 したがって、1行目は「1.0は実数だ」ということを示しています。

実数は常に近似値だと思ったほうが良いです。 というのは、その数を二進数に直して浮動小数点形式でメモリーに格納するからです。 例えば、十進数の0.1という数を考えてみましょう。

これが二進数の0.abcdef...と表されたとします。 小数点以下の位は、(1/2)、(1/2)^2、(1/2)^3、・・・を表します(ちょうど十進小数が(1/10)、(1/10)^2、(1/10)^3、・・・となるのと同じ)。 以下、左辺は十進、右辺は二進で表します。 次々2倍していくと

0.1 = 0.abcdef...
0.2 = a.bcdefg... => a=0
0.4 = b.cdefgh... => b=0
0.8 = c.defghi...   => c=0
1.6 = d.efghij... => d=1
整数部を取り去る
0.6 = 0.efghij...
1.2 = e.fghijk... => e=1
0.2 = 0.fghijk...
以下2行目からの繰り返しになる
したがって、
0.1 = 0.000110011001100110011.....

このように0.1は二進数では無限小数になります。 しかし、実数を収めるメモリサイズは有限なので、そのサイズに収まるように丸められ近似値にされます。 ですから、十進数では誤差無く表せるものがコンピュータの実数(浮動小数点数)では近似値になり、誤差がでることがあります。

print 0.2+0.4, "\n"

これを実行すると

0.6000000000000001

と表示されます。 十進数で計算すればぴったり0.6ですが、コンピュータの浮動小数点数では非常に小さい誤差が積み重なってこのような結果になるのです。

このことから

「実数の比較(特に==)は正しくないことが圧倒的に多い」

ということです。

if 0.2+0.4 == 0.6
  print "equal\n"
else
  print "not equal\n"
end

これを実行すると

not equal

と表示されます。 つまり「0.2+0.4と0.6はイコールでない」ということが起こるのです。

ですから、イコールを用いたいときには何らかの方法で実数を整数に直して計算することが必要になります。 これはRubyに限った話ではありません。 Excelなどでもこのことに注意しないとバグを生み出します。

また、大きさのあまりに違う数字を足すと、小さい方の数字が有効な桁数の外に出て無視されるということも起こります。

print 1000000000+0.000000001, "\n"

実行すると

1000000000.0

となり、第2項は第1項に比べあまりに小さすぎて、計算結果に反映されませんでした。

さて、リテラルでは小数点があるのが実数です。

  • 1 => 整数(Integerクラスのオブジェクト)
  • 1.0 => 実数(Floatクラスのオブジェクト)

同じ名前のメソッドが、異なるオブジェクトでは異なる振る舞いをすることがあるので注意が必要です。

print 1 / 2, "\n"
print 1.0 / 2, "\n"

実行すると

0
0.5

となります。 整数の割り算は小数点以下は切り捨てられますが、実数の割り算は小数点以下まで計算されます。

なお、Rubyには、この他に次のような数のオブジェクト(クラス)があります。

ファイル

Rubyではファイルもオブジェクトですが、整数や文字列のようなリテラルはありません。 ファイルを扱う方法はたくさんありますが、(私が)よく使うのは次のような方法です。

  • File.read(ファイルのパス名) =>ファイル全体を文字列として読み込む。文字列を返す
  • File.readlines(ファイルのパス名) =>ファイルを行ごとに配列要素にして読み込む。その配列を返す
  • File.write(書き込み先ファイルのパス名, 文字列) => ファイルに文字列を書き出す
  • File.exist?(パス名) =>パス名で与えられるファイルが通常ファイルとして存在すればtrue、それ以外はfalseを返す

Fileはファイル・オブジェクトのクラス名です。 Rubyではすべてのオブジェクトに文字列に直すメソッドが用意されているので、ファイルに書き込みたい文字列を書き出すFile.writeメソッドだけでも十分です。

例として、Rubyのファイルを読み込んで「#から行末まで」を削除し、別ファイルに書き出すプログラムを示します。

# This is a comment.
s = File.read("_example/example9.rb") #This is also a comment.
s = s.gsub(/#.*$/, "")
File.write("_example/file2.rb", s)

このプログラムの目論見は「Rubyのコメント部分を削除する」ということなのですが、実はそううまくは行きません。 なぜなら

#から行末まで」と「コメント」は同一ではないからです。

例えば上の例にかかげたRubyプログラムではgsubメソッドの正規表現に含まれる#から行末まではコメントではありません。 「コメントを削除」したいのであれば、Rubyの字句解析的なことをしなければならず、ことはそう単純ではありません。 ここでは時間の都合からバグ取りは省略します。

File.readとFile.writeの使い方を確認してください。 このように簡単にファイルの読み書きができます。

ディレクトリ、ファイル・ユーティリティ

ディレクトリ操作として良く使うのは次のような方法です。

  • Dir.children(ディレクトリのパス名) =>そのディレクトリに含まれるファイル名(...を除く)の配列を返す
  • Dir.glob(パターン文字列) =>文字列をglobパターンとしてそれに一致するファイル名の配列を返す。globパターンはbashのglobパターンのこと
  • Dir.mkdir(ディレクトリ名) =>ディレクトリを新規に作成する(後述のファイル・ユーティリティを使う方法も便利)
  • Dir.exist?(ディレクトリのパス名) =>そのディレクトリが存在すればtrue、なければfalseを返す

ファイル・ユーティリティ「fileutils」というライブラリがあります。 これを使うには

require `fileutils`

include FileUtils

としておきます。

  • cd(ディレクトリのパス名) =>カレントディレクトリを変更する
  • chmod(モード, パス名のリスト) =>リストにあるファイルのパーミッションをモードに変更する
  • cp(ソース, デスティネーション) =>ソースファイルをデスティネーションファイルにコピーする
  • mkdir_p(ディレクトリのパス名) =>ディレクトリを作成する。その親ディレクトリがなければ、それもすべて作成する
  • mv(ソース, デスティネーション) =>ソースファイルをデスティネーションファイルに移動する。ファイル名の変更にも使える
  • remove_entry_secure(パス名) =>パス名で与えられたファイルまたはディレクトリを削除する。ディレクトリの場合はそれ以下の全てのファイルを削除する
  • rm(パス名) =>パス名で与えられるファイルを削除する

fileutilsを使わなくてもDIrとFileクラスを使って同じことはできますが、より少ないタイプ量でできるので便利だと思います。

徒然Ruby(8)シンボルとハッシュ

(追記)ハッシュをシンボルテーブルに使う例を追加しました(2022/9/20)

今回はシンボルとハッシュについて説明します。

Rubyのドキュメントには、シンボルについて次のように書かれています。

Rubyの内部実装では、メソッド名や変数名、定数名、クラス名などの`名前'を整数で管理しています。 これは名前を直接文字列として処理するよりも速度面で有利だからです。 そしてその整数をRubyのコード上で表現したものがシンボルです。

シンボルは、ソース上では文字列のように見え、内部では整数として扱われる、両者を仲立ちするような存在です。

この説明を理解するには、一般にプログラム言語が変数などをどのように管理しているかを、おおまかにでも理解する必要があります。

シンボルテーブル

ここでは非常に簡単なプログラム言語を考え、変数をどう扱えば良いかを考えてみます。 この言語は名前を「mini」ということにします。

  • 変数はアルファベットの並びとする。 「abc」「hello」などは変数とすることができる。 「abc1」は数字が含まれるので変数にはならない
  • 変数には正の整数を代入できる。 「abc = 100」のように「変数 = 正の整数」の形で代入文を表す
  • 変数を表示することができる。 「print abc」のように「print 変数」の形で変数に代入されていた正整数を表示する。 このとき、改行も出力する
  • 空行や空白のみからなる行は無視する

この言語のプログラムは例えば次のようなものです。

abc = 100
efg = 50
print abc
print efg

abc = 200
print abc

このプログラムを実行すると

100
50
200

と表示されます。

では、miniは変数をどのように扱うのでしょうか? プログラムの各行に対するminiの動作は次のようになります。

  • 1: 「abcという名前の変数は100を値に持つ」ことを記録する
  • 2: 「efgという名前の変数は50を値に持つ」ことを記録する
  • 3: 記録から変数abcを探し、その値を出力する(表示する)
  • 4: 記録から変数efgを探し、その値を出力する
  • 5: 空行なので無視して、次の行に進む
  • 6: 変数abcは記録済みである。その変数の値を200に変更する
  • 7: 記録から変数abcを探し、その値を出力する

このことから、miniには変数名とその値を記録する表(table)が必要です。 この表を「シンボル・テーブル」ともいいます。

Rubyでminiを書いてみましょう。 シンボルテーブルは二重配列で表すことにします。 上の例では最終的にシンボルテーブルは

[ [ "abc", 200], [ "efg", 50 ] ]

となります。 miniのプログラムは次のようになります。

@sym_table = []

def lookup(k)
  @sym_table.each do |a|
    if a[0] == k
      return a[1]
    end
  end
  nil
end

def install (k, v)
  @sym_table.each do |a|
    if a[0] == k
      a[1] = v
      return
    end
  end
  @sym_table << [k, v]
end

File.readlines(ARGV[0]).each_with_index do |s, i|
  if /^print +(\w+)$/ =~ s
    k = $1
    v = lookup(k)
    if v
      print v, "\n"
    else
      print "Error #{k} is not defined.\n"
      exit
    end
  elsif /(\w+) *= *(\d+)$/ =~ s
    k = $1
    v = $2
    install(k, v)
  elsif /^ *$/ =~ s
    # ignore
  else 
    print "Syntax error in #{i+1}\n"
    exit
  end
end

@のつく変数がここではじめて出てきました。 この変数は「インスタンス変数」といいます。 インスタンス変数はメソッド定義の外でも内でも参照することができます。 インスタンス変数の説明はクラス定義のところでします。 @sym_tableがシンボルテーブルです。

  • lookup(k)はシンボルテーブルから名前がkの変数をさがし、その値を返す。 変数が未登録の場合はnilを返す
  • install(k, v)はシンボルテーブルに名前がkで値がvである変数を登録する。 変数が既に登録済みである場合はその値を更新する
  • each_with_indexイテレーションでは、まず最初の引数を取り出し、その名前のファイルを行ごとに読み込んで配列にする。 その各行に対して、代入、print、空行、その他に対してそれぞれ必要なことを行う
  • 代入では、installを使って変数をシンボルテーブルに登録したり、値を更新したりする
  • printではlookupを使って変数の値を取得し、表示する
  • 空行は無視(Rubyでは#以下はコメントとして実行せず、無視します)
  • その他の場合はエラーメッセージを出す

このプログラムを動かしてみましょう。 最初に示したminiのプログラムを「mini_sample.txt」という名前で保存しておきます。

$ ruby mini.rb mini_sample.txt
100
50
200
$

期待通りに動作しました。

さて、このプログラムは動作しますが、プログラムが長くなり、変数が多くなると時間がかかるようになります。 ですから、シンボルテーブルへの登録と参照をもっと高速にするアルゴリズムが必要ですが、ここでその説明をすると長くなるので詳しいことは省略します。

シンボル

さて、mini言語の例ででてきた変数名と配列のインデックス(整数)は1対1に対応しています。

  • abcのインデックスは0
  • efgのインデックスは1

これから文字列(変数名)の代わりに整数(インデックス)を使っても良いわけです。 この整数をRubyではシンボルといいます。

ただ、Rubyの実装は上記の例とは全然違うので、例え話と考えてください。 いずれにしても「文字列と整数が1対1に対応する仕組みがあるので、文字列の代わりに整数を使っても良い」というのがシンボルのアイディアです。

シンボルの良いところは、あるシンボルと別のシンボルが同じかどうかはその整数値がイコールかどうかで判断できる、したがって「比較(イコールの判定)を高速にできる」ということです。 このように、シンボルを文字列の代わりに使うと良い面がありますが、逆もあります。 例えばシンボルの文字列を「小文字から大文字に変換する」ためには、いったんシンボルを文字列に直してからその操作を行い、得られた文字列を再びシンボルに直すので、余計な時間がかかることになります。 この場合は文字列のままで操作をするほうが合理的です。

では、シンボルを使うと良いのはどのような場面でしょうか? Rubyのドキュメントには次のように書かれています。

実用面では、シンボルは文字の意味を明確にします。「名前」を指し示す時など、文字列そのものが必要なわけではない時に用います。

  • ハッシュのキー { :key => "value" }
  • アクセサの引数で渡すインスタンス変数名 attr_reader :name
  • メソッド引数で渡すメソッド名 __send__ :to_s
  • C の enum 的な使用 (値そのものは無視してよい場合)

シンボルを使うメリットは

  • 新しく文字列を生成しない分やや効率がよく、比較も高速。
  • 文字の意味がはっきりするのでコードが読みやすくなる
  • immutable なので内容を書き換えられる心配がない

これを現時点で完全に理解するのは難しいと思います。 一番用いられるのは、次の項で説明するハッシュのキーとしてなので、それだけでも覚えておけば十分だと思います。 通常は文字列を使っていれば良いと思います。

さて、シンボルのリテラルは3通りあります。

  • :abc
  • :'abc'
  • %s!abc!

この3つはいずれも文字列abcに対応するシンボルを表します。 最初の書き方が一番使われますが、すべての文字が使えるわけではありません。 アルファベットや数字は大丈夫ですが区切り文字の一部は使えません。 詳しくはRubyのドキュメントで確認してください。

シンボルは文字列オブジェクトと違い、同じ文字列のシンボルはオブジェクトとしても同じです。

  • "abc" == "abc" はtrue。==は異なるオブジェクトでも文字列が同じならばtrueだから
  • "abc".equal?("abc")はfalse。equal?メソッドは同じオブジェクトでなければfalseになるから。 最初の"abc"で文字列オブジェクトが作られ、2番目の"abc"で別の文字列オブジェクトが作られるから。
  • :abc == :abcはtrue。==は同じシンボルならばtrueになる(この「同じ」というのはオブジェクトとして同じと考えても、文字列として同じと考えてもよい)
  • :abc.equal?(:abc)はtrue。同じ文字列のシンボルはひとつしかないから。 最初の:abcでシンボルオブジェクトが作られ、2番目の:abcでは同じオブジェクトが参照されている。

最後に注意を述べます。 「シンボルは文字列を整数で表したもの」といいましたが、シンボル・オブジェクトは整数オブジェクトではありません。 したがって、整数のメソッドのabsなどはシンボルには使えません。 また、シンボルを整数に直すメソッドもありません。 あくまでシンボルは「文字列を表すもの」です。

ハッシュ

ハッシュは配列に似ていますが、インデックスに代わって任意のオブジェクトを用いることができます。

a = [ 10, 100, 1000 ]
h = { "ten" => 10, 100 => 100, nil => 1000 }
print a[0], "\n"
print a[1], "\n"
print a[2], "\n"
print h["ten"], "\n"
print h[100], "\n"
print h[nil], "\n"

実行すると

10
100
1000
10
100
1000

となります。 配列ではインデックスが0から始まる数字で、0番目の要素はa[0]で取り出せました。 ハッシュの場合は任意のオブジェクトに対して値が対応します。 上の例では文字列"ten"に対して整数10が対応するので、h["ten"]で10が取り出せます。

ハッシュのことを連想配列ということもあります。 ハッシュは波カッコで囲み、各要素をコンマで区切ります。 各要素は、「キー」=>「値」というパターンで記述します。

キーとなるオブジェクトにはhashequ?の2つのメソッドが定義されていなければなりません。 Rubyのドキュメントを見ると、Stringクラス(文字列オブジェクトのクラス)にはhashメソッドがありますが、Integerクラス(整数オブジェクトのクラス)にはhashメソッドがありません。 しかし、IntegerクラスにはRubyの組み込みのhash関数が適用されるので、実はhash関数が備わっています。

hashはキーになるオブジェクトからhashメソッドを使い、そのハッシュ値(整数値)を求めて保持して、 a["tex"]を実行する時に、"tex"のhash値とそれを比較して、対応する値を返しています(おそらく)。 キーであるオブジェクトが変更可能で、変更してしまうと、値を取り出せなくなってしまいます。 (そのときはrehashするという方法がありますが)。 ですから、キーは変更不可能なオブジェクトが適しています。 なお、文字列をキーにした場合、「文字列は変更可能」で「キーは変更不可能がのぞましい」という相反する条件を解決するために「ハッシュはキー文字列をコピーし、その複製をフリーズ」します(変更不可にする)。 元の文字列は変更可能のままです。

ハッシュのキーは文字列よりもシンボルの方が適しています。

  • シンボルは元々変更不可である
  • シンボルは比較(イコールかどうか)が高速なのでハッシュの値を高速に取得できる

これより、キーをシンボルで書けるならば、そうすることが望ましいです。

h = { :ten => 10, :hundred => 100, :thousand => 1000 }

この書き方の簡略な方法として、コロンを文字列の右に書くことができます。

h = { ten: 10, hundred: 00, thousand: 000 }

矢印を省略でいるのでタイプ量も減り、効率的です。

ハッシュの例

ハッシュはユーザ管理などに使えます。 ひとりひとりのユーザをハッシュで表し、

a = {name: "山田 太郎", email: "taro@example.co.jp" }
b = {name: "鈴木 花子", email: "hanako@example.com" }

このようにハッシュにすると分かりやすくなります。 同じデータを配列にした場合、どうなるかを見てみましょう。

a = [ "山田 太郎", "taro@example.co.jp" ]
b = [ "鈴木 花子", "hanako@example.com" ]

変数aのユーザ名を表すには、

  • ハッシュならばa[:name]
  • 配列ならばa[0]

明らかにハッシュのほうが分かりやすいです。 項目が増えれば増えるほどハッシュの方に軍配があがります。

ハッシュを使ったシンボルテーブル

このセクションのはじめにシンボルテーブルを説明しました。 その例では配列でシンボルテーブルを作りましたが、これは実用上は遅く、大量の名前を処理するには向きません。 それは配列を頭から検索するのに時間がかかるからです。 そこで、シンボルをキーにしたハッシュならば時間を短縮できるのではないかと考えました。 シンボルの比較は文字列より高速だからです。

その実験として、「不思議の国のアリス」の単語の頻度を調べるプログラムを作ってみました。 ルイス・キャロルの書いた「不思議の国のアリス」(Alice's Adventures in Wonderland)は1865年刊行ですでに著作権が切れています。 そして、「プロジェクト・グーテンベルク」というウェブサイトにその電子版が掲載され、無料でダウンロードできます。 そこからプレーンテキスト版「pg28885.txt」をダウンロードして使います。 単語は、アルファベットのみからなるので、文字列メソッドのsplitを使いました。

@a = File.read("pg28885.txt").split(/[^A-Za-z]+/)
  • File.read(ファイル名)でファイルを読み込み、それを文字列にしたものを返す
  • splitメソッドは引数を区切りとして文字列を分割し、その配列を返す
  • /[^A-Za-z]+/正規表現で、アルファベット以外の文字が1つ以上続くものを表す

これで単語の配列が得られます。 以下のプログラムでは配列を頭から検索する方法(wc1)と、シンボルをキーとしたハッシュを使う方法(wc2)を行い、 ベンチマーク・オブジェクトを使って時間計測を行いました。

require 'benchmark'

@a = File.read("pg28885.txt").split(/[^A-Za-z]+/)

# 配列による単語の頻度調査

def install_a (w)
  @word_table_a.each do |a|
    if a[0] == w
      a[1] += 1
      return
    end
  end
  @word_table_a << [w, 1]
end

def wc1
  @word_table_a = []
  @a.each { |s| install_a(s) }
  @word_table_a.sort{|a,b| a[1] <=> b[1]}.reverse.take(10)
end

# ハッシュによる単語の頻度調査

def install_s (w)
  s = w.to_sym
  if @word_table_s.has_key?(s)
    @word_table_s[s] += 1
  else
    @word_table_s[s] = 1
  end
end

def wc2
  @word_table_s = {}
  @a.each { |s| install_s(s) }
  @word_table_s.to_a.sort{|a,b| a[1] <=> b[1]}.reverse.take(10).map{|a| [a[0].to_s, a[1]]}
end

# 両者の結果が同じかどうかチェック

def w_test
  if wc1 != wc2
    print "wc1 != wc2\n"
  end
end

# ベンチマーク

def bm
  Benchmark.benchmark(Benchmark::CAPTION, 14, nil) do |rep|
    rep.report("wc with array") { wc1 }
    rep.report("wc with hash") { wc2 }
  end
end

# 最も多く現れた単語から10番目まで

def top10
  wc2.each do |a|
    print "#{a[0]}:  #{a[1]}\n"
  end
end

# 実行する作業を選択

# w_test
# top10
bm

このプログラムでは3つの作業を選択できます

  • w_test: 2つの方法で行った結果が等しくなるかのチェック
  • top10: 上位10単語とその出現回数を表示
  • bm: ベンチマーク。両者の実行時間を計測して表示

ベンチマークの結果は次のとおりです。

                     user     system      total        real
wc with array    1.345434   0.000000   1.345434 (  1.345614)
wc with hash     0.027752   0.000000   0.027752 (  0.027792)

ハッシュを用いたほうが圧倒的に速くその時間の比は約48:1です。 この方法が最速ではありませんが、プログラム作成の容易さから考えると有力な手法であると思います。 なお、上位10単語は以下のとおりでした。

the:  1716
and:  886
to:  827
a:  687
of:  616
it:  550
I:  545
she:  520
said:  465
in:  420

冠詞のtheが圧倒的に多く、次がandでした。 saidが9位に入っているのは物語だからでしょう。

徒然Ruby(7)文字列と正規表現

文字列は最も使うオブジェクトのひとつです。 特にウェブ・アプリケーションでは、コンテンツだけでなくHTMLのタグやCSSを含めすべてが文字列です。 Rubyは文字列オブジェクトのメソッドが充実しており、またパターンマッチのための正規表現も充実しています。

文字列リテラル

文字列リテラルには

  • ダブルクォートで囲む(今まで使ってきた方法)
  • シングルクォート(')で囲む
  • %記法
  • ヒア・ドキュメント

があります。 ここでは最も使うダブルクォート、シングルクォートとヒアドキュメントを説明します。 その他の記法はRubyのドキュメントを参照してください。

ダブルクォートで囲む文字列リテラル

この記法はすでに出てきていますが、まだ説明が不十分でした。 ここでは、(1)この文字列リテラルの補足説明(2)バックスラッシュ記法(3)式展開を説明します。

ダブルクォートの文字列では

  • 空白を挟んで複数の文字列があれば、それらは結合されてひとつの文字列とみなされる
  • 文字列の途中に改行があれば、それは文字列中の改行コードになる(改行の代わりに\nがあるのと同じ)
  • 改行が文字列の途中でなければ(つまり複数の文字列の間であれば)改行前と後の2つの文字列に分かれる
a = "abc" "def" "ghi"
b = "abc
def"
c = "pqr" "stu"
"vwx"
print a, "\n"
print b, "\n"
print c, "\n"

これを実行すると

abcdefghi
abc
def
pqrstu

となります。

  • 変数aに代入された文字列は"abcdefghi"と同じ
  • 変数bに代入された文字列は"abc\ndef"と同じ
  • 変数cに代入された文字列は"pqrstu"と同じ
  • "vwx"は変数に代入されていないから、文字列として評価されるが、近いうちに消滅する(ガベージ・コレクション)

なお、ガベージ・コレクションというのは、どこからも参照されていない不用なオブジェクトを消滅させて、割り当てられていたメモリを解放することをいいます。 これをしないとどんどん不用なオブジェクトにメモリが割り当てられて、最悪の場合はメモリ不足になってしまいます。 ガベージ・コレクションはRubyが自動的に行ってくれます。

英数字やコンマ、ピリオドなどの表示できる文字以外に、改行やタブなどのコードを表すにはバックスラッシュ記法を用います。 最も使うのは改行\nです。 それ以外には次のようなものがあります(全部ではありません)

  • \t =>タブ
  • \文字 =>文字はtのような特殊な意味をもつもの(\tはタブになる)以外の文字。 その文字自身になる。 たとえば、\##自身を表す。 #は後ででてくる式展開でも使うので、#自身を表したい時にバックスラッシュをつける
  • \改行 =>改行を取り消す。複数行に文字列が渡るときに改行を抑止するために使う

なお、文字列でなくても改行直前にバックスラッシュがあるとその改行は抑止されます。 ですので、次の2つは同じ文字列を表します。

a = "abcd\
efg"
b = "abcd" \
"efg"
print a, "\n"
print b, "\n"

式展開は文字列中に#{式}の形で書き、式を文字列で表したものに置き換えます。 例えば、式の部分が整数の100であれば、それを文字列の"100"として文字列中に埋め込みます。

number = 100
print "数字は#{number}\n"

実行すると

数字は100

と表示されます。 式はもっと複雑なものでも構いません。 式展開は非常に便利で、文字列で最も良く使われる機能です。

シングルクォートで囲む文字列リテラル

シングルクォートで囲む文字列は、ダブルクォートのようなバックスラッシュ記法や式展開をしません。 シングルクォート自身を含めたい時にエスケープが必要なので、\'とバックスラッシュ記法を使います。 これにともない、バックスラッシュ自身もエスケープが必要な場合が出てくるので\\と表します。 行末のバックスラッシュはバックスラッシュ自身として解釈されます。

a = 'abc\ndef'
b = 'abc\\ndef'
c = '\abc
def'
d = 'abc\
def'
print a, "\n"
print b, "\n"
print c, "\n"
print d, "\n"

実行すると

abc\ndef
abc\ndef
\abc
def
abc\
def

となります。 シングル・クォートでは文字をその文字自身として表したい時に使いますが、バックスラッシュの扱いだけは注意が必要です。

ヒア・ドキュメント

ヒア・ドキュメントは<<EOSの次の行から行頭のEOSまでのすべての行からなる文字列です。 複数行に渡る長い文字列を表すためのものです。 EOSの代わりに任意の文字列を使っても構いません。

a = <<EOS
 春はあけぼの。やうやう白くなりゆく山ぎは、すこしあかりて、紫だちたる雲のほそくたなびきたる。
 夏は夜。月のころはさらなり。やみもなほ、蛍の多く飛びちがひたる。また、ただ一つ二つなど、ほのかにうち光りて行くもをかし。雨など降るもをかし。
 秋は夕暮れ。夕日のさして山の端いと近うなりたるに、烏の寝どころへ行くとて、三つ四つ、二つ三つなど、飛びいそぐさへあはれなり。まいて雁などのつらねたるが、いと小さく見ゆるはいとをかし。日入りはてて、風の音、虫の音など、はたいふべきにあらず。
 冬はつとめて。雪の降りたるはいふべきにもあらず、霜のいと白きも、またさらでもいと寒きに、火など急ぎおこして、炭もて渡るもいとつきづきし。昼になりて、ぬるくゆるびもていけば、火桶の火も白き灰がちになりてわろし。
EOS

print a

実行すると、枕草子の第一段が表示されます。 つまり、変数aの指す文字列は4行の長い文字列であるわけです。

EOSにダブルクォートをつけ<<"EOS"とするとダブルクォート文字列と同じように、バックスラッシュ記法と式展開を使えます。 同様にEOSにシングルクォートをつけ<<'EOS'とすると挟まれた文字列そのものになります。 このとき文中のシングルクォートにエスケープは必要ありません。 なお、クォートのない<<EOSはダブル・クォートと同じ扱いです。

インデントが可能ですがそれについてはドキュメントを参照してください。

パターンマッチ

文字列の検索には正規表現が用いられます。 正規表現の実装は一通りではありません。 現在のRuby(バージョン3.1)では「鬼雲」という正規表現ライブラリが使われています。 他の言語で正規表現を使ったことがあっても、細かい点ではRubyと異なる可能性があります。 また、Ruby正規表現は多岐にわたっているので、詳細はドキュメントを参照してください。 ここでは主な書き方に絞って説明します。

正規表現リテラル

正規表現はオブジェクトのひとつです。 正規表現は文字列の検索に用いられ、検索パターンの情報とメソッドを持っています。 また、正規表現は文字列、整数、配列と同じようにリテラルで表現することができます。 正規表現の場合はスラッシュ(/)で文字列を囲んで表します。 リテラルの規則は非常に多いので、ここですべてを解説することはできません。 詳細はRubyのドキュメントを参照してください。

例えば文字列"Hello"を検索することを考えてみます。 "Hello"という検索パターンを正規表現リテラルで表すと、

/Hello/

となります。 そして、そのパターンが文字列に含まれているかどうかを調べるには次のようにします。

a = "Hello world. Hello world.\n"
if /Hello/ =~ a
  print "マッチした\n"
else
  print "マッチしなかった\n"
end

print (/Hello/ =~ a), "\n"

=~は左辺の正規表現パターンが右辺の文字列の中に存在するとき(それを「マッチする」という)マッチした位置(インデックス)を返し、マッチしなかったときnilを返します。 このプログラムの文字列にはHello worldが2つありますが、はじめのHello worldにマッチするので、返される値は0です。 プログラムの最後の一行は0を表示します。 プログラムの実行結果は次のようになります。

マッチした
0

0はif文では真になることに注意してください。 if文で偽になるのはnilとfalseだけです。

Helloだけでなくhelloも検索対象にしたいときは

/[Hh]ello/

という正規表現を使います。 [ ]は文字クラスという仕組みで、その中にある文字のどれかに一致します。 更にすべて大文字のHELLOも入れたければ

/[Hh]ello|HELLO/

縦棒|はその左のパターンまたは右のパターンのどちらかにマッチすれば良い、という意味になります。 この他に次のようなものがあります。

  • メタ文字(正規表現で特別な意味を持つ文字)は( ) [ ] { } . ? + * | \で、これらの文字自身を表すにはバックスラッシュでエスケープする
  • #{ }で式展開できる(ダブルクォート文字列と同じ)
  • \nは改行にマッチ。その他にバックスラッシュで表す特別な文字(タブなど)がある
  • .(ドット)は「任意の1文字」にマッチ
  • [ ]で文字クラスを表す。 例えば[atc]はa、t、cのいずれにもマッチする。 [a-z](小文字全体)のようにハイフンで範囲を表せる
  • *は前の文字の0個以上の繰り返しにマッチ
  • +は前の文字の1個以上の繰り返しにマッチ
  • ^は行頭にマッチ
  • $は行末にマッチ

これ以外にも豊富なメタ文字がありますので、Rubyのドキュメントを参照してください。

Ruby版のgrep

Unixの文字列検索コマンドgrepRuby版を作ってみましょう。 名前をrubygrep.rbとすることにします。 コマンドラインにパターン、ファイル名の2つの引数を取り、パターンにマッチした行を標準出力に出力します。

$ ruby rubygrep.rb '^\#{1,6} ' 2022-9-10-Array.md

この例では2022-9-10-Array.mdという名前のファイルを検索します。 パターンは

  • ^は行頭に一致
  • \#はナンバー記号(#)。 正規表現リテラルでは式展開でこの記号(#)を使うので、バックスラッシュでエスケープしておく
  • {1,6}は1以上6以下の繰り返し
  • 半角空白は(文字通り)半角空白に一致

これらを総合すると「行頭からナンバー記号が1から6個並び、空白が続く行」となります。 すなわち、Markdownの見出しを検索することになります。

プログラムを作るために、ファイルの読み込みを説明しておきましょう。 ファイルを行ごとの配列に読み込むには

File.readlines(ファイル名)

というメソッドを使います。 rubygrep.rbは次のようになります。

r = Regexp.compile(ARGV[0])
File.readlines(ARGV[1]).each do |s|
  if r =~ s
    print s
  end
end
  • Regexp.compile(文字列)は文字列を正規表現に変換する。 コマンドラインでは正規表現リテラルとして書くことができないので、文字列としてRubyに伝わる。 Rubyではそれを正規表現オブジェクトに変換する必要がある
  • File.readlines(ARGV[1])でファイルを行ごとに読み、その配列を返す。 その配列に対してeachメソッドで各要素を取り出してパラメータsに代入しブロックを実行する
  • ブロックではsが正規表現rに一致すればsをprintで標準出力に書き出す

Ruby正規表現をサポートしているので、非常に短いプログラムで済みました。 実行してみます。

$ ruby rubygrep.rb '^\#{1,6} ' 2022-9-10-Array.md
## 配列のリテラル
## 2次元配列
## 配列のメソッド
## 配列の変更と複製
## ARGV

見出しの部分だけ表示されました。 期待通りの結果です。

なお、マッチの演算子=~は実は正規表現オブジェクトのメソッド.=~()の糖衣構文です。

r =~ s # => r.=~(s)

メソッド名は=~です。

したがって、演算子の左側は正規表現オブジェクトでなくてはならないのですが、実は左右を逆にしても使えます。

"Hello world. Hello world.\n" =~ /Hello/

これを糖衣構文でメソッドに直すと.=~()は文字列のメソッドになってしまいます。 実は文字列オブジェクトでも.=~()メソッドを定義していて、それは演算子の左右を入れ替え、正規表現.=~()メソッドの値を返すことになっています。 事情は複雑ですが、要するに正規表現と文字列を左右どちらに置いてもOKということです。

マッチデータオブジェクト

検索時あるいは検索後にマッチした文字列を取り出したいことがあります。

例えば「Markdown文書の目次を自動生成する」ことを考えてみましょう。 目次の対象になるのは見出しです。 見出しはATXタイプ(ナンバー記号が行頭に来るタイプ)だけを対象にします。

見出し検索の正規表現パターンを次のようにします。

/^\#{1,6} +(.+)$/
  • 行頭から1から6個のナンバー記号
  • 1個以上の半角空白
  • 任意の文字が行末まで

括弧( )には2つの機能があります。

  • グループを表す。数式の括弧と同じ
  • マッチした文字列のその部分を取り出す。

マッチには=~またはmatchメソッドを使います。

=~でマッチを行った場合は、その直後に前から1番目の( )でマッチした文字列が$1に、以下n番目の( )にマッチした文字列が$n$に代入されます。

a = "## 配列のリテラル"
if /^\#{1,6} +(.+)$/ =~ a
  print $1, "\n"
end

このプログラムでは( )がひとつしかありません。 if文のマッチは成立し、括弧とマッチする文字列は「配列のリテラル」ですので、それが$1に代入されています。 プログラムを実行すると、

配列のリテラル

と表示されます。 注意が必要なのは、$1は次のパターンマッチが行われると内容が変わってしまうことです。 ですから、マッチ文字列をとっておきたいときは、s = $1など、別の変数に$1の内容を代入しておくことです。 これをうっかりしたバグが結構あります。

matchメソッドを使うと、マッチが実行され、その結果がMatchDataオブジェクトとして返ってきます。 MatchDataオブジェクトはマッチの情報を保持するオブジェクトです。 $1に相当するデータは.[1]メソッドで取得できます。

m = /^\#{1,6} +(.+)$/.match("## 配列のリテラル")
print m[1], "\n"

mには正規表現で文字列に対してマッチを行った結果のMatchDataオブジェクトが代入されます。 m[1]で1番めの括弧に対応するマッチ文字列が得られますので、結果は

配列のリテラル

が表示されます。 文字列検索でマッチした文字列を得るにはmatchメソッドが便利で、私もよく使います。 このときの注意点としては

  • 文字列と正規表現の左右を入れ替えても同じMatchDataオブジェクトが得られる。 このへんの事情は=~メソッドと同様
  • 文字列が正規表現とマッチしないときはmatchメソッドはnilを返す。 このとき[ ]を使うと、nilにはそのメソッドがないのでエラーになってしまう。 そこで、.to_aメソッドで配列に変更しておくとエラーを避けることができる

2番めの注意点はもう少し説明が必要です。

  • MatchDataオブジェクトのto_aメソッドは、マッチした全体の文字列が0番目の要素、以下n番目の( )にマッチする文字列がn番目の要素となる配列を返す
  • nil.to_aは空の配列[ ]を返す

これを具体的なプログラムで説明しましょう。

m = /^\#{1,6} +(.+)$/.match("## 配列のリテラル")
print m[1], "\n"
print m.to_a[1], "\n"
m = /^\#{1,6} +(.+)$/.match("配列のリテラル")
print m.class, "\n"
print m.to_a[1]
print m[1]

これを実行すると

$ ruby example7.rb
配列のリテラル
配列のリテラル
NilClass

example7.rb:6:in `<main>': undefined method `[]' for nil:NilClass (NoMethodError)

print m[1]
       ^^^

となります。 行ごとに説明します。

  • 1: マッチが成立し、mにはMatchDataオブジェクトが代入される
  • 2: MatchDataオブジェクトmには[ ]メソッドがあり、1番目のマッチ文字列が表示される=>「配列のリテラル」が表示される
  • 3: MatchDataオブジェクトが配列["## 配列のリテラル", "配列のリテラル"]to_aメソッドで変換される。 更に[1]で配列の1番めの要素が取り出され、表示される=>「配列のリテラル」が表示される
  • 4: マッチが成立せず、mにはnilが代入される
  • 5: m.classはmのクラス名を返す=>「NilClass」が表示される
  • 6: m.to_anilから空の配列になる。 空の配列の1番めの要素が無いので、nilが返される。 printはnilを空文字列として出力する=>空行が出力される
  • 7: nilには[ ]メソッドがないのでエラーになる=>NoMethodErrorが表示される

長い説明になりましたが、まとめると、

「MatchDataオブジェクトに[ ]メソッドを使うときはその前にto_aメソッドを使おう」

ということです。

文字列のメソッド

文字列には便利なメソッドがいっぱいありますが、ここでは検索と置換に関係するメソッドを取り上げます。

subとgsub

subとgsubは置換メソッドです。 その違いは、subが最初にマッチした部分の置換だけをするのに対して、gsubは繰り返しマッチと置換を行います。 そして置換によってできた新規文字列を返します。 元の文字列オブジェクト自身は変更されません。

a = "abcdefg"
b = a.sub(/def/, "DEF")
print "a = ", a, "\n"
print "b = ", b, "\n"

実行してみます。

a = abcdefg
b = abcDEFg

メソッドsubによってdefがDEFに置換され、変数bに代入されました。 元の文字列と置換された文字列は別のオブジェクトですから、表示されたaとbは異なります。

元の文字列自身を破壊的に置換したいときはsub!メソッドを使います。 エクスクラメーションマークのついたメソッドは「破壊的」であることが多いです。 さきほどのプログラムをsub!に置き換えて実行してみましょう。

a = "abcdefg"
b = a.sub!(/def/, "DEF")
print "a = ", a, "\n"
print "b = ", b, "\n"
a = abcDEFg
b = abcDEFg

sub!はマッチしたときは元の文字列に置換をほどこし、その文字列を返します。 したがって、変数bはaと同じ文字列を指しています。 その結果aとbが両方とも置換後の文字列になって表示されます。

注意しなければならないのはsubとsub!の返り値の違いです。

  • subはパターンがマッチすればそこを置換した新しい文字列オブジェクトを返し、マッチしなければ元と同じ内容の文字列を新規に生成して返します。 いずれの場合も元の文字列オブジェクトとは別の文字列オブジェクトを返します。
  • sub!はパターンがマッチすれば、元の文字列の中で置換してそれを返し、マッチしなければnilを返します。

特にマッチしないときの振る舞いが全然違うので、返し値を利用する場合は注意が必要です。

print "abcdef\n".sub(/ddd/,"DEF").sub(/ab/,"AB")
print "abcdef\n".sub!(/ddd/,"DEF").sub!(/ab/,"AB")

これを実行すると

ABcdef
example7.rb:2:in `<main>': undefined method `sub!' for nil:NilClass (NoMethodError)

print "abcdef\\n".sub!(/ddd/,"DEF").sub!(/ab/,"AB")
                                  ^^^^^

1行目は、最初のsubメソッドでマッチが起こらなかったので元と同じ内容の新規文字列を返し、その文字列に対して2番めのsubメソッドを実行した結果が表示されます。

2行目では、最初のsub!メソッドでマッチが起こらなかったのでnilが返され、nilに対してsub!メソッドを実行しようとしましたが、nilにはそんなメソッドが無いのでエラーになります。

さて、subの2番めのパラメータには、マッチした文字列を埋め込むことができます。

  • \0はマッチした文字列全体
  • \1\2、・・・は( )の1番目、2番目・・・のマッチした文字列
  • バックスラッシュ自身を表したいときはエスケープする(\\とする)

例えば

print "abcdefg\n".sub(/d(..)g/,'\1')

これを実行すると

abcef

と表示されます。 パターンにdefgがマッチし、( )に相当する部分がefなので、置換文字列の\1にはefが代入されます。 その結果、defgがefに置換されます。

この例では文字列にシングルクォート文字列を使いました。 ダブルクォート文字列を使うこともできますが、その場合バックスラッシュはエスケープしなければなりません。 したがって、

print "abcdefg\n".sub(/d(..)g/,"\\1")

このように\\1と書かなければならないことに注意してください。 なお、シングルクォートでもバックスラッシュをエスケープ可能ですので\\1はOKです。

置き換え文字列の中に\1などを埋め込めるのは便利に思うかもしれませんが、バグを生みやすいことにも注意が必要です。 置換文字列がリテラルで与えられるなら問題はないのですが、外部から取り込んだ文字列(例えばファイルを読み込んで得られた文字列)などの場合に\1などが入っていると意図しない動作になるおそれがあります。

このような問題を避けるには、ブロック付きのsubを使う方法があります。 このsubはマッチが起こった時にブロックを実行し、その値への置換を行います。 ブロックの中では、$0$1・・・の形でマッチした文字列、( )に対応する部分文字列を参照できます。 これらは以前述べたマッチが起こった直後のグローバル変数$0$1・・・です。

a = '\1'
print "abcdefg\n".sub(/d(..)g/){ a+$1 }

ブロックの中では\1に対する置き換えはないので、

abc\1ef

と表示されます。 以上のことから、単純ではない置換や置換先の文字列が不確定の場合はブロック付きのsubが推奨されています。 なお、ブロックにはパラメータをひとつ置くことができ、パラメータはマッチした文字列全体が引数として代入されます。

以上subを見てきましたが、実用的には繰り返し置換するgsubの方がよく使われます。 gsubは「繰り返し」置換する点だけがsubとの違いなので、subでの注意点はそのままgsubにも通用します。

split

splitメソッドは、区切りを表す文字列を引数にとり、区切られた文字列の配列を返します。 例えば

print "a,b,c".split(",")
print "\n"

を実行すると

["a", "b", "c"]

と表示されます。

引数は正規表現も可能です。 例えば、コンマ区切りデータを配列に変換するには、区切りにコンマと改行の文字クラスを指定すれば良いです。

c = <<EOS
2,3,5,7,11
13,17,19,23,29
31,37,41,43,47
53,59,61,67,71
73,79,83,89,97
EOS
c.split(/[,\n]/).each do |i|
  print i, "\n"
end

このプログラムを実行すると2から97までが表示されます。

コンマ区切りデータをきちんと定義したものをCSV(Comma separated value)といいます。 RubyではCSVを扱うライブラリがあるので、そちらを使うほうがより実用的です。

scan

scanは繰り返しマッチを行い、マッチした文字列を配列にして返すメソッドです。 コンマ区切りデータから数字を取り出す例をscanで書き直してみましょう。

c = <<EOS
2,3,5,7,11
13,17,19,23,29
31,37,41,43,47
53,59,61,67,71
73,79,83,89,97
EOS
c.scan(/\d+/).each do |i|
  print i, "\n"
end

\d[0-9]と同じで、十進数クラスを表します。 scanは十進数の連続を探して取り出しています。 splitを使ったプログラムと違い、区切りは十進数以外であれば何でもOKです。

今回splitとscanはデータの取り出しに使いましたが、更に広い応用もあります。 特にドキュメントを加工、変換するようなプログラムでは活用が期待できます。

徒然Ruby(6)配列

配列は、どのプログラミング言語にもあると思います。 複数の要素を一括して扱うことができるのが配列です。 Rubyの配列はメソッドが充実しているので、プログラムを効率的、機能的に書くのに役立ちます。

配列のリテラル

配列を表すには角括弧を用います。

a = [1, 2, 3]

変数aに配列が代入されています。 右辺は配列を表していて、この配列には1,2,3の3つの要素があり

  • 0番目の要素が1
  • 1番目の要素が2
  • 2番目の要素が3

です。 「〜番目」の「〜」の数字をその要素のインデックスといいます。 インデックスは0から始まることに注意してください。 また、インデックスには負数を用いることもできます。

  • -1番目の要素が3
  • -2番目の要素が2
  • -3番目の要素が1

つまり、負数の場合はおしりから前に戻ってきます。

配列を表すのに角括弧をつかいました。 この配列を表す記法を「リテラル」といいます。 同様にダブル・クォートで囲んだ記法は文字列のリテラルです。 整数のリテラルは、その整数自身を書けばよく、整数オブジェクトの10は単に「10」と書きます。 このようにリテラルとはそのオブジェクトを直接表現する記法のことです。

変数が配列を表すとき、要素を参照するには[ ]を用います。

a = [1, 2, 3]
print a[1], "\n"

printの引数がコンマ区切りで複数渡されています。 このとき、printは各引数に対して画面表示を行います。 ひとつひとつに分けてprintするのと同じ動作になります。

a[1]は配列の1番めの要素を表します。 インデックスは0から始まるのでa[1]は2になります。

a[5]のように、配列に要素がないときはnilが返されます。 nilは「何もない」ということを表すオブジェクトだと考えてほぼ間違いありません。

配列の要素に代入するには[ ]=を使います。

a = [1, 2, 3]
a[1] = 5
print a, "\n"

実行してみると

[1, 5, 3]

と表示されます。 1番目の要素が2から5に変わり、print aで配列を表示します。 printは配列をこのように表示してくれるので便利ですね。

a[6] = 10のように、何もないところに要素が代入されると、その前の空いていたところにはnilが代入されます。

[1, 5, 3, nil, nil, nil, 10]

配列はRubyのオブジェクトのひとつです。 ですので、配列はメソッドを持ちます。 実は要素の参照と代入もメソッドです。 [ ][ ]=は糖衣構文で、それぞれメソッド.[].[]=に直して実行されます。

a[2] => a.[](2)
a[3] = 5 => a.[]=(3, 5)

ひとつめは、「[]」がメソッド名で2が引数、ふたつめは「[]=」がメソッド名で3と5が引数です。

配列の要素には任意のオブジェクトを代入できます。 配列自身には型がなく、いろいろな種類のオブジェクトがひとつの配列に混在しても構いません。

文字列の配列を表すには[ ]とダブルクォートを用いて

[ "a", "b", "c" ]

と書けますが、%記法というのを使うと少し労力を減らすことができます。

abc = %w!a b c!
print abc, "\n"

実行すると

["a", "b", "c"]

と表示されます。 %記法では、「%w」が「文字列の配列を表す」という意味で、「!」(区切り)で挟まれた部分の空白で区切られた各文字列が要素になります。 文字列に!を入れたいときは、他の文字(非英字)を区切り文字に使います。

abc = %w|! !! !!!|
print abc, "\n"

実行すると

["!", "!!", "!!!"]

となります。 括弧類を区切り文字にするときは左括弧と右括弧を組み合わせます。

ab = %w(睦月 如月 弥生)
cd = %w[卯月 皐月 水無月]
ef = %w{文月 葉月 長月}
gh = %w<神無月 霜月 師走>
print ab, "\n"
print cd, "\n"
print ef, "\n"
print gh, "\n"

実行すると

["睦月", "如月", "弥生"]
["卯月", "皐月", "水無月"]
["文月", "葉月", "長月"]
["神無月", "霜月", "師走"]

%記法ではダブルクォートを書かなくて良いのが、意外に楽になった感じがします。

1から10までの整数を要素に持つ配列を作るにはRangeオブジェクトを使うと簡単です。 Rangeオブジェクトは整数の範囲を表すオブジェクトです。 例えば(1..10)は1から10までの整数を表します。 このRangeオブジェクトにはto_aというメソッドがあります。 to_aはその範囲の数字からなる配列を作ります。

a = (1 .. 10).to_a
print a, "\n"

実行すると

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

となります。 同様に1から1000までの配列も

a = (1..1000).to_a

で簡単に作れますね。 [ ]リテラルで書くと気が遠くなるでしょう。 C言語では、forループを使って作るのが最も合理的だと思いますが、それでもRubyの短いコードで解決できるのには敵いません。

2次元配列

たてよこに広がりを持つデータを配列で表すことができます。 例えばカレンダーを考えてみましょう。

カレンダー

2次元データは縦に1行目、2行目・・・、横に1列目、2列目・・・と数えます(なので、数学では2次元データを「行列」というわけです)。 1行目は7つのマスがあり、それを配列で表すと

[nil, nil, nil, nil, nil, 1, 2]

nilは「そこに何もない」ということを表します。 2行目以下も同様に配列で表せます。 それを配列で表すと

calendar = [
  [nil, nil, nil, nil, nil, 1, 2],
  [3, 4, 5, 6, 7, 8, 9],
  [10, 11, 12, 13, 14, 15, 16],
  [17, 18, 19, 20, 21, 22, 23],
  [24, 25, 26, 27, 28, 29, 30]
]

となります。 配列は1行で書いてもよいのですが、長くなってわかりにくいので改行して行ごとに書きました。 カレンダーの3行目は配列で表せます。

calendar[2] #=> [10, 11, 12, 13, 14, 15, 16]

更に、左から4番めのマスは

calendar[2][3] #=> 13

となります。 [ ]は糖衣構文で、Rubyはメソッドに直してから実行するのでした。

calendar[2] => calendar.[](2)

[]がメソッド名で、2はメソッドの引数。このメソッドは[10, 11, 12, 13, 14, 15, 16]を返す。 返されたものが配列なので、再び[]メソッドが使え、

calendar[2][3] => (calendar.[](2)).[](3) => [10, 11, 12, 13, 14, 15, 16].[](3) => 13

となります。 以上から、上から3行目、左から4列めの要素はcalendat[2][3]と表せます。 数字が1だけずれるのは、1から数え始めるか0から数え始めるかの違いから起こります。 どちらも0から始めることに直せば、

上から2行目、左から3列めの要素はcalendat[2][3]

ということになります。 一般に

上からm行目、左からn列めの要素はcalendat[m][n]

です。

このように、たてよこに広がりのあるデータは配列を二重に使うことによって表せます。 この二重の配列を2次元配列といいます。

注意したいのは、2次元配列という新たなオブジェクトが作られたわけではなく、もともとあった(1次元)配列とそのメソッドを繰り返し使っただけだということです。 ですから、3回繰り返せば3次元配列、4回繰り返せば4次元配列・・・・というように拡張できます。

配列のメソッド

配列には多くの有用なメソッドがありますが、ここではその一部を紹介します。 まず、eachとeach_indexはよく使われるメソッドです。 eachは配列要素を順に取り出しブロックを実行します。

[1,2,3,4,5].each do |i|
  print i, "\n"
end
  • 最初の要素1をパラメータiに代入してブロックを実行=>1が表示される
  • 次の要素2をパラメータiに代入してブロックを実行=>2が表示される
  • 以下、順に5までパラメータiに代入して実行

実行結果は次のようになります

1
2
3
4
5

each_indexは要素のインデックス(0からはじめて何番目か)をパラメータにします。

a = ["", "", ""]
a.each_index do |i|
  print i
  print " => "
  print a[i]
  print "\n"
end

実行してみます。

0 => 青
1 => 黃
2 => 赤

この2つに似たメソッドにeach_with_indexがあり、これは要素とインデックスの2つをパラメータに渡します。

["青", "黃", "赤"].each_with_index do |x, i|
  print i
  print " => "
  print x
  print "\n"
end

出力結果はeach_indexの例と同じになります。

Rubyでは、eachイテレータを使うことが多いです。 for文やwhile文もRubyにはあるのですが、イテレータのほうが読みやすく分かりやすくなることが多いです。

配列の変更と複製

配列は変更可能なオブジェクトです。 それは、[ ]=で要素を代入できることからも分かります。 その他に配列自身を変更するメソッドには次のようなものがあります。

  • << => 配列の最後に要素を加え、できた配列を値として返す。 糖衣構文によって、演算子のように記述できる。 例えば、[1,2] << 3によって、配列が[1, 2, 3]に変更され、その配列がメソッドの値として返る。 メソッドで書き直すと[1,2].<<(3)となる。
  • appendまたはpush。 この2つは同じ動作のメソッドで、引数(複数可)を配列の後ろに加える。 [1, 2].append(3, 4) => [1, 2, 3, 4]。 引数は配列[3, 4]にしても同じ結果になる
  • pop => 配列の最後の要素を取り除き、その取り除いたオブジェクトを値として返す。 引数が正の整数であれば、その数だけ取り除く
  • clear => 配列の要素をすべて取り去り、空の配列にする。 このとき、配列の要素だったオブジェクトは残る。 配列がそれらのオブジェクトを要素として指さなくなったということ
  • delete => 引数と同じ要素をすべて取り除く
  • delete_at => 引数はインデックスを表す。そのインデックスの要素を取り除く

この他にも配列自身を変更するメソッドがありますが、詳細はRubyのドキュメントを参照してください。 なお、自身を変更するメソッドを「破壊的」メソッドといいます。

clear、delete、delete_atなどで説明したように、配列から削除することはそのオブジェクトを消滅させることではありません。 配列は変数に似ていて、各オブジェクトを「指している」だけです。

この点はCの配列とは異なります。 Cの配列は型があり、その型の「配列の要素数分のメモリが確保されます。 そして配列のメモリの中に値が書き込まれていきます。 したがって、配列のある要素を変更すると、以前の値は消えてしまいます。 Rubyの場合は指しているオブジェクトが変わるだけで以前のオブジェクトは消滅しません。

a = "abc"
b = "def"
c = [a, b]
print c, "\n"
c[0] = "ABC"
print a, "\n"
print c, "\n"

これを実行すると次のようになります。

["abc", "def"]
abc
["ABC", "def"]

配列cの0番目の要素は文字列オブジェクト"abc"から"ABC"に変わりましたが、元のオブジェクト"abc"は残っていて、変数aを表示した時にabcが表示されています。

配列はオブジェクトを指しているだけなので、配列を複製(コピー)して新たな同じ内容の配列をオブジェクトとして作成するとき、各オブジェクトは複製されないことに注意してください。 複製はdupメソッドまたはcloneメソッドで行います。 この2つは細かい点で振る舞いが違いますが、ここではその説明は省略します。

Dupのイメージ

このようなコピーをシャロー・コピー(shallow copy)といいます。

シャロー・コピーをすると同じオブジェクトを異なる配列から参照しているので、一方でオブジェクトを変更(破壊的メソッドを使う)と他方の配列にも影響します。 そのためバグを生じやすいので注意しなければなりません。 これを避けるには

  • 破壊的メソッドを使わない。 例えばa[0]=="abc"のとき、a[0].replace("ABC")は破壊的メソッドで文字列オブジェクトが変更される。 それに対してa[0]="ABC"は新しい文字列"ABC"を生成して0番目の要素を取りかえるだけで元のオブジェクトは変更されずに残る
  • オブジェクトの複製も行う。 eachメソッドを使って各オブジェクトの複製を作るなどの方法があります。 このとき元の配列と複製された配列は違うオブジェクトを指すので、破壊的メソッドを使っても他の配列に影響はありません
  • オブジェクトを変更禁止にしておく。 freezeメソッドを使うとオブジェクトは変更禁止になります。 すべての破壊的メソッドは効かなくなります(エラーになる)

のようなことが考えられます。 一番良いのは安易に配列の複製をしないことだと思います。

ARGV

コマンドラインからRubyプログラムを実行する時に引数を渡すことができます。 引数は配列ARGVに渡されます。 大文字から始まる識別子は定数を表します。 ARGVは定数なのでこれに代入することはできません。

ARGV.each do |x|
  print x, "\n"
end

これをexample6.rbに保存して、実行してみましょう。

$ ruby example6.rb Hello world
Hello
world
$

引数は半角の空白で区切られてARGVに格納されます。 eachメソッドで引数をひとつずつ取り出して表示しています。

引数が何個あるかを調べたいときがあります。 そのときはsizeメソッドを使います。

徒然Ruby(5)ローカル変数、文字列、インスタンス

今回の目標はインスタンスです。 インスタンスを説明するために、ローカル変数と文字列オブジェクトを事前に扱います。

ローカル変数

変数にはローカル変数、インスタンス変数、クラス変数がありますが、今回はローカル変数のみ説明します。

ローカル変数は(1)英小文字またはアンダースコア(_)で始まり(2)英文字、数字、アンダースコアが続きます。 例えばabc_d55などは変数を表すことができます。 これらはメソッド名にもなりうるので、それが変数なのかメソッド名なのかはプログラムの中で判断します。

ローカル変数はその変数に対する代入文がはじめて現れた時に、代入と同時に定義されます。 一度定義されると、そこからその変数が宣言されたブロック、メソッド定義、またはクラス/モジュール定義(次回以降に説明)の終りまで有効です。 この有効範囲をスコープといいます。 メソッド定義の外側で定義されたローカル変数はメソッド定義の中では参照できません。

abc = 10
print abc
print "\n"

def abcdefg
  print "Hello world.\n"
  print abc
  print "\n"
end

abcdefg

このプログラムexample5.rbとして保存し、実行するとエラーになります。

$ ruby example5.rb
10
Hello world.
example5.rb:7:in `abcdefg': undefined local variable or method `abc' for main:Object (NameError)

  print abc
        ^^^
        from example5.rb:11:in `<main>'
$

プログラムを順を追って見ていきましょう

  • ローカル変数abcに10を代入する
  • print abcで10が画面に表示される=>実行画面の最初の10
  • メソッドabcdefgを定義する。 メソッドは
    • Hello world.を画面に表示し
    • ローカル変数abcを表示し、改行を表示する
  • メソッド定義では実行はしないので、エラーにはならない。
  • メソッドabcdefgを呼び出し、実行する
    • Hello world.を画面表示する=>実行画面2行目のHello world.
    • ローカル変数abcの値を画面表示しようとするが、abcが未定義なのでエラーになる=>3行目以降のエラーメッセージ
  • エラーになったのは1行目で定義したローカル変数abcはメソッド内部では参照できないから。 つまり、メソッド内部はスコープではないからです。

abcをメソッドの外に移せば、スコープになるのでエラーにならずに10が表示されます。

abc = 10
print abc
print "\n"

def abcdefg
  print "Hello world.\n"
end

abcdefg
print abc
print "\n"

実行すると

10
Hello world.
10

また、メソッドの中でローカル変数を定義すると、その変数はメソッドの中だけで有効で、外はスコープ外になります。

文字列と変数

変数には文字列を代入することもできます。

a = "Hello world.\n"
b = a
a = "Good by.\n"

print b

ちょっとややこしいプログラムなんですが、これを実行すると画面には何が現れるでしょうか

  • 1 Hello world. が表示される
  • 2 Good by. が表示される

実行してみましょう

Hello world.

1が正解でした。 プログラムを順に見ていきます。

  • 変数aに文字列"Hello world.\n"が代入された=>aは文字列"Hello world.\n"を指している
  • 変数bに変数aの指している文字列を代入した=>bは文字列"Hello world.\n"を指している
  • 変数aに文字列"Good by.\n"が代入された=>aは文字列"Goodby.\n"を指している

最後の代入は変数'b'には関係ありませんから、bは相変わらず文字列"Hello world.\n"を指しています。

Rubyの文字列は可変です。 これはRubyの特徴で、良い場合と困る場合があります。 可変であるためにバグを生みやすいということは言えると思います。

例えば、文字列は[]=を使って文字を書き換えできます。

a = "free\n"
a[0] = "t"
print a

このプログラムを実行するとtreeが表示されます。

  • 変数aに文字列"free\n"が代入された
  • aの指している文字列の0番目の文字(文字を数えるとき最初の文字は0番目です)を"t"に変える=>"free\n"が"tree\n"になる
  • aの指す文字列"tree\n"を画面に表示

この[]=というのは配列のn番目の要素を代入するのに似ています。 文字列は文字の配列だと考えれば自然な演算です。

さて、次のプログラムを実行すると何が表示されるでしょうか

a = "free\n"
b = a
a[0] = "t"

print b
  • 1 freeが表示される
  • 2 treeが表示される

実行すると、"tree"が表示されます。 2が正解です。 なぜでしょうか?

  • 変数aに文字列"free\n"が代入された
  • 変数bに変数aの指している文字列を代入した=>bは文字列"free.\n"を指している=>aとbは同じ文字列を指していることに注意
  • aの指している文字列の0番目の文字を"t"に変える=>"free\n"が"tree\n"になる =>bもaと同じ文字列を指しているので、bは"tree\n"を指している
  • bの指す文字列"tree\n"を画面に表示

何となくわかったでしょうか? 正しく理解するためには、インスタンスを理解することが必要です。

インスタンス

Rubyでは、文字列も数字もプログラムの対象になるものはすべてオブジェクトです。 オブジェクトはその中に状態を保ち続けることができる変数とメソッドを持ったもので、そのオブジェクトがどういう変数やメソッドを持っているかを定義しているものをクラスといいます。 クラスに基づいて、メモリ上にオブジェクトを実現したものをインスタンスといいます。 ひとつのクラスに対して通常は複数のインスタンスが可能です。

なお、「オブジェクト」と「インスタンス」という言葉はそれぞれのプログラミング言語により、使い分けられています。 Rubyの場合は両者は同じと考えて差し支えありません。 ですが、「インスタンス」というときは、「クラス」をメモリ上に実体化したというニュアンスが強くなります。 また、オブジェクトは「オブジェクト指向」という概念的な意味で使うこともあります。

例えば文字列のクラスはStringといいます。 "Hello world\n"というダブルクォートで囲まれた文字列がプログラム中に現れると、RubyはStringクラスのインスタンスを作ります。 インスタンスはメモリ上に作られ、その文字列がHello world\nであるという情報や、String固有のメソッドがあるという情報を保持します。

a = "Hello world.\n"

RubyはまずHello world\nという文字列のインスタンスをメモリ上に作成し、その場所を変数aに代入します。 変数aはそのインスタンスを指しているだけです。

a = "Hello world.\n"
a = "Good by.\n"
a = "Hello world.\n"
b = "Hello world.\n"

この場合、Hello world.\nという文字列インスタンスが2つ作られます。 aとbが指しているインスタンスは別のインスタンスです(文字列としては同じですが)。 インスタンスにはオブジェクトidという番号が振られ、区別できるようになっています。 オブジェクトidはobject_idメソッドで知ることができます。

print "Hello world.\n".object_id
print "\n"
print "Hello world.\n".object_id
print "\n"

これを実行すると

60
80

のようになります。 (あなたが実行するとき60や80とは違う数字になるかもしれません)。 同じ文字列だが、インスタンスとしては別だということがidの違いからわかります。

もう理解できたとは思いますが、最後に1問。

a = "free\n"
b = "free\n"
a[0] = "t"
print b

このとき、

  • 1 freeが表示される
  • 2 treeが表示される

のどちらですか?

答えは1です。 aとbは別のインスタンスを指しているので、aの指している文字列が変わってもbの指している文字列は変わっていません。

「同じ」ということ

a = "free\n"
b = "free\n"

このとき、aとb(正しくはaの指している文字列とbの指している文字列)は同じでしょうか? これはちょっとむずかしい問題です。

そこで、どちらを基準に考えているかによって2種類の「同じ」を判断する計算が必要になります。

  • a == b => aとbが同じ文字列ならtrue、違う文字列ならばfalse
  • a.eql?(b) => aとbが同じ文字列ならtrue、違う文字列ならばfalse
  • a.equal?(b) => aとbが同じインスタンスならtrue、違うインスタンスならば(文字列として同じであっても)false

eql?equal?は文字列のメソッドです。 実は==も文字列のメソッドで、a == ba.==(b)というメソッドだとして評価されます。 Rubyではメソッド名にアルファベットや数字だけでなく==?のような記号も使えるのが面白いところで、長所です。 この==メソッドとeql?は、ほぼ同じです。 詳しくはRubyのドキュメントを見てください。

糖衣構文

このように==という論理演算子Rubyではメソッド.==( )に直されて評価されるのはなぜでしょうか(==のようなメソッドと別形式が用意されているときその別形式を糖衣構文またはシンタックス・シュガーといいます)。 それは「同じ」ということの意味がオブジェクトごとに違うからです。 また、実装上も==をオブジェクトごとに定義するのが楽です。 (むしろ、一般的な==演算子を作り、どのオブジェクトにも通用するようにするのは事実上無理です)

同じことは+という演算子についてもいえます。 実はこれも糖衣構文で

10 + 5 => 10.+(5)

このように整数のメソッドに直して評価されます(メソッド名が+)。 整数の場合は算術的な加算としてメソッドが定義されていますが、文字列では連結としてメソッドが定義されています。

"abc" + "def" => "abc.+("def") => "abcdef"

他のオブジェクトでも+メソッドを定義することができます。 加算がオブジェクトごとに意味づけられるというのは面白い考えです。

整数のインスタンス

さて、整数の場合は文字列と違って可変ではありません。 このとき同じ整数を表すインスタンスを複数作るのは非効率です。 そこで整数の場合はあるひとつの整数を表すインスタンスは一つしか存在しません。

print 100.object_id
print "\n"
print 100.object_id
print "\n"

これを実行すると

201
201

となります。 1行目の整数100と3行目の整数100は同じオブジェクトidなので、インスタンスとしても同じとわかります。

Rubyの変数とCの変数の違い

Rubyの変数はオブジェクトを指しているだけで、変数自体に型はありません。

Cの場合は変数に型がかならず付けられます。

int n;

n = 10;

変数nはint型(整数型)として定義されます。 このとき、Cではnに対して整数を格納できるサイズのメモリを割り当てます。 n=10;の代入文で、そのメモリに10が格納されます。

このようにCでは型とメモリが変数に結びついています。

int a, b;

a = b = 100;

このとき、aとbはそれぞれメモリを割り当てられて、100はそれぞれのメモリに代入されます。 このあと、aのメモリを変更してもbの値は変わりません。

Rubyの場合は

a = b = "free\n"

a[0] = "t"

aとbは同じオブジェクトを指しているので、aがオブジェクトの内容を変更すると、bも同じものを参照しているので内容が変わります。

C言語習得者がRubyを習うと、このあたりで躓くことが多いと思います。 Rubyの変数はCのポインタのようなものだと思うとわかりやすいと思います。

さて、最後に「オブジェクト」と「インスタンス」についてもう一度。 今回は「インスタンス」を多用しましたが、Rubyのドキュメントでは「オブジェクト」の方が多く用いられています。 今後もそのときの文脈でどちらかを選択して使いますが、意味は同じだと考えてください。

徒然Ruby(4)トップレベルのメソッド定義

今回はメソッド定義です。 メソッド定義はRubyの核心ですが、今回はトップレベルに限って説明します。 この限定によって、内容はかなり易しくなっています。

トップレベルに限ればメソッド定義はCの関数定義とほとんど変わりません。

メソッド定義と実行

メソッドはdefからendまでで定義をすることができます。

def example
  print "Hello world.\n"
end

example

このプログラムを説明しましょう。

  • defの後にメソッド名を書く。 このメソッドはexampleという名前のメソッド。
  • endの前までがメソッドの本体。 ここでは「Hello world.と表示する」ことを定義。 定義中は実行はしない。 定義と実行は別
  • メソッドを呼び出すにはメソッド名を書く。 メソッドは呼び出されると、自身を実行する

このプログラムexample4.rbというファイルの保存し、実行すると次のようになります。

$ ruby example4.rb
Hello world.
$

定義したメソッドは何回でも呼び出せます。

def example
  print "Hello world.\n"
end

example
example
example

これを実行すると

$ ruby example4.rb
Hello world.
Hello world.
Hello world.
$

となります。 また、前回学んだイテレータを使うこともできます。

def example
  print "Hello world.\n"
end

3.times do
  example
end

実行結果は前と同じです。

$ ruby example4.rb
Hello world.
Hello world.
Hello world.
$

Cを学んだ人ならば、メソッド定義はCの関数定義に似ていると思うでしょう。 現時点ではそのような理解で良いと思います。

パラメータと引数

メソッドにはパラメータを付け加えることができます。 パラメータは括弧の中にコンマで区切って書きます。

def sum(a, b)
  a + b
end

print sum(5, 10)
print "\n"

実行すると15が表示されます。

  • sumメソッドには2つのパラメータabがある
  • メソッドsuma + bを計算する
  • メソッド定義で最後に計算された値が、そのメソッドが実行されたときの「値」になる
  • print の引数はsum(5, 10)である。sum(5, 10)はメソッドsumに引数5と10を与えて実行したときの「値」である。 5と10はパラメータのaとbに代入され、メソッドにより5+10=15が計算され、15が値として返ってくる。 したがって、printは15を表示する
  • 最後にprintは改行を画面に出力する

「パラメータ(parameter)」と「引数(argument)」という言葉の区別ですが、

  • メソッド定義でメソッド名の後ろの括弧にある文字(aやb)はパラメータ
  • メソッド呼び出し(メソッドを実行すること)のときにメソッド名の後ろの括弧につけるオブジェクト(5や10)が引数

です。

Rubyでは括弧を省略することができます。 そのとき必要に応じて空白を入れて区切りを明確にします。

def sum a, b
... ... ...
print sum 5, 10

括弧は演算の順序を示すときにも使われます。

(2+3)*(4+5)

この括弧は省略できません。 省略すると

2+3 * 4+5

これは、2+12+5=19になってしまうからです。 *の両側に空白があったからといって、2+34+5を先に計算してはくれません。

Rubyではメソッド名と左括弧の間に空白を入れないことが大事です。 一般にRubyではメソッドの括弧を省略しそこに代わりの空白を入れることが可能なので、構文の解釈に曖昧さが生じます。

def double x
  2*x
end

print double (2) + 3
print "\n"

print double(2) + 3
print "\n"

これを実行すると

10
7

と表示されます。 最初のdoubleの呼び出しではdouble(の間に空白があるので、メソッドの括弧が省略され、(2)が計算の順序を表す括弧だと解釈されたのです。 それで、まず(2)+3=5を計算して、その5をメソッドdoubleに引数として渡し、2倍され、10が表示されたのです。 つまり、

double((2)+3)

と解釈されたのです。

2番めのdoubleの呼び出しではdoubleの直後に(があるので、括弧が引数を表す括弧だと解釈され、2が倍になって4、そのあと3が加えられて7が表示されました。

(double(2))+3

このようなことが生じるのは、Rubyが括弧の省略を許しているためです。 とにかく、メソッドの括弧はメソッド名の直後に書くようにしてください。

if文

多くの言語と同じようにRubyにもif文があります。

def even_or_odd(n)
  if n.even?
    print "偶数です\n"
  else
    print "奇数です\n"
  end
end

even_or_odd(3)
even_or_odd(6)

実行すると

奇数です
偶数です

となります。

  • メソッド名にアンダースコア(_)も使える
  • パラメータnは整数だと仮定する。整数のメソッドeven?を呼び出す。 このメソッドはnが偶数ならtrue(真)、そうでなければfalse(偽)を返す。
  • if文では、ifの後ろが真であれば直後を実行し、偽であればelse以下を実行する。 なおif文の「真」とは、「falseでもnilでもない」こと。 falseやnilもオブジェクトの一種。 したがって、整数の0や空文字列""は真とみなされる。

ifの右側を「条件」といいますが、ここにはいろいろな演算子が使えます。 良く用いられるのは

  • == 等しい
  • > 左が大きい。>=左が大きいかイコール
  • < 右が大きい。<=右が大きいかイコール

などです。

  • else以下は省略できる
  • ifとelseの間にelsifを入れることができる。 前のif(またはelsif)の条件が偽でelsifの条件が真のとき、直後の文が実行される

再帰呼出し、パラメータのスコープ

メソッドのパラメータはそのメソッドの中でのみ有効です。 これはメソッド定義でもメソッド呼び出しでもそうです。 これをパラメータのスコープといいます。 スコープは有効範囲ともいいます。

メソッドが複数回呼び出されるとき、それぞれの呼び出しに対応するパラメータはすべて別変数になります。 このことは再帰呼出しをするときに重要になります。

def fact(n)
  if n<1
    nil
  elsif n==1
    1
  else
    n*fact(n-1)
  end
end

print fact(5)
print "\n" 

これを実行すると

120

と表示されます。

  • ここでは引数nは整数であると想定する
  • 引数nが1より小さければnilを返す
  • 引数nが1ならば1を返す
  • それ以外(1より大きな整数)のときは、nにn-1のときのfactの値を掛けて返す

最後が複雑ですが、順に考えればわかりやすいです。

  • n=2のときは「n=1のときのfactは1なので、2*1=2」
  • n=3のときは「n=2のときのfactは2なので、3*2=6」
  • n=4のときは「n=3のときのfactは6なので、4*6=24」
  • n=5のときは「n=4のときのfactは24なので、5*24=120」

このメソッドは定義の中で自分自身を呼んでいます。 これを「再帰呼出し」といいます。 再帰呼出しのとき、そのどの呼び出しにおいてもパラメータは別物なので、このような計算結果になります。

再帰呼出しは、この例の階乗(factorial)計算に限らず、使えるケースが少なくありません。

まとめ

  • トップレベルのメソッドはCの関数(あるいは他の言語の関数や手続き)のように使うことができる
  • メソッドにはパラメータをつけることができる。 パラメータはそのメソッドの中でのみ有効
  • パラメータはメソッド名直後(から次の)まで。 括弧は略すことができる。 そのときは左括弧の代わりに半角空白を入れる
  • メソッドを再帰呼出しすることができる
  • if - elsif - else - end が使える

徒然Ruby(3)ブロックとイテレータ

前回の整数の後半の内容はあまりプログラミングで使われない内容になっていました。 すみません。 今回は極めて有用なことを書きます。 ブロックとイテレーションです。

繰り返し

同じことを何回もやるときのブログラムについて述べます。例えばHello worldを3回表示したければ、

print "Hello world.\n"
print "Hello world.\n"
print "Hello world.\n"

でも良いのですが、もっと良い方法があります。 整数のメソッドにtimesというのがあります。 これはそのメソッドに付属するブロックをその整数の回数だけ繰り返すというメソッドです。

3.times do
  print "Hello world.\n"
end

doからendまでを「ブロック」といいます。 ブロックにはRubyの任意のプログラムを書けます。 timesメソッドはその整数の回数だけ繰り返すので、10回繰り返したければ10のtimesメソッドを使えば良いのです。

10.times do
  print "Hello world.\n"
end

他の言語にもブロックがあるのかどうかは知りませんが、少なくともCにはありません。 Cで同じようなことをするには関数へのポインタを使いますが、Rubyのようにスマートにしかも短く書くことはできません。

timesのように繰り返しをするメソッドでループを表現することをイテレーションといいます。

uptoメソッド

整数のメソッドで有用なもう一つはuptoです。 このメソッドには引数があります。

1.upto 5 do |i|
  print i
  print "\n"
end

整数1のuptoメソッドは1から始めて引数の5までをブロックのパラメータのiに代入してループします。 パラメータの文字はiでなくても、例えばnumberのような6文字でも構いません。 この文字は変数というものの一種です。 変数は文字列ではないのでダブルクォートで囲むことはしません。 変数については別の回で詳しく説明します。

パラメーターはそのブロックの中でのみ有効です。iには最後に5が代入されますが、ブロックが終了すると、iは消えてしまいます。ブロックの外でiを参照しても5にはなりません。

ブロックのパラメータは縦棒|で囲むことになっています。 それに対してメソッドの引数はカッコ()で囲むか、半角空白でメソッドと引数を離すのでした。 メソッドとブロックは似ていますが、本質的に違うものです。

実行すると

1
2
3
4
5

と表示されます。

uptoに似たメソッドでdowntoがあります。 これはその整数から1ずつ小さくなって、引数まで繰り返します。

10.downto(6) do |i|
  print i
  print "\n"
end

ここではメソッドdowntoの引数にカッコをつけました。 半角空白で離してカッコを省略する書き方もできます。 実行すると

10
9
8
7
6

と表示されます。

ブロックの2つの書き方

ブロックは、do, endで囲みましたが、これを波カッコにすることもできます。

1.upto(5) {|i| print i; print "\n"}

波カッコでは、ブロックが短く、1行で書ける時に使う人が多いと思います。 注意すべきは

  • 引数にはカッコをつけなければいけない。 メソッドとカッコの間に半角空白を入れない。 この理由は、Rubyがdo-endよりも{ }の方がその前の引数と強く結びつくためです(構文解析上の問題)。
  • 1行に2つの文を入れるときはセミコロンで区切る

ということです。

私は以前はdo-endを多く使っていましたが、最近は波カッコが多くなってきました。 というのは、Rubyのメソッドは優れていて、一行でブロックが収まることが多いからです。 どちらを使うかはメソッドによって9割くらい決まるように思います。 今回のtimes、upto、downtoはdo-endを使うことが多いかもしれません。

まとめ

今回はRubyの最も優れた点のひとつであるブロックを紹介しました。 ブロックは基本的にメソッドの引数の最後に現れます。 ブロック単独で現れることもあるのですが、それはかなり高度な話になります。 通常はブロックはメソッドと一緒に使われると考えて差し支えありません。

次回はメソッドをトップレベルで定義することを扱おうと思います。 この場合のメソッドは今回のブロックにちょっと似ています。