为啥 Enumerable 在 Ruby 中没有长度属性?
Posted
技术标签:
【中文标题】为啥 Enumerable 在 Ruby 中没有长度属性?【英文标题】:Why does Enumerable not have a length attribute in Ruby?为什么 Enumerable 在 Ruby 中没有长度属性? 【发布时间】:2015-05-04 12:44:57 【问题描述】:至少在 Ruby 1.9.3 中,Enumerable
对象没有length
属性。这是为什么呢?
【问题讨论】:
没有“可枚举类”。 “任何 Enumerable 类”是指混合了Enumerable
模块的任何类吗?这样的类(和其他类)有一个length
(又名size
)方法。
这里有Enumerable#count
。
@ChrisHeald 的回答提醒我,在我说“这样的类有一个 length
方法的地方,我没有提到我指的是内置类,但即便如此我现在也不确定. 有谁知道没有length
方法的混入Enumerable
的内置类?
谢谢,@Max。我认为有不少(例如,Integer
、Numerica
、Proc
、CSV
、Matrix
),也许定义length
的类是例外而不是规则。
值得注意的是,并不是所有的枚举都是有序的。例如,Set
是可枚举的,但不是有序的。在集合上调用#sort
仍然有意义的原因是,返回值 是有序的(它是Array
,正如Enumerable
的文档所示)。
【参考方案1】:
不保证 Enumerable 具有长度 - 对 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 提供的结果集的大小,请尝试使用 .to_a.length
#count
。
【讨论】:
但是有Enumerable#count
,和length
差不多。
一般来说#count
是O(n),而#length
是O(1);文档和提供的示例清楚地说明了这一点,因为您必须迭代枚举以发现需要多少次调用才能终止。
但是count
仍然比to_a.length
更受欢迎
公平点。我认为更完整的答案是“Enumerable 不支持#length,因为它无法在恒定时间内提供该答案”。
好答案。一个细节:定义<=>
只是在使用需要它的Enumerable
方法时才需要。有很多Enumerable
方法(如count
)不使用<=>
。【参考方案2】:
Enumerable
并不是一个真正的类,它是一个模块——一个由多个类使用的横切功能的集合。
例如,Array
、Set
和 Hash
都是 include
它 - 您可以在它们上调用任何 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]
如您所见,我从未定义过 CountUntil#map
,但我通过包含 Enumerable
免费获得了它。
解决您关于length
的问题:并非所有包含Enumerable
的类都有定义的长度,尽管大多数都有。例如,Enumerator
可用于创建无限流。
【讨论】:
你可能想解决Enumerable#count
的存在。【参考方案3】:
Enumerable 有count
方法,它通常是枚举的直观“长度”。
但是为什么不叫它“长度”呢?好吧,因为它的运作方式非常不同。在像Array
和Hash
这样的Ruby 内置数据结构中,length
只是检索数据结构的预先计算的大小。它应该总是立即返回。
然而,对于Enumerable#count
,它无法知道它正在运行什么样的结构,因此没有快速、巧妙的方法来获取枚举的大小(这是因为Enumerable
是一个模块,并且可以包含在任何类中)。它获得枚举大小的唯一方法是实际枚举它并计数。对于无限枚举,count
将(适当地)永远循环并且永不返回。
【讨论】:
谢谢。忽略这一点我觉得很愚蠢。不过,我很高兴我问了。我不会意识到行为上有如此不同。 另一方面,Enumerator
有一个 size
方法,它对无限枚举按预期工作:loop.size #=> Infinity
@Stefan, ...对于有限的枚举器,它需要懒惰地完成。例如,enum = [1,2,3].to_enum; enum.size #=> nil
。
@CarySwoveland 你是对的,枚举器必须返回它的大小。但它似乎适用于内置方法:[1,2,3].each.size #=> 3
以上是关于为啥 Enumerable 在 Ruby 中没有长度属性?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 Enumerable 中的方法返回 Enumerator?
为啥不推荐使用 Enumerable#each_with_object ?
为啥 .NET Core 中 Enumerable.Zip() 的 2 参数重载但 .NET Standard 中没有? [关闭]