徒然Ruby(10)便利なメソッド - おもこん

おもこん

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

徒然Ruby(10)便利なメソッド

ここでは私が便利だと思ったメソッドを紹介します。

配列の便利なメソッド

map

mapは配列で最も使うメソッドのひとつです。 各々の要素に対してブロックを実行し、その値からなる新しい配列を返します。

print [1,2,3].map {|i| i+1} #=>[2, 3, 4]

#から改行まではRubyのコメントです。 コメントは実行の対象ではなく、プログラマーが説明やメモを書くためのものです。 Rubyの習慣として#=>は実行したときの画面出力を書いたり、式の値を書いたりします。 上のプログラムのコメントの意味は「プログラムを実行すると[2, 3, 4]が画面に現れる」ということです。 今までプログラムと実行結果を別々に書いていたのですが、この書き方でよりコンパクトに書くことができます。

  • 配列[1,2,3]にmapメソッドが実行される
  • mapメソッドは配列要素をひとつづつ取り出し、ブロックを実行する。 ブロックは波カッコで囲まれた部分(ブロックはdo〜endでも波カッコでも表せる)
  • 最初に配列から1が取り出されiに代入される。 ブロックで「i+1」が実行されると「1+1=2」となり、「2」がブロックの値になる
  • その値がmapメソッドの作る配列の最初の要素になる
  • 以下、配列の次の要素2を用いたブロックの値が3、3を用いたブロックの値が4なので、mapの返す配列は[2, 3, 4]となる。

ひとことでこのプログラムを表現すると「配列の各要素に1を加えた配列を求める」ということになります。 mapは配列オブジェクトを新たに作るので、元のオブジェクトは変更されません。

ディレクトdir1/dir2以下のすべてのファイルのパス名の配列を作り、表示するには

d = "dir1/dir2"
paths = Dir.children(d).map {|file| d + "/" + file}
paths.each {|p| print "#{p}\n"}

mapメソッドもeachメソッドもブロックが短いので波カッコを使うのが良いと思います。 そうすることでプログラム全体の行数を抑えることができ、プログラムの全体を見渡すことができるようになります。

私は以前はdo〜endをブロックに用いることが多かったのですが、最近は波括弧が多くなってきました。 その使い分けはブロックが長いか短いかで決めています。

  • Dir.children(d)でディレクトリd直下のファイル名の配列が得られる
  • mapは各ファイル名に親ディレクトリをつけたパス名にし、その配列を返す
  • eachで各パスを順に取り出しprintメソッドで1行に1要素を画面出力する

mapでできることはeachでもできます。 最初の1を足す例をeachで書き直すと

b = []
[1,2,3].each {|i| b << i+1 }
print b, "\n" #=> [2, 3, 4]

eachを使う場合は空の配列bを用意して、そこに1を加えた要素を追加していくことになります。 mapより複雑なことをしていることがわかると思います。

一般に、配列は関連した要素(例えばあるディレクトリ直下のファイル全体)の集合を表します。 そのような集合の要素に対しては「共通の操作」を行うことが多いです。 mapはそのような共通の操作をまとめて行います。 ひとつひとつの要素に操作をするeachと比べ、まとめて行えるmapの方が優れているといえます。

inject

injectは「たたみこみ演算」を行います。 要素をひとつずつ取り出し、操作を行い、その結果を次の要素との演算に使います。 例えば配列[1,3,5]の要素を合計する操作を考えましょう。

  • 最初に1を取り出す
  • 次の要素3を加え(1+3=4)その結果4を次の操作に使う
  • 次の要素5を加え(4+5=9)その結果9が答えになる

この操作は同じ演算を繰り返し行っています。 これが「たたみこみ演算」です。

print [1,3,5].inject {|i, j| i+j}, "\n" #=> 9
  • 最初の要素1がiに代入される
  • 次の要素3がjに代入される
  • 1回目のブロックの計算(1+3=4)が行われる
  • 2回目の計算では前回の結果4がiに代入される
  • 次の要素5がjに代入される
  • ブロックの計算(4+5=9)が行われる
  • 9がinjectメソッドの値として返される

計算の初期値として引数を与えることもできます。 例えば、mapで[1,2,3]から[2,3,4]を作ったのと同じことをinjectで実現することができます。

print [1,2,3].inject([]) {|i, j| i << j+1}, "\n" #=>[2,3,4]
  • 最初に初期値として空の配列[ ]がiに代入される
  • 初期値がある場合は「次の要素」は最初の要素1になる。 1がjに代入される
  • 1回目のブロックの計算([ ] << 1+1)が行われる。 配列に(破壊的に)2が付け足され、[2]になり、その配列が返される
  • 2回目の計算では前回の結果[2]がiに代入される
  • 次の要素2がjに代入される
  • ブロックの計算([2] << 2+1)が行われる。 配列に(破壊的に)3が付け足され、[2,3]になり、その配列が返される
  • 3回目の計算では前回の結果[2,3]がiに代入される
  • 次の要素3がjに代入される
  • ブロックの計算([2,3] << 3+1)が行われる。 配列に(破壊的に)4が付け足され、[2,3,4]になり、その配列が返される
  • [2,3,4]がinjectメソッドの値として返される

これは、eachメソッドを使ったのと同じ方法です。 injectはeachに比べ、1行でコンパクトに書けるのが長所です。 私は以前はeachを使っていたのが、最近はinjectを使うようになりました。

sort

sortは配列をソートする(整列する)メソッドです。 たとえば

  • [1,3,2].sort =>[1,2,3]を返す(この配列は新規に作られたもので元の[1,3,2]はそのまま)
  • `["bird", "dog", "cat"].sort =>["bird", "cat", "dog"]を返す。文字列はアルファベット順(より正しくは文字コード順)になる

ソートをするためには、配列要素に<=>メソッドが定義されていることが必要です。 このメソッドはa <=> bに対し

  • a が b より大きいなら正の整数
  • a と b が等しいなら 0
  • a が b より小さいなら負の整数
  • a と b が比較できない場合は nil

を返します。 整数や文字列には<=>演算子があらかじめ定義されています。 なお、この演算子は「宇宙船演算子」といわれます。 その形がUFOに似ているからだといわれますが、その由来には諸説あるようです。 この演算子は様々なプログラム言語に実装されていて、比較(<=>)の元になる役割を果たしています。

sortは宇宙船演算子が定義されていないオブジェクトに対しては、ブロックで大小を評価することによってソートすることができます。 また、宇宙船演算子があっても、別の基準でソートしたいときはブロックを用います。 例えば、[1,3,2]を大きい順にソートするには、宇宙船演算子の符号を逆にするためにマイナスをかけます。 以下では小さい順と大きい順の両方を示します。

print [1,3,2].sort, "\n" #=>[1,2,3]
print [1,3,2].sort{|a,b| -(a<=>b)}, "\n" #=>[3,2,1]

数の文字列"9"、"5"、"13"、"20"、"12"、"4"を文字列としてソートするのと、数字としてソートするのでは結果が違います。

a = ["9", "5", "13", "20", "12", "4"]
print a.sort, "\n"
print a.sort{|a,b| a.to_i<=>b.to_i}, "\n"

to_iは文字列を整数に変換するメソッドです。 実行すると

["12", "13", "20", "4", "5", "9"]
["4", "5", "9", "12", "13", "20"]

となります。 文字列のソートは辞書順なので"12"が"4"よりも前にきます。

その他の配列でよく使うメソッド

uniqは重複を除くメソッドです。

[1,2,2,2,3].uniq #=>[1,2,3]

これは文字列の配列を扱っている時に使うことが多いです。

include?はあるオブジェクトが配列の要素になっているときにtrue、そうでないときにfalseを返します。

[1,3,5,7].include?(7) #=> true
[1,3,5,7].include?(8) #=> false

each系のメソッドは大変良く使います。 eachはすでに解説したので、このセクションでは省略します。

to_sメソッド

to_sメソッドはすべてのオブジェクトで実装されています。 そのオブジェクトを文字列に直すメソッドです。

1.to_s #=> "1"
1.23.to_s #=> "1.23"
[1,2].to_s #=> "[1, 2]"
1..2.to_s #=> "1..2"
{one: 1, two: 2}.to_s #=> {:one=>1, :two=>2}

このメソッドはprintメソッドの中で使われています。 printメソッドは、引数が文字列でなければto_sメソッドを使って文字列に直して表示します。 そのおかげで、任意のオブジェクトをprintが出力できるわけです。

ここでちょっとした注意。

print {one: 1, two: 2}

とするとエラーになります。 これはこのプログラムに曖昧さがあるためです。 メソッドの次には引数だけでなくブロック(実はブロックも引数なのですが)が来る可能性があります。 波括弧がブロックを表すとすると、ブロックの中が「one: 1, two: 2」で、これを無理やり実行しようとするとコロンのところでエラーになってしまいます。 このような曖昧さを避けるには丸括弧を使ってください。

print ({one: 1, two: 2})

printと丸括弧の間にスペースが無いほうが普通なのですが、あったとしてもこの文は実行できます。

to_sメソッドはダブルクォート文字列の式展開でも使われます。 式展開では、その式が文字列でなければto_sを使って文字列に直してから埋め込みます。 "abc = #{100}"では、100は整数なのでto_sが使われます。 このようにto_sは様々な場面で背後で活躍しているわけです。

to_sを直接使うことは少ないと思いますが、to_sのおかげでプログラム中の表現が簡潔になっていることが多いです。

<<メソッド

<<メソッドは文字列、配列、整数などで使われますが、オブジェクトによって意味が違うメソッドです。 また、このメソッドは二項演算子として使えますが、糖衣構文によってメソッドであると解釈されて実行されます。

"abc" << "de" #=> "abc".<<("de") => "abcde"
  • 文字列では、元の文字列に引数の文字列を破壊的に繋げ、その文字列を返す
  • 配列では、引数を配列に破壊的に付け加え、その配列を返す
  • 整数では、整数を二進数とみてそのビットを左に引数分だけずらす。 逆の演算子>>があり、これは右にビットをずらす。 このとき小数点以下は切り捨てられる

余談ですが、機械語に近いレベルでは左シフトが2倍、右シフトが1/2倍を高速で行える演算としてよく用いられます。

chomp、chop、strip系メソッド

文字列のメソッドです。

  • chomp =>文字列末尾の改行を取り除いた新しい文字列オブジェクトを返す
  • chop =>文字列の最後の文字を取り除いた新しい文字列オブジェクトを返す
  • strip =>文字列の前後の空白文字を取り除いた新しい文字列オブジェクトを返す
  • lstrip =>文字列の先頭の空白文字を取り除いた新しい文字列オブジェクトを返す
  • rstrip =>文字列の末尾の空白文字を取り除いた新しい文字列オブジェクトを返す

lengthまたはsize

配列や文字列で使うメソッドで要素数、文字数を返します。

Time.now

現在の時刻のTimeオブジェクトを返します。

print Time.now, "\n" #=> 2022-09-19 16:10:15 +0900

年月日、時分秒が表示されます。 最後の「+900」はAsia/Tokyoの協定標準時(UTC)からの時差です。 したがって、UTCは「2022-09-19 7:10:15」になります。 UTCとの差が無いのはヨーロッパやアフリカのいくつかの国です。 アイスランドレイキャビクの標準時はUTCに一致します。