从第一个元素创建一个可枚举的ruby,以及一个从前一个元素中获取下一个元素的函数

时间:2016-02-20 23:20:59

标签: ruby functional-programming lazy-evaluation

在Scala中,您可以从第一个元素定义一个流,并从前一个元素中获取下一个元素的函数;

Stream.iterate(1)(x => 2 * x + 1)

在Ruby中存在这样的东西吗?

当然我们可以手动滚动它 -

 module Kernel
   def stream
     Enumerator.new do |y|
       that = self
       while true
         y << that
         that = yield that
       end
     end
   end
 end

但它是惯用的吗?有没有这样的东西?

3 个答案:

答案 0 :(得分:3)

您所询问的内容称为(在Stream.iterate中称为),或更一般地称为。它是(在Enumerable#inject中称为)又称的确切类别 - 理论对偶。

  

有没有这样的东西?

不幸的是,核心或标准库中没有执行此功能的方法。

  

但它是惯用的吗?

我可能会将它设为Enumerator的单例方法并使用Kernel#loop代替while true,但这是关于它的。否则,是的,您的代码非常惯用。

虽然我们正在使用它,但我们称之为unfold

def Enumerator.unfold(start)
  new do |y|
    loop do
      y << start
      start = yield start
    end
  end
end

答案 1 :(得分:1)

我不知道这样做的惯用方法。 由于您的代码似乎没有产生您喜欢的内容,因此我将向您举例说明如何实现它。

您可以使用Fiber创建外部迭代展开,如下所示:

def stream(init, &block)
  Fiber.new do 
    memo = init
    loop do
      Fiber.yield memo
      memo = block.call memo
    end
  end
end

a = stream(1){ |x| 2 * x + 1 }

a.resume  # => 1
a.resume  # => 3
a.resume  # => 7
a.resume  # => 15

如果您希望Enumerator::Lazy拥有所有超级大国,请考虑以下事项:

def stream2(init, &block)
  Enumerator.new do |yielder| 
    memo = init
    loop do 
      yielder << memo
      memo = block.call memo 
    end 
  end.lazy
end

a2 = stream2(1){ |x| 2 * x + 1 }

a2.next  # => 1
a2.next  # => 3
a2.next  # => 7

a2.take(10).force  # => [1, 3, 7, 15, 31, 63, 127, 255, 511, 1023]

其行为与此相同:

def stream3(init, &block)
  memo = init
  (1..Float::INFINITY).lazy.map{ |_| memo = block.call(memo) }
end

最后一个可能是最惯用的,并且最好隐藏枚举的内部。但是,我个人不喜欢stream3,因为它只会产生数字以丢弃它们。

HTH

答案 2 :(得分:-3)

在Ruby中,您将使用Enumerable#inject

[7, 8, 9].inject(1){ |carry, item| 2 * carry + item }

块的返回值将用作进位变量的下一个值。