如何在 Ruby 中获取 Enumerable 的第 n 个元素

7
例如,要返回第10,000个质数,我可以编写以下代码:
require 'prime'
Prime.first(10000).last #=> 104729

但是,为了获取最后一个元素而创建一个巨大的中间数组有点麻烦。

考虑到 Ruby 是一种优雅的语言,我希望能够像这样实现:

Prime.at(9999) #=> 104729

但是没有Enumerable#at方法。

上述解决方法是否有意义,或者有更直接的方法来获取Enumerable的第n个元素?


考虑建议将 Enumerable#at 添加到 Ruby 核心。 - Cary Swoveland
4个回答

9

我能想到的最接近假设中的at方法是drop,它跳过指定数量的元素。不过它试图返回一个实际数组,所以如果你要与无限序列一起使用,需要与lazy结合使用,例如:

Prime.lazy.drop(9999).first

5

这个怎么样?

Prime.to_enum.with_index(1){|e, i| break e if i == 10000}
# => 104729

对于不懒惰的枚举器,您可能希望在枚举器上放置lazy


1
这样可以避免中间数组,但不幸的是,它甚至更加繁琐。有没有想法为什么没有像Enumerable#at这样的内置方法? - Stefan
2
可能是因为如果你只对特定的值感兴趣,那么应该有一种直接返回这个值的方法,而不是使用枚举器。对于质数,我认为更多的用例是当你想要第n个质数时,而不是从头开始遍历。所以Prime类的设计方式不太合适,或者受到其内部算法的影响过大。 - sawa
我认为这是因为当我们使用 Enumerable 时,大多数情况下我们实际上对对象的枚举感兴趣,而不是特定索引处的一个对象,并且编写 Enumerable#at 并不难 - 参见 @sawa 的示例。但这只是一种观点 - 我不知道这是否是正确的假设。 - eugen
大多数情况下,可枚举对象实际上并不是无限的,而是由某些有限的数据结构支持(例如ArrayHash)。因此,可以非常高效地直接从数据结构中获取第n个元素,而无需枚举列表,例如my_array[10]。如果您只有一个(无限的)列表,就像这里的情况一样,您别无选择,只能获取所有元素并删除您不感兴趣的元素。最终,Enumerable模块提供的所有功能都基于实现each方法以循环遍历其任意元素的对象。 - Holger Just
@HolgerJust 是正确的,我完全知道枚举无法从 Enumerable 中跳过。但这并不能解释为什么 Enumerable#at 会消失,不是吗?Array 仍然可以提供自己的(更快的)实现来替代 at - Stefan

0

基于sawa的解决方案,这里提供了一种更简洁的替代方案:

Prime.find.with_index(1) { |_, i| i == 10000 }
#=> 104729

或者

Prime.find.with_index { |_, i| i == 9999 }
#=> 104723

0
module Enumerable
  def at(n)
    enum = is_a?(Enumerator) ? self : each
    (n-1).times { enum.next }
    enum.next
  end
end

(0..4).at(3)
  #=> 2 
{ a:1, b:2, c:3, d:4, e:5 }.at(3)
  #=> [:c, 3] 
'0ab_1ab_2ab_3ab_4ab'.gsub(/.(?=ab)/).at(3)
  #=> "2" 
require 'prime'; Prime.at(10000)
  #=> 104729 
e = 1.step; e.at(10)
  #=> 10 
[0,1,2,3,4].at(6)
  #=> nil 
'0ab_1ab_2ab_3ab_4ab'.gsub(/.(?=ab)/).at(6)
  #=> StopIteration (iteration reached an end)

请注意:'0ab_1ab_2ab_3ab_4ab'.gsub(/.(?=ab)/).class #=> Enumerator

需要进行一些改进,例如检查n是否为大于零的整数,并改善异常处理,例如处理self不是枚举器但没有each方法的情况。


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