Ruby 範囲を遅延連結するにはどうすればよいですか?
概要
非常に広い範囲を反復処理して、特定の制約を満たす最初の要素を見つける必要があります。それはすでに Ruby で効率的に行うことができます。
# Runs until memory is exhausted _without_ lazy!
(1..).lazy.select { |i| i > 5 }.first
# => 6
ただし、私のユースケースでは、範囲のランダムな間隔で反復を開始し、範囲の終わりに到達したときにチェックに合格した要素がない場合は、範囲の先頭から(ランダムな間隔に到達するまで)継続したいと考えています。必要に応じてもう一度)。 Ruby で 2 つの異なる「範囲」を 1 つに結合することを参考にして、私は次のようになりました…
letter = ('b'..'y').to_a.sample
[*letter..'z', *'a'...letter].map { |c| c.capitalize }.join
# => "FGHIJKLMNOPQRSTUVWXYZABCDE"
もちろん、反復する範囲としてアルファベットはありません。これは単なる小規模な例であり、私のユースケースでは失敗します。
さらにグーグルで調べて実験した結果、次の構成にたどり着きました。
# lazy version of previous alphabet example
[(letter..'z'), ('a'...letter)].lazy.flat_map { |r| r.each.lazy }.map { |c| c.capitalize }.force.join
=> "FGHIJKLMNOPQRSTUVWXYZABCDE"
# Comparable to what I want
start = rand(2**64)
# => 15282219649142738977
[(start..2**64), (0...start)].lazy.flat_map { |r| r.each.lazy }.select { |i| i % 7 == 0 }.first(5)
# => [15282219649142738978, 15282219649142738985, 15282219649142738992, 15282219649142738999, 15282219649142739006]
iter = [(start..2**64), (0...start)].lazy.flat_map { |r| r.each.lazy }.select { |i| i % 7 == 0 }
# => #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: [15282219649142738977..18446744073709551616, 0...15282219649142738977]>:flat_map>:select>
iter.next
# => 15282219649142738978
iter.next
# => 15282219649142738985
それは私には複雑すぎるように思えますが、誰かがもっと良いアイデアを持っているでしょうか?
お時間をいただきありがとうございます。 ザビエル。
解決策
- を使用して列挙子を連結できます。範囲は列挙子ではありませんが、Range#each を使用して列挙子を取得できます。次に例を示します。
enum = (-3..0).each + (1..)
結合された列挙子は、連結された各列挙子を反復します。
enum.take(10)
#=> [-3, -2, -1, 0, 1, 2, 3, 4, 5, 6]
Ruby には、まさにこれを行う専用のメソッド Enumerable#find があります。コレクションを反復し、ブロックが (それ以上反復せずに) 真実の結果を返す最初の要素を返します。
enum.find { |i| i > 5 }
#=> 6