如何在Ruby中惰性拼接Range(范围)?
问题描述
我需要遍历一个超大Range,找到符合特定约束的首个元素。Ruby原本可通过lazy高效实现常规Range遍历,但我的需求是从Range的随机位置开始遍历,若到末尾未找到符合条件的元素,则从Range开头继续遍历至该随机位置。
试过非惰性实现,但splat运算符和map均不支持惰性,无法适配超大Range场景。自己摸索出一种惰性实现写法,但过于复杂,希望得到更简洁的解决方案。
原非惰性实现示例
letter = ('b'..'y').to_a.sample [*letter..'z', *'a'...letter].map { |c| c.capitalize }.join # => "FGHIJKLMNOPQRSTUVWXYZABCDE"
我自己的惰性实现示例
# 对应字母示例的惰性版本 [(letter..'z'), ('a'...letter)].lazy.flat_map { |r| r.each.lazy }.map { |c| c.capitalize }.force.join # => "FGHIJKLMNOPQRSTUVWXYZABCDE" # 接近实际需求的超大数字Range场景 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
优化后的惰性实现方案
1. 简化核心遍历逻辑
利用Range#lazy直接返回惰性枚举器,替代冗余的r.each.lazy写法,同时封装成复用方法:
# 封装环形遍历的惰性枚举器生成方法 def circular_range_enumerator(start_val, end_val) [(start_val..end_val), (0...start_val)].lazy.flat_map(&:lazy) end # 字母示例简化版 letter = ('b'..'y').to_a.sample circular_range_enumerator(letter, 'z').map(&:capitalize).force.join # => 输出与原示例一致 # 超大数字Range场景简化版 start = rand(2**64) iter = circular_range_enumerator(start, 2**64).select { |i| i % 7 == 0 } iter.next # => 符合条件的第一个值 iter.next # => 符合条件的第二个值
2. 直接查找首个符合条件元素
如果仅需首个匹配项,用find替代构建枚举器后取next,更高效(找到匹配项即停止遍历):
start = rand(2**64) first_match = circular_range_enumerator(start, 2**64).find { |i| i % 7 == 0 } # => 直接得到第一个符合条件的元素
内容的提问来源于stack exchange,提问作者Xavier Mol
相关产品推荐
相关产品推荐

