什么时候枚举器::yield #yield method有用?

时间:2022-03-02 23:30:46

This question mentions the Enumerator::Yielder#yield method. I haven't used it before, and I'm wondering under what circumstances it would be useful.

这个问题提到了枚举器::yield #yield方法。我以前从未用过它,我想知道在什么情况下它会有用。

Is it mainly useful when you want to create an infinite list of items, such as the Sieve of Eratosthenes, and when you need to use an external iterator?

当您想要创建一个无限项列表(比如Eratosthenes的筛子),以及需要使用外部迭代器时,它是否主要有用?

4 个解决方案

#1


10  

Although it's pretty handy for constructing infinite lists and lazy iterators, my favourite usage is wrapping an existing Enumerable with additional functionality (any enumerable, without needing to know what it really is, whether it's infinite or not etc).

虽然构造无限列表和惰性迭代器非常方便,但我最喜欢的用法是用附加的功能(任何可枚举的,不需要知道它到底是什么,它是无限的还是无限的等等)包装现有的可枚举。

Trivial example would be implementing each_with_index method (or, more generally, with_index method):

简单的例子是实现each_with_index方法(或者更一般地说,with_index方法):

module Enumerable
  def my_with_index
    Enumerator.new do |yielder|
      i = 0
      self.each do |e|
        yielder.yield e, i
        i += 1
      end
    end
  end

  def my_each_with_index
    self.my_with_index.each do |e, i|
      yield e, i
    end
  end
end

[:foo, :bar, :baz].my_each_with_index do |e,i|
  puts "#{i}: #{e}"
end
#=>0: foo
#=>1: bar
#=>2: baz

Let's now extend to something not already implemented in corelib :), such as cyclically assigning value from a given array to each enumerable element (say, for colouring table rows):

现在让我们扩展到corelib:)中还没有实现的东西,例如循环地将给定数组中的值分配给每个可枚举元素(例如,为表行着色):

module Enumerable
  def with_cycle values
    Enumerator.new do |yielder|
      self.each do |e|
        v = values.shift
        yielder.yield e, v
        values.push v
      end
    end
  end
end

p (1..10).with_cycle([:red, :green, :blue]).to_a # works with any Enumerable, such as Range
#=>[[1, :red], [2, :green], [3, :blue], [4, :red], [5, :green], [6, :blue], [7, :red], [8, :green], [9, :blue], [10, :red]]

The whole point is that these methods return Enumerator, which you then combine with usual Enumerable methods, such as select, map, inject etc.

重点是这些方法返回枚举器,然后将其与通常的可枚举方法(如select、map、inject等)结合使用。

#2


1  

For example you can use it to construct Rack response bodies inline, without creating classes. An Enumerator can also work "outside-in" - you call Enumerator#each which calls next on the enumerator and returns every value in sequence. For example, you can make a Rack response body returning a sequence of numbers:

例如,您可以使用它在不创建类的情况下,内联地构造机架响应体。枚举数也可以“由外到内”工作——您可以调用Enumerator#each,每个都在枚举数上调用next,并按顺序返回每个值。例如,您可以使一个机架响应体返回一个数字序列:

run ->(env) {
  body = Enumerator.new do |y|
   9.times { |i| y.yield(i.to_s) }
  end
  [200, {'Content-Length' => '9'}, body]
}

#3


0  

Since Mladen mentioned getting other answers, I thought I would give an example of something I just did earlier today. I'm writing an application that will receive data from multiple physical devices, analyze the data, and connect related data (that we see from multiple devices). This is a long-running application, and if I never threw away data (say, at least a day old with no updates), then it would grow infinitely large.

因为Mladen提到了获取其他答案,我想我可以举一个我今天早些时候做过的例子。我正在编写一个应用程序,它将接收来自多个物理设备的数据,分析数据,并连接相关数据(我们在多个设备中看到)。这是一个长期运行的应用程序,如果我从不丢弃数据(比方说,至少有一天是没有更新的),那么它就会变得无限大。

I've only been learning Ruby for about 7-8 months, so in the past, I would have done something like this:

我学Ruby才7-8个月,所以在过去,我可能会做这样的事情:

delete_old_stuff if rand(300) == 0

and accomplish this using random numbers. However, this is not purely deterministic. I know that it will run approximately once every 300 evaluations (i.e. seconds), but it won't be exactly once every 300 times.

用随机数来完成。然而,这并不是纯粹的确定性。我知道它大约每300次评估(即秒)运行一次,但它不会每300次。

What I wrote up earlier looks like this:

我之前写的是这样的:

counter = Enumerator.new do |y|
  a = (0..300)
  loop do
    a.each do |b|
      y.yield b
    end
    delete_old_stuff
  end
end

and I can replace delete_old_stuff if rand(300) == 0 with counter.next

如果rand(300) = 0,我可以用counter.next替换delete_old_stuff

Now, I'm sure there is a 1) more efficient or 2) pre-made way of doing this, but being sparked to play with Enumerator::Yielder#yield by your question and the linked question, this is what I came up with.

现在,我确信有一种更有效的方法,或者说是预先准备好的方法,但我被激发去和枚举器玩::屈服者#屈服于你的问题和相关的问题,这就是我想到的。

(and obviously, if you see a way to improve this or anything, let me know! I want to learn as much as I can)

(很明显,如果你发现了改进的方法,请告诉我!)我想尽可能多地学习

#4


0  

It seems to be useful when you have multiple objects you want to enumerate over, but flat_map isn't suitable, and you want to chain the enumeration with another action:

当您有多个要枚举的对象时,这似乎很有用,但是flat_map不合适,并且您希望使用另一个操作来链接枚举:

module Enumerable
  def count_by
    items_grouped_by_criteria = group_by {|object| yield object}
    counts = items_grouped_by_criteria.map{|key, array| [key, array.length]}
    Hash[counts]
  end
end

def calculate_letter_frequencies
  each_letter.count_by {|letter| letter}
end

def each_letter
  filenames = ["doc/Quickstart", "doc/Coding style"]
  # Joining the text of each file into a single string would be memory-intensive
  enumerator = Enumerator.new do |yielder|
    filenames.each do |filename|
      text = File.read(filename)
      text.chars.each {|letter| yielder.yield(letter)}
    end
  end
  enumerator
end

calculate_letter_frequencies

#1


10  

Although it's pretty handy for constructing infinite lists and lazy iterators, my favourite usage is wrapping an existing Enumerable with additional functionality (any enumerable, without needing to know what it really is, whether it's infinite or not etc).

虽然构造无限列表和惰性迭代器非常方便,但我最喜欢的用法是用附加的功能(任何可枚举的,不需要知道它到底是什么,它是无限的还是无限的等等)包装现有的可枚举。

Trivial example would be implementing each_with_index method (or, more generally, with_index method):

简单的例子是实现each_with_index方法(或者更一般地说,with_index方法):

module Enumerable
  def my_with_index
    Enumerator.new do |yielder|
      i = 0
      self.each do |e|
        yielder.yield e, i
        i += 1
      end
    end
  end

  def my_each_with_index
    self.my_with_index.each do |e, i|
      yield e, i
    end
  end
end

[:foo, :bar, :baz].my_each_with_index do |e,i|
  puts "#{i}: #{e}"
end
#=>0: foo
#=>1: bar
#=>2: baz

Let's now extend to something not already implemented in corelib :), such as cyclically assigning value from a given array to each enumerable element (say, for colouring table rows):

现在让我们扩展到corelib:)中还没有实现的东西,例如循环地将给定数组中的值分配给每个可枚举元素(例如,为表行着色):

module Enumerable
  def with_cycle values
    Enumerator.new do |yielder|
      self.each do |e|
        v = values.shift
        yielder.yield e, v
        values.push v
      end
    end
  end
end

p (1..10).with_cycle([:red, :green, :blue]).to_a # works with any Enumerable, such as Range
#=>[[1, :red], [2, :green], [3, :blue], [4, :red], [5, :green], [6, :blue], [7, :red], [8, :green], [9, :blue], [10, :red]]

The whole point is that these methods return Enumerator, which you then combine with usual Enumerable methods, such as select, map, inject etc.

重点是这些方法返回枚举器,然后将其与通常的可枚举方法(如select、map、inject等)结合使用。

#2


1  

For example you can use it to construct Rack response bodies inline, without creating classes. An Enumerator can also work "outside-in" - you call Enumerator#each which calls next on the enumerator and returns every value in sequence. For example, you can make a Rack response body returning a sequence of numbers:

例如,您可以使用它在不创建类的情况下,内联地构造机架响应体。枚举数也可以“由外到内”工作——您可以调用Enumerator#each,每个都在枚举数上调用next,并按顺序返回每个值。例如,您可以使一个机架响应体返回一个数字序列:

run ->(env) {
  body = Enumerator.new do |y|
   9.times { |i| y.yield(i.to_s) }
  end
  [200, {'Content-Length' => '9'}, body]
}

#3


0  

Since Mladen mentioned getting other answers, I thought I would give an example of something I just did earlier today. I'm writing an application that will receive data from multiple physical devices, analyze the data, and connect related data (that we see from multiple devices). This is a long-running application, and if I never threw away data (say, at least a day old with no updates), then it would grow infinitely large.

因为Mladen提到了获取其他答案,我想我可以举一个我今天早些时候做过的例子。我正在编写一个应用程序,它将接收来自多个物理设备的数据,分析数据,并连接相关数据(我们在多个设备中看到)。这是一个长期运行的应用程序,如果我从不丢弃数据(比方说,至少有一天是没有更新的),那么它就会变得无限大。

I've only been learning Ruby for about 7-8 months, so in the past, I would have done something like this:

我学Ruby才7-8个月,所以在过去,我可能会做这样的事情:

delete_old_stuff if rand(300) == 0

and accomplish this using random numbers. However, this is not purely deterministic. I know that it will run approximately once every 300 evaluations (i.e. seconds), but it won't be exactly once every 300 times.

用随机数来完成。然而,这并不是纯粹的确定性。我知道它大约每300次评估(即秒)运行一次,但它不会每300次。

What I wrote up earlier looks like this:

我之前写的是这样的:

counter = Enumerator.new do |y|
  a = (0..300)
  loop do
    a.each do |b|
      y.yield b
    end
    delete_old_stuff
  end
end

and I can replace delete_old_stuff if rand(300) == 0 with counter.next

如果rand(300) = 0,我可以用counter.next替换delete_old_stuff

Now, I'm sure there is a 1) more efficient or 2) pre-made way of doing this, but being sparked to play with Enumerator::Yielder#yield by your question and the linked question, this is what I came up with.

现在,我确信有一种更有效的方法,或者说是预先准备好的方法,但我被激发去和枚举器玩::屈服者#屈服于你的问题和相关的问题,这就是我想到的。

(and obviously, if you see a way to improve this or anything, let me know! I want to learn as much as I can)

(很明显,如果你发现了改进的方法,请告诉我!)我想尽可能多地学习

#4


0  

It seems to be useful when you have multiple objects you want to enumerate over, but flat_map isn't suitable, and you want to chain the enumeration with another action:

当您有多个要枚举的对象时,这似乎很有用,但是flat_map不合适,并且您希望使用另一个操作来链接枚举:

module Enumerable
  def count_by
    items_grouped_by_criteria = group_by {|object| yield object}
    counts = items_grouped_by_criteria.map{|key, array| [key, array.length]}
    Hash[counts]
  end
end

def calculate_letter_frequencies
  each_letter.count_by {|letter| letter}
end

def each_letter
  filenames = ["doc/Quickstart", "doc/Coding style"]
  # Joining the text of each file into a single string would be memory-intensive
  enumerator = Enumerator.new do |yielder|
    filenames.each do |filename|
      text = File.read(filename)
      text.chars.each {|letter| yielder.yield(letter)}
    end
  end
  enumerator
end

calculate_letter_frequencies