今回 Ruby Gold 2.1 を受験するので,Ruby 1.8 から Ruby 2.1 で追加された機能を勉強しておく必要がある.
詳しくは Ruby Association から出てる以下の PDF に書いてあるのでしっかりと理解する.
Enumerable#lazy
PDF の P11 に書かれてる Enumerable#lazy
の例を見てみた.
なるほど.1以上の自然数を二乗した結果を3個抽出している.遅延評価だから無駄に計算しないようになってる.
p (1..Float::INFINITY).lazy.map { |i| i ** 2 }.take(3).force # >> [1, 4, 9]
ただこの例だとうまく伝わらなさそうな気がした.
書き方はいろいろあると思うけど,もし .map
で .take(3)
なら以下のようなコードでも同じだし.あまり良さが際立ってないかなと.
p (1..3).map { |i| i ** 2 } # >> [1, 4, 9]
素数を10個抽出する例を考えてみる
Enumerable#lazy
の例を考えるなら .map
よりも .select
の方が良い気がする.今回は素数を10個抽出する例を考えてみる.
自分なりに考えながらいろいろなパターンのコードを書いてみた.
1. 雑に範囲を固定する
上限を 1000 に仮で固定する場合ならこのように書ける..lazy
の出番なし.
ただし「10個抽出する」という命題に対して上限を事前に決めるのは不適切だし,実際には (1..1000)
でループが回ってしまってるため計算量も無駄にしている.
require 'prime' p (1..1000).select { |i| i.prime? }.take(10) # >> [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
2. 無限大に回す
上限に対する課題を解決するために Float::INFINITY
にする.マニュアルから引用すると「浮動小数点数における正の無限大」のことを意味する.
ただし,ループの課題は変わらず,さらに上限が無限大になってしまってるため,永遠に計算が続いてしまって反応がなくなる.
require 'prime' p (1..Float::INFINITY).select { |i| i.prime? }.take(10)
3. ストップカウント
そこで,単純な解決策として10個抽出したところで処理を止めるように実装した.微妙だ.イマイチすぎる.
require 'prime' array = [] (1..Float::INFINITY).each { |i| array << i if i.prime? break if array.length == 10 } p array # >> [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
4. 遅延評価
そこで .lazy
の出番!
.lazy
+ .take(10)
で必要な分だけ計算することができる.
と思ったら Enumerator::Lazy
が返ってきてしまっている!
require 'prime' p (1..Float::INFINITY).lazy.select { |i| i.prime? }.take(10) # >> #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: 1..Infinity>:select>:take(10)>
to_a
で配列に変換して使う.できた!
require 'prime' p (1..Float::INFINITY).lazy.select { |i| i.prime? }.take(10).to_a # >> [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
ちなみに force
は Enumerable#to_a
のエイリアスなので同じ結果になる.
Ruby Association から出てるサンプルは force
を使ってる.
require 'prime' p (1..Float::INFINITY).lazy.select { |i| i.prime? }.take(10).force # >> [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
まとめ
Enumerable#lazy 素敵だ!