rubyを使うとかなり頻繁に利用するのがEnumerableオブジェクト(イテレータ)である。
意識して使ってないけど、配列のeachメソッドやループのtimesなどがこれに該当する。
Enumerableオブジェクト
組み込みオブジェクトのArrayやHashがincludeしているモジュール。
Enumerableをincludeして、eachメソッドを定義してあげれば独自モジュールでもEnumerableが提供する様々なメソッドを利用出来る。
Arrayを使った場合のコード
array = [1, 2, 3]
# eachでループ
array.each {|x|
puts x
}
# 添字付きでループ
array.each_with_index {|x, i|
puts "#{i} = #{x}"
}
出力結果はこんな感じ
1
2
3
0 = 1
1 = 2
2 = 3
独自クラスの場合
class Hoge
include Enumerable
def initialize(num)
@num = num
end
def each
@num.each {|num|
# prefixにhoge_を付加する。
yield "hoge_#{num}"
}
end
end
出力結果はこんな感じ
Enumerableで定義されてるgrepもちゃんと使えてるのが分かる。
each start...
hoge_1
hoge_2
hoge_3
hoge_4
grep start...
hoge_2
hoge_3
ruby始めた頃は、each_with_indexなんて知らなくて、こんなループをかいてた・・・。
まぁ、動くから間違ってはないけど直感的ではないよね。一瞬なにがしたいんだ?って思ってしまう。
array.count.times {|i|
puts "array[#{i}] = [#{array[i]}]"
}
Enumeratorオブエクト
each以外のメソッドでもEnumerableを使うためのラッパークラス
Enumeratorはイミュータブルなので、生成したあとの状態変更はできない。
なので、配列でパラメータ渡しをするのではなくEnumeratorを指定したほうが良い。
# eachメソッドを定義しているクラスのto_enumを呼び出すと、Enumeratorを生成できる。
enum = array.to_enum
enum.each {|x|
puts x
}
# enumメソッドを定義していないクラスの場合
class Hoge
def initialize(num)
@num = num
end
def hoge
@num.each {|num|
yield "hoge_#{num}"
}
end
end
hoge = Hoge.new([1, 2, 3, 4])
# enum_forメソッドに、eachと同義のhogeメソッドを指定して Enumeratorを生成する。
enum = hoge.enum_for(:hoge)
puts "each start..."
enum.each {|x|
puts x
}
puts "grep start..."
enum.grep(/^.*[23]$/) {|x|
puts x
}
実行結果はこんな感じ
hogeメソッドしか持っていなくても、Enumerableのメソッドを利用できている。
each start...
hoge_1
hoge_2
hoge_3
hoge_4
grep start...
hoge_2
hoge_3