【问题标题】:create a ruby enumerable from a first element, and a function to get the next element from the previous one从第一个元素创建一个可枚举的 ruby​​,以及一个从前一个元素获取下一个元素的函数
【发布时间】:2016-02-20 23:20:59
【问题描述】:

在 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

但它是惯用的吗?已经有这样的东西了吗?

【问题讨论】:

  • 不知道你为什么用惰性评估标记这个问题。您可能想查看Enumerator::Lazy
  • 用英文书写时请使用英文标点。
  • @CarySwoveland 当然,无论您使用哪种语言,您都必须遵守标点符号规则。但首先,本网站不欢迎除英语以外的其他语言(正如您还评论了一些其他用俄语写的问题)。
  • @CarySwoveland 我在这里使用“when”的意图接近“since”。

标签: ruby functional-programming lazy-evaluation


【解决方案1】:

您要询问的内容称为(在 中称为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

【讨论】:

  • 我喜欢将其作为 Enumerator 的单例方法的想法。但是,实施让我感到困惑。据我所见,这总是会生成一个 Ruby 模块的枚举器,其中 Enumerator 作为初始值。那是我们想要的吗?也许您想为初始值添加一个参数并使用that = initial_value 而不是that = self
  • 在重构 OP 的实现时令人尴尬的brainfart。
【解决方案2】:

我不知道有一种公认的惯用方式。 由于您的代码似乎无法生成您喜欢的内容,因此我将举例说明如何实现它。

您可以使用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

【讨论】:

    【解决方案3】:

    在 Ruby 中,您将使用 Enumerable#inject:

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

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

    【讨论】:

    • 这正是 OP 所要求的对偶。 Enumerable#injectrubyfold,即catamorphism,OP 询问的是unfold(在scala 中称为Stream.iterate),即anamorphism
    • 这不能解决我的问题。
    • @GeorgeSimms 我已经为误会您而道歉。无需进一步抨击我并继续投票。我考虑删除这个答案,但决定把它留在这里,这样其他人就不会犯和我一样的错误了。不确定我是否会再次花时间回答您的问题。
    猜你喜欢
    • 1970-01-01
    • 2018-07-02
    • 1970-01-01
    • 1970-01-01
    • 2021-12-08
    • 1970-01-01
    • 2016-03-29
    • 1970-01-01
    • 2021-03-14
    相关资源
    最近更新 更多