在 Ruby 中使用 to_enum 创建可枚举对象有什么好处?

发布于 2024-09-02 13:12:17 字数 305 浏览 1 评论 0原文

为什么要使用 to_enum 方法而不是直接使用对象来创建对 Ruby 中对象的代理引用?我想不出任何实际用途,试图理解这个概念和概念。有人可能会使用它,但我见过的所有例子似乎都很微不足道。

例如,为什么使用:

"hello".enum_for(:each_char).map {|c| c.succ }

而不是

"hello".each_char.map {|c| c.succ }

我知道这是一个非常简单的例子,有人有任何现实世界的例子吗?

Why would you create a proxy reference to an object in Ruby, by using the to_enum method rather than just using the object directly? I cannot think of any practical use for this, trying to understand this concept & where someone might use it, but all the examples I have seen seem very trivial.

For example, why use:

"hello".enum_for(:each_char).map {|c| c.succ }

instead of

"hello".each_char.map {|c| c.succ }

I know this is a very simple example, does anyone have any real-world examples?

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

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

发布评论

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

评论(5

如果没有提供块,大多数接受块的内置方法都会返回一个枚举器(例如示例中的 String#each_char )。对于这些,没有理由使用 to_enum;两者将产生相同的效果。

不过,有一些方法不返回枚举器。在这种情况下,您可能需要使用 to_enum

# How many elements are equal to their position in the array?
[4, 1, 2, 0].to_enum(:count).each_with_index{|elem, index| elem == index} #=> 2

另一个例子,Array#product#uniq#uniq! 不用于接受块。在 1.9.2 中,这一点已更改,但为了保持兼容性,没有块的表单无法返回 Enumerator。人们仍然可以“手动”使用 to_enum 来获取枚举器:

require 'backports/1.9.2/array/product' # or use Ruby 1.9.2+
# to avoid generating a huge intermediary array:
e = many_moves.to_enum(:product, many_responses)
e.any? do |move, response|
  # some criteria
end 

to_enum 的主要用途是当您实现自己的迭代方法时。您通常会将以下内容作为第一行:

def my_each
  return to_enum :my_each unless block_given?
  # ...
end

Most built-in methods that accept a block will return an enumerator in case no block is provided (like String#each_char in your example). For these, there is no reason to use to_enum; both will have the same effect.

A few methods do not return an Enumerator, though. In those case you might need to use to_enum.

# How many elements are equal to their position in the array?
[4, 1, 2, 0].to_enum(:count).each_with_index{|elem, index| elem == index} #=> 2

As another example, Array#product, #uniq and #uniq! didn't use to accept a block. In 1.9.2, this was changed, but to maintain compatibility, the forms without a block can't return an Enumerator. One can still "manually" use to_enum to get an enumerator:

require 'backports/1.9.2/array/product' # or use Ruby 1.9.2+
# to avoid generating a huge intermediary array:
e = many_moves.to_enum(:product, many_responses)
e.any? do |move, response|
  # some criteria
end 

The main use of to_enum is when you are implementing your own iterative method. You typically will have as a first line:

def my_each
  return to_enum :my_each unless block_given?
  # ...
end
你的心境我的脸 2024-09-09 13:12:17

我认为这与内部和外部迭代器有关。当您返回这样的枚举器时:

p = "hello".enum_for(:each_char)

p 是外部枚举器。外部迭代器的优点之一是:

外部迭代器比内部迭代器更灵活。例如,使用外部迭代器比较两个集合的相等性很容易,但使用内部迭代器实际上是不可能的......但另一方面,内部迭代器更容易使用,因为它们为您定义了迭代逻辑。 [摘自《Ruby 编程语言》一书,第 1 章。 5.3]

所以,使用外部迭代器你可以这样做,例如:

p = "hello".enum_for(:each_char)
loop do
    puts p.next
end

I think it has something to do with internal and external Iterators. When you return an enumerator like this:

p = "hello".enum_for(:each_char)

p is an external enumerator. One advantage of external iterators is that:

External iterators are more flexible than internal iterators. It's easy to compare two collections for equality with an external iterator, for example, but it's practically impossible with internal iterators…. But on the other hand, internal iterators are easier to use, because they define the iteration logic for you. [From The Ruby Programming Language book, ch. 5.3]

So, with external iterator you can do, e.g.:

p = "hello".enum_for(:each_char)
loop do
    puts p.next
end
简单 2024-09-09 13:12:17

假设我们想要获取一个键数组和一个值数组并将它们缝合在哈希中:

With #to_enum

def hashify(k, v)
  keys = k.to_enum(:each)
  values = v.to_enum(:each)
  hash = []
  loop do
    hash[keys.next] = values.next
    # No need to check for bounds,
    # as #next will raise a StopIteration which breaks from the loop
  end
  hash
end

Without #to_enum:

def hashify(k, v)
  hash = []
  keys.each_with_index do |key, index|
    break if index == values.length
    hash[key] = values[index]
  end
  hash
end

阅读起来要容易得多第一种方法,你觉得呢?不是很容易,但想象一下如果我们以某种方式操作 3 个数组中的项目会怎样? 5? 10?

Let's say we want to take an array of keys and an array of values and sew them up in a Hash:

With #to_enum

def hashify(k, v)
  keys = k.to_enum(:each)
  values = v.to_enum(:each)
  hash = []
  loop do
    hash[keys.next] = values.next
    # No need to check for bounds,
    # as #next will raise a StopIteration which breaks from the loop
  end
  hash
end

Without #to_enum:

def hashify(k, v)
  hash = []
  keys.each_with_index do |key, index|
    break if index == values.length
    hash[key] = values[index]
  end
  hash
end

It's much easier to read the first method, don't you think? Not a ton easier, but imagine if we were somehow manipulating items from 3 arrays? 5? 10?

滴情不沾 2024-09-09 13:12:17

这并不完全是您问题的答案,但希望它是相关的。

在第二个示例中,您调用 each_char 而不传递块。当不使用块调用时,each_char 返回一个Enumerator,因此您的示例实际上只是执行同一操作的两种方法。 (即两者都会导致创建可枚举对象。)

irb(main):016:0> e1 = "hello".enum_for(:each_char)
=> #<Enumerator:0xe15ab8>
irb(main):017:0> e2 = "hello".each_char
=> #<Enumerator:0xe0bd38>
irb(main):018:0> e1.map { |c| c.succ }
=> ["i", "f", "m", "m", "p"]
irb(main):019:0> e2.map { |c| c.succ }
=> ["i", "f", "m", "m", "p"]

This isn't quite an answer to your question, but hopefully it is relevant.

In your second example you are calling each_char without passing a block. When called without a block each_char returns an Enumerator so your examples are actually just two ways of doing the same thing. (i.e. both result in the creation of an enumerable object.)

irb(main):016:0> e1 = "hello".enum_for(:each_char)
=> #<Enumerator:0xe15ab8>
irb(main):017:0> e2 = "hello".each_char
=> #<Enumerator:0xe0bd38>
irb(main):018:0> e1.map { |c| c.succ }
=> ["i", "f", "m", "m", "p"]
irb(main):019:0> e2.map { |c| c.succ }
=> ["i", "f", "m", "m", "p"]
别闹i 2024-09-09 13:12:17

它非常适合大型或无限的生成器对象。
例如,下面将为您提供整个斐波那契数列的枚举器,从 0 到无穷大。

def fib_sequence
  return to_enum(:fib_sequence) unless block_given?
  yield 0
  yield 1
  x,y, = 0, 1
  loop { x,y = y,x+y; yield(y) }
end

to_enum 有效地允许您使用常规的 yields 编写此代码,而不必弄乱 Fiber

然后,您可以根据需要对其进行切片,这将非常节省内存,因为内存中不会存储任何数组:

module Slice
    def slice(range)
        return to_enum(:slice, range) unless block_given?
        start, finish = range.first, range.max + 1
        copy = self.dup
        start.times { copy.next }
        (finish-start).times { yield copy.next }
    end
end
class Enumerator
    include Slice
end

fib_sequence.slice(0..10).to_a
#=> [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
fib_sequence.slice(10..20).to_a                                                                                                                           
#=> [55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]

It's great for large or infinite generator objects.
E.g., the following will give you an enumerator for the whole Fibonacci seequence, from 0 to infinity.

def fib_sequence
  return to_enum(:fib_sequence) unless block_given?
  yield 0
  yield 1
  x,y, = 0, 1
  loop { x,y = y,x+y; yield(y) }
end

to_enum effectively allows you to write this with regular yields without having to mess with Fibers.

You can then slice it as you want, and it will be very memory efficient, since no arrays will be stored in memory:

module Slice
    def slice(range)
        return to_enum(:slice, range) unless block_given?
        start, finish = range.first, range.max + 1
        copy = self.dup
        start.times { copy.next }
        (finish-start).times { yield copy.next }
    end
end
class Enumerator
    include Slice
end

fib_sequence.slice(0..10).to_a
#=> [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
fib_sequence.slice(10..20).to_a                                                                                                                           
#=> [55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文