2013-12-23 25 views
2

我想为Ruby 2的Enumerator :: Lazy类实现一个take_until方法。它的工作原理与take_while类似,但当代码块返回true时停止迭代。结果应该包括产生的块匹配的项目。如何在Enumerator :: Lazy方法中停止迭代?

我的问题是我如何表示迭代的结束已达到?使用常规枚举器时,可以在每个方法中引发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 

要从此枚举器接收前五项,您将需要评估第六个结果。这并不像它那样懒惰。在我的使用情况下,这是不可取的,因为这个过程会被阻止。

提供枚举::懒惰

标准库包括take方法,做类似于我想要的东西纯Ruby实现的take。它不会使用块作为条件,而是使用数字,但是一旦达到该数字,它就会跳出迭代,而不是评估另外一个项目。继上面的例子:

enum.lazy.take(5).force 

这没有得到第6项,所以不阻止。问题是标准库中的版本是用C语言实现的,我似乎无法弄清楚这是如何在纯Ruby中实现的。该方法的红宝石实现将是一个可以接受的答案。

提前致谢!

+0

我讨厌问,但不是你只是使用take_while并根据需要修改条件? –

+0

这是一个有效的问题,但我认为答案是:不,我不能。我的使用案例涉及到一系列的响应,我特别想要在遇到某些情况时对序列进行分隔。 Take_while不会包含匹配的项目本身,而是将顺序返回到第一个“未命中”。希望这是有道理的。 –

+1

所以'take_until'会否定一个'take_while',它会做一个额外的'yield next'? – steenslag

回答

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 [email protected] 
     @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 
+0

'take_while'的主要问题在于,它的性质会尝试评估比您需要的更多项目。在我的应用程序中,枚举器不会产生数字,而是通过网络获取消息。试图评估一个不存在的信息(但是?)是一种非常不受欢迎的阻止行为。这就是为什么在我的情况下,take_while不是一个有效的选项。 –

+0

那么,因此建议相应地重写条件。 : - | (但是请稍后查看我编辑的答案。) –

+0

There ...使用'take_while'实现'take_until'实现,根据我的初始答案。或者我误解了你的编辑? –

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]

我保持这个问题,开的时间长一点,看看是否有人想出了一个真正的懒惰实现,但我怀疑我们会找到一个。

1

这是一个古老的问题,但无论如何:正如你所说,你真正需要的是一个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 
+0

@ Marc-AndréLafortune:我想知道你对此的看法,必须有办法打破懒惰的普查员。 – tokland