为什么在Ruby中,Enumerable没有长度属性?

11

至少在 Ruby 1.9.3 中,Enumerable 对象没有 length 属性。这是为什么?


1
没有“Enumerable类”。你所说的“任何可枚举类”是指已经混入了Enumerable模块的任何类吗?这样的类(以及其他类)都有一个length(又名size)方法。 - Cary Swoveland
4
Enumerable#count 方法。 - cremno
@ChrisHeald 的回答提醒我,在我说“这些类有一个 length 方法”时,我没有提到我指的是内置类,但现在我甚至不确定。是否有人知道一个混入了 Enumerable 但没有 length 方法的内置类? - Cary Swoveland
@Max,......或 size,例如,Range#size。 :-) - Cary Swoveland
1
谢谢,@Max。我认为有相当多的类(例如IntegerNumericaProcCSVMatrix),也许定义length的类是个例外而不是规则。 - Cary Swoveland
显示剩余6条评论
3个回答

18

Enumerable类有一个count方法,通常会返回枚举对象的直观“长度”。

但为什么不能称之为“长度”呢?因为它的操作方式完全不同。在 Ruby 的内置数据结构中(如ArrayHash),length只是检索预计算的数据结构大小,它应该立即返回。

然而,在Enumerable#count中,它无法知道其操作的结构类型,因此没有快速、聪明的方法来获取枚举对象的大小(这是因为Enumerable是一个模块,并且可以包含在任何类中)。它获取枚举对象的大小的唯一方法就是对其进行枚举并计数。对于无限枚举,count将(适当地)永远循环并永远不会返回。


谢谢。我觉得自己很傻,竟然忽略了那个。但我很高兴问了出来,否则我不会意识到行为上有这么大的区别。 - kdbanman
另一方面,Enumerator有一个size方法,并且对于无限枚举,它的工作方式符合预期:loop.size #=> Infinity - Stefan
@Stefan,对于有限的枚举器,需要进行惰性处理。例如,enum = [1,2,3].to_enum; enum.size #=> nil - Cary Swoveland
@CarySwoveland 你说得对,枚举器必须返回其大小。但似乎内置方法可以正常工作:[1,2,3].each.size #=> 3 - Stefan

3

枚举对象并不保证有长度 - 混入 Enumerable 的唯一要求是它响应 #each,这使其返回系列中的下一个项,以及 #<=>,它允许比较可枚举提供的值。像 #sort 这样的方法将在排序过程中枚举整个集合,但可能事先不知道集合的范围。请考虑:

class RandomSizeEnumerable
  include Enumerable
  def each
    value = rand 1000
    while value != 500
      yield value
      value = rand 1000
    end
  end

  # Not needed for this example, but included as a part of the Enumerable "interface".
  # You only need this method if #max, #min, or #sort are used on this class.
  def <=>(a, b)
    a <=> b
  end
end

该可枚举对象将被调用,直到迭代器生成值“500”,这将导致停止枚举。结果集被收集并排序。然而,在这种情况下,#length方法是没有意义的,因为在迭代器被耗尽之前无法确定长度!

我们可以对像#sort这样返回数组的结果调用#length

p RandomSizeEnumerable.new.sort.length # 321
p RandomSizeEnumerable.new.sort.length # 227
p RandomSizeEnumerable.new.sort.length # 299

传统上,当长度已知且可以在常数时间内返回时,使用#length,而当长度可能事先不知道并需要通过迭代结果集进行计算时,则倾向于使用#count(有时也使用#size),这样会花费线性时间。如果你需要获取 Enumerable 提供的结果集大小,请尝试使用#count,而不是 .to_a.length


1
但是有Enumerable#count,它几乎与length相同。 - mu is too short
通常情况下,#count 的时间复杂度为O(n),而 #length 的时间复杂度为O(1)。文档和提供的示例清楚地说明了这一点,因为您必须迭代可枚举对象才能发现需要多少次调用才能终止。 - Chris Heald
但是 count 仍然比 to_a.length 更受欢迎。 - Max
有道理。我认为更完整的答案应该是“Enumerable不支持#length,因为它不能在常量时间内提供答案”。 - Chris Heald
好的回答。一个细节:只有在需要使用Enumerable方法时才需要定义<=>。有许多Enumerable方法(例如count)不使用<=> - Cary Swoveland
显示剩余2条评论

0

Enumerable并不是一个类,而是一个模块 - 一个包含跨越多个类使用的横切功能的集合。

例如,ArraySetHashinclude它 - 您可以在它们上调用任何Enumerable方法。

Enumerable值得注意的是,它对“主机”类的要求非常少。您只需要定义each方法和include Enumerable,就可以免费获得所有这些方法!例如:

class CountUntil
  def initialize(number)
    @number = number
  end

  include Enumerable

  def each
    current = 0
    while current < @number
      yield current
      current += 1
    end
  end
end

# Usage:

CountUntil.new(10).map { |n| n * 5 }
# => [0, 5, 10, 15, 20, 25, 30, 35, 40, 45]

如你所见,我从包含的Enumerable中得到了免费的CountUntil#map的定义,我并没有自己去定义它。
对于你关于length的问题:并不是所有包含Enumerable的类都有定义length属性,尽管大多数都有。例如,Enumerator可以用来创建无限流。

1
你可能想要提及Enumerable#count的存在。 - mu is too short

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接