Ruby 相当于 C# 的“yield”关键字,或者,创建序列而不预分配内存

发布于 2024-08-22 06:52:00 字数 1697 浏览 5 评论 0原文

在 C# 中,您可以执行以下操作:

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

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

有没有办法在 Ruby 中做同样的事情?我试图处理的具体示例是将矩形数组展平为要枚举的值序列。返回值不一定是ArraySet,而是某种只能按顺序迭代/枚举的序列,而不能按索引迭代/枚举。因此,整个序列不需要同时分配在内存中。在 .NET 中,这是 IEnumerableIEnumerable

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

编辑

也许我原来的问题不够清楚——我认为 yield 在 C# 和 Ruby 中具有非常不同的含义,这是造成这里混乱的原因。

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

下面是一个如何使用 get_items 的简单示例:

things = obj.get_items.select { |i| !i.thing.nil? }.map { |i| i.thing }

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

我确实有可能使用 Enumerable为了实现这一点。一个简单的解决方案对我们来说是一个Array(其中包括模块Enumerable),但我不想在内存中创建一个包含 N 个项目的中间集合,因为可以只延迟地提供它们并完全避免任何内存峰值。

如果这仍然没有意义,那么请考虑上面的代码示例。 get_items 返回一个枚举,在该枚举上调用 select。传递给 select 的是一个实例,它知道如何在需要时提供序列中的下一项。重要的是,整个项目集合尚未计算出来。仅当 select 需要某个项目时,它才会请求该项目,并且 get_items 中的潜在代码将启动并提供该项目。这种惰性沿着链进行,这样 select 仅在 map 请求时才从序列中绘制下一个项目。因此,一次可以对一个数据项执行一长串操作。事实上,以这种方式构建的代码甚至可以处理无限值序列,而不会出现任何类型的内存错误。

所以,这种懒惰很容易在C#中编码,而我不知道如何在Ruby中做到这一点。

我希望这样能更清楚(以后我会尽量避免在凌晨 3 点写问题。)

In C#, you could do something like this:

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

This returns an enumerable sequence of 10 million integers without ever allocating a collection in memory of that length.

Is there a way of doing an equivalent thing in Ruby? The specific example I am trying to deal with is the flattening of a rectangular array into a sequence of values to be enumerated. The return value does not have to be an Array or Set, but rather some kind of sequence that can only be iterated/enumerated in order, not by index. Consequently, the entire sequence need not be allocated in memory concurrently. In .NET, this is IEnumerable and IEnumerable<T>.

Any clarification on the terminology used here in the Ruby world would be helpful, as I am more familiar with .NET terminology.

EDIT

Perhaps my original question wasn't really clear enough -- I think the fact that yield has very different meanings in C# and Ruby is the cause of confusion here.

I don't want a solution that requires my method to use a block. I want a solution that has an actual return value. A return value allows convenient processing of the sequence (filtering, projection, concatenation, zipping, etc).

Here's a simple example of how I might use get_items:

things = obj.get_items.select { |i| !i.thing.nil? }.map { |i| i.thing }

In C#, any method returning IEnumerable that uses a yield return causes the compiler to generate a finite state machine behind the scenes that caters for this behaviour. I suspect something similar could be achieved using Ruby's continuations, but I haven't seen an example and am not quite clear myself on how this would be done.

It does indeed seem possible that I might use Enumerable to achieve this. A simple solution would be to us an Array (which includes module Enumerable), but I do not want to create an intermediate collection with N items in memory when it's possible to just provide them lazily and avoid any memory spike at all.

If this still doesn't make sense, then consider the above code example. get_items returns an enumeration, upon which select is called. What is passed to select is an instance that knows how to provide the next item in the sequence whenever it is needed. Importantly, the whole collection of items hasn't been calculated yet. Only when select needs an item will it ask for it, and the latent code in get_items will kick into action and provide it. This laziness carries along the chain, such that select only draws the next item from the sequence when map asks for it. As such, a long chain of operations can be performed on one data item at a time. In fact, code structured in this way can even process an infinite sequence of values without any kinds of memory errors.

So, this kind of laziness is easily coded in C#, and I don't know how to do it in Ruby.

I hope that's clearer (I'll try to avoid writing questions at 3AM in future.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

长不大的小祸害 2024-08-29 06:52:00

自 Ruby 1.9 (并向后移植到 1.8.7)。请参阅生成器: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() }

It's supported by Enumerator since Ruby 1.9 (and back-ported to 1.8.7). See Generator: Ruby.

Cliche example:

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() }
山人契 2024-08-29 06:52:00

您的具体示例相当于 10000000.times,但让我们暂时假设 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)

编辑:为了澄清我的回答是:

在上面的示例中,my_times 可以(并且确实)在没有块的情况下使用,它将返回一个 Enumerable 对象,这将让您迭代从 0 到 n 的数字。所以它与 C# 中的示例完全相同。

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

在 1.8.7+ 中,它已成为定义产生项目的方法,以便在没有块的情况下调用时,它们将返回一个枚举器,这将让用户惰性地迭代这些项目。这是通过将行 return enum_for(:name_of_this_method) except block_given? 添加到方法的开头来完成的,就像我在示例中所做的那样。

Your specific example is equivalent to 10000000.times, but let's assume for a moment that the times method didn't exist and you wanted to implement it yourself, it'd look like this:

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)

Edit: To clarify my answer a bit:

In the above example my_times can be (and is) used without a block and it will return an Enumerable object, which will let you iterate over the numbers from 0 to n. So it is exactly equivalent to your example in C#.

This works using the enum_for method. The enum_for method takes as its argument the name of a method, which will yield some items. It then returns an instance of class Enumerator (which includes the module Enumerable), which when iterated over will execute the given method and give you the items which were yielded by the method. Note that if you only iterate over the first x items of the enumerable, the method will only execute until x items have been yielded (i.e. only as much as necessary of the method will be executed) and if you iterate over the enumerable twice, the method will be executed twice.

In 1.8.7+ it has become to define methods, which yield items, so that when called without a block, they will return an Enumerator which will let the user iterate over those items lazily. This is done by adding the line return enum_for(:name_of_this_method) unless block_given? to the beginning of the method like I did in my example.

翻身的咸鱼 2024-08-29 06:52:00

在没有太多 ruby​​ 经验的情况下,C# 在 yield return 中所做的事情通常被称为惰性求值惰性执行:仅在需要时提供答案。它与分配内存无关,而是将计算推迟到实际需要时为止,以类似于简单线性执行的方式表示(而不是具有状态保存的底层迭代器)。

快速谷歌一下,发现了一个测试版的 ruby 库。看看是不是你想要的。

Without having much ruby experience, what C# does in yield return is usually known as lazy evaluation or lazy execution: providing answers only as they are needed. It's not about allocating memory, it's about deferring computation until actually needed, expressed in a way similar to simple linear execution (rather than the underlying iterator-with-state-saving).

A quick google turned up a ruby library in beta. See if it's what you want.

晨曦÷微暖 2024-08-29 06:52:00

C# 从 Ruby 中删除了“yield”关键字 - 请参阅此处实现迭代器 了解更多。

至于您的实际问题,您可能有一个数组数组,并且您想在列表的整个长度上创建单向迭代?也许值得将 array.flatten 作为起点 - 如果性能还不错,那么您可能不需要走得太远。

C# ripped the 'yield' keyword right out of Ruby- see Implementing Iterators here for more.

As for your actual problem, you have presumably an array of arrays and you want to create a one-way iteration over the complete length of the list? Perhaps worth looking at array.flatten as a starting point - if the performance is alright then you probably don't need to go too much further.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文