kakakakakku blog

Weekly Tech Blog: Keep on Learning!

Enumerable#lazy で無限大のリストを扱う

今回 Ruby Gold 2.1 を受験するので,Ruby 1.8 から Ruby 2.1 で追加された機能を勉強しておく必要がある.

詳しくは Ruby Association から出てる以下の PDF に書いてあるのでしっかりと理解する.

Ruby技術者認定試験改訂のお知らせ

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]

ちなみに forceEnumerable#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 素敵だ!

関連エントリー

Ruby Gold 関連