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

时间:2010-05-19 05:21:03

标签: ruby enumeration

为什么要使用to_enum方法而不是直接使用对象,在Ruby中创建对象的代理引用?我想不出任何实际用途,试图理解这个概念&有人可能会使用它,但我看到的所有例子都显得非常微不足道。

例如,为什么要使用:

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

而不是

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

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

5 个答案:

答案 0 :(得分:14)

大多数接受块的内置方法都会返回一个枚举器,以防止没有提供块(如示例中的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

答案 1 :(得分:3)

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

p = "hello".enum_for(:each_char)

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

  

外部迭代器比内部迭代器更灵活。例如,将两个集合与外部迭代器进行相等比较很容易,但内部迭代器实际上是不可能的....但另一方面,内部迭代器更容易使用,因为它们为您定义迭代逻辑。 [来自The Ruby Programming Language一书,ch。 5.3]

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

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

答案 2 :(得分:3)

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

使用#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

没有#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?

答案 3 :(得分:1)

这不是你问题的答案,但希望它是相关的。

在您的第二个示例中,您在不传递阻止的情况下调用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"]

答案 4 :(得分:1)

对于大型或无限的发电机对象来说非常棒。 例如,以下内容将为您提供整个Fibonacci序列的枚举器,从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]