Ruby相当于C#的'yield'关键字,或者,在没有预先分配内存的情况下创建序列

Dre*_*kes 7 ruby yield sequences

在C#中,你可以这样做:

public IEnumerable<T> GetItems<T>()
{
    for (int i=0; i<10000000; i++) {
        yield return i;
    }
}
Run Code Online (Sandbox Code Playgroud)

这将返回一个包含1000万个整数的可枚举序列,而无需在该长度的内存中分配集合.

有没有办法在Ruby中做同等的事情?我试图处理的具体例子是将矩形数组展平为要枚举的值序列.返回值不必是Array或者Set,而是某种只能按顺序迭代/枚举的序列,而不是索引.因此,不需要在存储器中同时分配整个序列.在.NET中,这是IEnumerableIEnumerable<T>.

关于Ruby世界中使用的术语的任何澄清都会有所帮助,因为我对.NET术语更熟悉.

编辑

也许我原来的问题还不够清楚 - 我认为yield在C#和Ruby 中具有非常不同含义的事实是造成混淆的原因.

我不想要一个需要我的方法来使用块的解决方案.我想要一个具有实际返回值的解决方案.返回值允许方便地处理序列(过滤,投影,连接,压缩等).

这是一个我可能会如何使用的简单示例get_items:

things = obj.get_items.select { |i| !i.thing.nil? }.map { |i| i.thing }
Run Code Online (Sandbox Code Playgroud)

在C#中,任何返回IEnumerable使用a的方法yield return都会导致编译器在幕后生成满足此行为的有限状态机.我怀疑使用Ruby的延续可以实现类似的东西,但我还没有看到一个例子,我自己也不清楚如何做到这一点.

我确实有可能用它Enumerable来实现这一目标.一个简单的解决方案是我们Array(包括模块Enumerable),但我不想在内存中创建一个包含N个项目的中间集合,只要它们可以懒得提供它们并且完全避免任何内存峰值.

如果这仍然没有意义,那么请考虑上面的代码示例. get_items返回一个枚举,在其select上调用.传递给的select是一个知道如何在需要时提供序列中的下一个项目的实例.重要的是,尚未计算整个项目集.只有当select需要一个项目时它才会要求它,并且潜在的代码get_items将开始行动并提供它.这种懒惰带有链条,这样select只有在map询问它时才从序列中抽取下一个项目.这样,可以一次对一个数据项执行长链操作.实际上,以这种方式构造的代码甚至可以处理无限的值序列而不会出现任何类型的内存错误.

所以,这种懒惰很容易用C#编码,我不知道如何用Ruby做.

我希望这更清楚(我会尽量避免在将来凌晨3点写问题.)

Mat*_*hen 14

Enumerator从Ruby 1.9开始支持它(后端移植到1.8.7).参见Generator:Ruby.

陈词滥调的例子:

fib = Enumerator.new do |y|
  y.yield i = 0
  y.yield j = 1
  while true
    k = i + j
    y.yield k
    i = j
    j = k
  end
end

100.times { puts fib.next() }
Run Code Online (Sandbox Code Playgroud)


sep*_*p2k 5

您的具体示例等同于10000000.times,但我们暂时假设时间方法不存在并且您想自己实现它,它看起来像这样:

class Integer
  def my_times
    return enum_for(:my_times) unless block_given?
    i=0
    while i<self
      yield i
      i += 1
    end
  end
end

10000.my_times # Returns an Enumerable which will let
               # you iterate of the numbers from 0 to 10000 (exclusive)
Run Code Online (Sandbox Code Playgroud)

编辑:澄清我的答案:

在上面的例子中,my_times可以(并且是)在没有块的情况下使用,它将返回一个Enumerable对象,它将允许您迭代从0到n的数字.所以它完全等同于你在C#中的例子.

这适用于使用enum_for方法.enum_for方法将方法的名称作为其参数,这将产生一些项目.然后它返回一个Enumerator类的实例(包括模块Enumerable),迭代后将执行给定的方法并为您提供该方法产生的项目.请注意,如果您只迭代枚举的前x个项,则该方法将仅执行直到x项已被生成(即,只执行该方法所需的数量),并且如果迭代可枚举两次,则方法将执行两次.

在1.8.7+中,已经成为定义方法,它产生项目,因此当没有块调用时,它们将返回一个Enumerator,它将让用户懒洋洋地迭代这些项目.这是通过将行添加return enum_for(:name_of_this_method) unless block_given?到方法的开头来完成的,就像我在我的示例中所做的那样.