我正在尝试为Ruby 2的Enumerator :: Lazy类实现take_until
方法。它应该与take_while
类似,但是当yielding块返回true时停止迭代。结果应该包括生成的块匹配的项目。
我的问题是我如何表示已达到迭代结束?使用常规枚举器时,可以在每个方法中引发StopIteration错误,以指示迭代器的结束。但这似乎不适用于懒惰的枚举:
class Enumerator::Lazy
def take_until
Lazy.new(self) do |yielder, *values|
yielder << values
raise StopIteration if yield *values
end
end
end
(1..Float::INFINITY).lazy.take_until{ |i| i == 5 }.force
我也试图突破阻止无效。 The documentation for Enumerator::Lazy似乎也无济于事。
为什么使用take_while
不是有效选项。
take_while的主要问题是,它本质上会尝试再评估一个项目,而不是你需要的项目。在我的应用程序中,枚举器不会产生数字,但会通过网络获取消息。试图评估一条不存在的消息(还有?)是一种非常不受欢迎的阻止动作。以下设计的例子说明了这一点:
enum = Enumerator.new do |y|
5.times do |i|
y << i
end
sleep
end
enum.lazy.take_while{ |i| i < 5 }.force
要从此枚举器接收前五项,您需要评估第六项结果。这并不像它可能那么懒惰。在我的用例中,这是不可取的,因为该过程会阻塞。
为Enumerator :: Lazy 提供take
的纯Ruby实现
标准库包含一个take
方法,它可以执行类似于我想要的操作。它没有使用块作为条件而是使用数字,但是一旦达到该数量,它就会突破迭代,而不是再评估一个项目。继上面的例子之后:
enum.lazy.take(5).force
这不会到达第6项,因此不会阻止。问题是标准库中的版本是用C实现的,我似乎无法弄清楚如何在纯Ruby中实现它。该方法的ruby实现将是一个可接受的答案。
提前致谢!
答案 0 :(得分:2)
这是一个老问题,但无论如何:正如你所说,你真正需要的是Lazy#take_until
,当然Lazy#take_while
需要获得下一个项目以决定是否打破。我一直无法使用Lazy#take_until
来实现Lazy#new { ... }
,显然没有破解机制。这是一种可行的解决方法:
class Enumerator::Lazy
def take_until
Enumerator.new do |yielder|
each do |value|
yielder << value
break if yield(value)
end
end.lazy
end
end
答案 1 :(得分:0)
根据我的评论,修改take_while
的方法是更好的选择(或至少是有效选项):
(1..Float::INFINITY).lazy.take_while { |i| i < 6 }.force
=> [1, 2, 3, 4, 5]
对于不太容易重写的更复杂的条件,添加一个变量:
found = false
(1..Float::INFINITY).lazy.take_while do |i|
if i == 5
found = true
else
!found
end
end.force
=> [1, 2, 3, 4, 5]
您也可以根据最后一个块定义take_while
:
class Enumerator::Lazy
def take_until
take_while do |*args|
if !@found
@found = yield(*args)
true
else
false
end
end
end
end
请注意,它也不会不必要地调用块:
p (1..20).lazy.take_until{|i| p i; i == 5}.force
p (1..20).lazy.take_until{|i| p i; i == 3}.force
p (1..20).lazy.take_until{|i| p i; i == 8}.force
答案 2 :(得分:0)
我刚刚发现了这个实现。它不是最优的,因为它会通过内部缓存结果来隐式强制迭代过早。
class Enumerator::Lazy
def take_until
if block_given?
ary = []
while n = self.next
ary << n
if (yield n) == true
break
end
end
return ary.lazy
else
return self
end
end
end
使用我的问题中的例子:
enum = Enumerator.new do |y|
5.times do |i|
y << i
end
sleep
end
p enum.lazy.take_until{ |i| i == 4 }.force
现在将返回[0, 1, 2, 3, 4]
我保持这个问题的开放时间有点长,看看是否有人想出一个真正懒惰的实现,但我怀疑我们会找到一个。