如何惰性连接 Ruby 范围?

Xav*_*Mol 4 ruby iteration range lazy-evaluation

我有一个非常大的范围来迭代并找到满足特定约束的第一个元素。这已经可以在 Ruby 中高效地完成。

# Runs until memory is exhausted _without_ lazy!
(1..).lazy.select { |i| i > 5 }.first
# => 6
Run Code Online (Sandbox Code Playgroud)

然而,在我的用例中,我想以范围的随机间隔开始迭代,如果到达范围末尾时没有元素通过检查,则从范围的开头继续(直到达到随机间隔)再次,如果需要的话)。通过将两个不同的“范围”合并为一个红宝石作为参考,我来到......

letter = ('b'..'y').to_a.sample
[*letter..'z', *'a'...letter].map { |c| c.capitalize }.join
# => "FGHIJKLMNOPQRSTUVWXYZABCDE"
Run Code Online (Sandbox Code Playgroud)

当然,我没有字母表作为迭代范围,这只是小规模的示例,对于我的用例来说是失败的。

  • (splat)运算*符并不懒惰
  • map不懒

通过更多的谷歌搜索和实验,我得出了以下结构:

# 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
Run Code Online (Sandbox Code Playgroud)

这对我来说确实过于复杂,也许有人有更好的主意?

谢谢您抽出时间,
泽维尔。

Ste*_*fan 6

如何惰性连接 Ruby 范围?

您可以通过连接枚举器+。范围不是枚举器,但您可以通过 检索Range#each,例如:

enum = (-3..0).each + (1..)
Run Code Online (Sandbox Code Playgroud)

组合枚举器将迭代每个串联的枚举器:

enum.take(10)
#=> [-3, -2, -1, 0, 1, 2, 3, 4, 5, 6]
Run Code Online (Sandbox Code Playgroud)

我有一个非常大的范围来迭代并找到满足特定约束的第一个元素

Ruby 有一个专门的方法Enumerable#find可以做到这一点。它迭代集合并返回块返回真实结果的第一个元素(无需进一步迭代),例如

enum.find { |i| i > 5 }
#=> 6
Run Code Online (Sandbox Code Playgroud)

  • 从技术上讲,“Enumerator#+”的参数只需是可枚举的,因此第二个“each”不是必需的。 (2认同)

Ale*_*lex 5

您可以使用Enumerable#chain

>> start = rand(2**64)
=> 9019096319891825624

>> (start..2**64).chain(0...start).lazy.select { |i| i % 7 == 0 }.first(3)
=> [9019096319891825629, 9019096319891825636, 9019096319891825643]

>> Enumerator::Chain.new(start..2**64, 0...start).lazy.select { |i| i % 7 == 0 }.first(3)
=> [9019096319891825629, 9019096319891825636, 9019096319891825643]
Run Code Online (Sandbox Code Playgroud)