【问题标题】:Skip over iteration in Enumerable#collect跳过 Enumerable#collect 中的迭代
【发布时间】:2011-07-06 08:15:08
【问题描述】:
(1..4).collect do |x|
  next if x == 3
  x + 1
end # => [2, 3, nil, 5]
    # desired => [2, 3, 5]

如果满足next 的条件,则collectnil 放入数组中,而我要做的是将no 元素放入返回的数组中,如果条件满足。如果不对返回的数组调用delete_if { |x| x == nil },这是否可能?

(使用 Ruby 1.8.7;我的代码摘录非常抽象)

【问题讨论】:

  • 在 2.7 中引入了一个真正快速的专用解决方案:filter_mapmore info here

标签: ruby enumerable


【解决方案1】:

我会简单地在结果数组上调用.compact,这会删除数组中的所有 nil 实例。如果您希望它修改现有数组(没有理由不这样做),请使用.compact!

(1..4).collect do |x|
  next if x == 3
  x
end.compact!

【讨论】:

  • 出于兴趣,我对目前建议的四种解决方案进行了快速基准测试:收集+压缩、收集+压缩!、拒绝+收集并随时构建结果数组。至少在 MRI 1.9.1 中,收集+压缩!以微弱优势成为速度赢家,collect+compact 和 reject+collect 紧随其后。构建结果数组的速度大约是那些速度的两倍。
  • @glenn:您介意将以下内容添加到您的基准测试中吗:(1..4).inject([]){|a,x| x == 3 ? a : a.push(x + 1)}?谢谢。
  • 当然。这是迄今为止最慢的一个,尽管只比构建结果数组慢一点。我还添加了一个这样调整的版本:a.inject([]){|aa,x| aa << x + 1 unless x == 3; aa},它比构建数组更快,但仍然比三个 3 快速方式慢得多。
  • 这可行,但您必须担心原始数组中包含合法nils 的情况。
  • 我不知道使用map{}.compact!,如果没有跳过任何内容,它会返回nil。我想如果你能保证至少有一个元素会被跳过......
【解决方案2】:

我建议使用:

(1..4).to_a.delete_if {|x| x == 3}

而不是 collect + next 语句。

【讨论】:

  • real 代码比我的问题中的抽象代码更复杂,因此不幸的是,这无法实现我想要的。 (collect 实际上是操纵值,而不是仅仅返回它)
【解决方案3】:

只是一个建议,你为什么不这样做:

result = []
(1..4).each do |x|
  next if x == 3
  result << x
end
result # => [1, 2, 4]

以这种方式,您保存了另一个迭代以从数组中删除 nil 元素。希望对你有帮助=)

【讨论】:

  • 这就像 Mladen 的 reject 示例的更详细版本。
  • 其实不是,“reject”遍历数组一次,“collect”又遍历一次数组,所以有两次迭代。在“每个”的情况下,它只这样做一次 =)
  • 这是一个合理的想法,但在 Ruby 中,进行速度测试并查看实际发生的情况通常非常有趣。在我的快速基准测试中(当然,这可能与 Andrew 的真实情况相匹配,也可能不匹配),以这种方式构建数组实际上是其他任何方式的两倍。我怀疑问题是在 C 中遍历数组实际上比在 Ruby
  • 这里实际上不需要next。只需 result &lt;&lt; x unless x == 3 就可以了,而且更干净一些。
  • @Staelen 抱歉,您是对的。尽管如此,我仍然认为更漂亮和更快的解决方案会赢得这一轮。
【解决方案4】:

有一个方法Enumerable#reject 只是为了达到目的:

(1..4).reject{|x| x == 3}.collect{|x| x + 1}

将一种方法的输出直接用作另一种方法的输入的做法称为方法链接,在 Ruby 中很常见。

顺便说一句,map(或collect)用于将可枚举的输入直接映射到输出。如果您需要输出不同数量的元素,您可能需要Enumerable 的另一种方法。

编辑:如果您对某些元素被迭代两次这一事实感到困扰,您可以使用基于inject(或名为each_with_object 的类似方法)的不太优雅的解决方案:

(1..4).each_with_object([]){|x,a| a << x + 1 unless x == 3}

【讨论】:

  • 这是最慢的答案(虽然不是很多),但肯定是最干净的。我只是希望有一种方法可以使用collect 来做到这一点,因为我希望调用next 会返回绝对没有,而不是“无”(又名nil)。
  • 如果它比任何其他方法都慢,你不觉得 Ruby 的优化器可以做一些工作吗?
【解决方案5】:

您可以将决策拉入辅助方法中,并通过Enumerable#reduce 使用它:

def potentially_keep(list, i)
  if i === 3
    list
  else
    list.push i
  end
end
# => :potentially_keep

(1..4).reduce([]) { |memo, i| potentially_keep(memo, i) }
# => [1, 2, 4]

【讨论】:

    【解决方案6】:

    Ruby 2.7+

    现在有!

    Ruby 2.7 正是为此目的引入了filter_map。它既惯用又高效,我希望它很快就会成为常态。

    例如:

    numbers = [1, 2, 5, 8, 10, 13]
    numbers.filter_map { |i| i * 2 if i.even? }
    # => [4, 16, 20]
    

    这是good read on the subject

    希望对某人有用!

    【讨论】:

    • 很棒的发现 :) 它也适用于 OP 尝试的nextnumbers.filter_map { |i| next if i.odd?; i * 2 }
    猜你喜欢
    • 2011-08-06
    • 1970-01-01
    • 2015-06-20
    • 2011-06-28
    • 1970-01-01
    • 1970-01-01
    • 2015-05-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多