在Ruby中映射和删除nil值

Pet*_*ton 334 ruby

我有一个地图,可以更改值或将其设置为零.然后我想从列表中删除nil条目.该列表不需要保留.

这就是我目前拥有的:

# A simple example function, which returns a value or nil
def transform(n)
  rand > 0.5 ? n * 10 : nil }
end

items.map! { |x| transform(x) } # [1, 2, 3, 4, 5] => [10, nil, 30, 40, nil]
items.reject! { |x| x.nil? } # [10, nil, 30, 40, nil] => [10, 30, 40]
Run Code Online (Sandbox Code Playgroud)

我知道我可以做一个循环并有条件地收集另一个数组,如下所示:

new_items = []
items.each do |x|
    x = transform(x)
    new_items.append(x) unless x.nil?
end
items = new_items
Run Code Online (Sandbox Code Playgroud)

但它似乎不是红宝石般的.有没有一种很好的方法可以在列表中运行一个函数来删除/排除nils?

the*_*Man 868

你可以使用compact:

[1, nil, 3, nil, nil].compact
=> [1, 3] 
Run Code Online (Sandbox Code Playgroud)

我想提醒大家,如果你得到一个包含nils作为map块输出的数组,并且该块试图有条件地返回值,那么你就会有代码味道,需要重新考虑你的逻辑.

例如,如果您正在执行以下操作:

[1,2,3].map{ |i|
  if i % 2 == 0
    i
  end
}
# => [nil, 2, nil]
Run Code Online (Sandbox Code Playgroud)

然后不要.相反,在你之前map,reject你不想要的东西或select你想要的东西:

[1,2,3].select{ |i| i % 2 == 0 }.map{ |i|
  i
}
# => [2]
Run Code Online (Sandbox Code Playgroud)

我考虑使用compact清理混乱作为最后努力摆脱我们没有正确处理的事情,通常是因为我们不知道会发生什么.我们应该始终知道在我们的程序中抛出了什么类型的数据; 意外/未知数据很糟糕.无论何时我在我正在研究的数组中看到nils,我都会深入了解它们存在的原因,看看我是否可以改进生成数组的代码,而不是让Ruby浪费时间和内存生成nils然后筛选数组以删除他们以后.

'Just my $%0.2f.' % [2.to_f/100]
Run Code Online (Sandbox Code Playgroud)

  • 现在那是红宝石般的! (25认同)
  • 两个解决方案在集合上迭代两次......为什么不使用`reduce`或`inject`? (9认同)
  • 这听起来不像你读过OP的问题或答案.问题是,如何从数组中删除nils.`compact`是最快的,但实际上在开始时正确编写代码消除了完全处理nils的需要. (4认同)
  • 为什么要这样?OP需要删除`nil`条目,而不是空字符串.BTW,`nil`与空字符串不同. (3认同)
  • 我不同意!问题是"映射并删除零值".好吧,映射和删除nil值是为了减少.在他们的例子中,OP映射然后选择nils.调用地图然后紧凑,或选择然后映射,相当于犯了同样的错误:正如你在答案中指出的那样,它是一种代码味道. (2认同)
  • 来自@Ziggy的回答应被接受为正确答案 (2认同)
  • @theTinMan 为什么带有“select”的解决方案需要另一个“map”?`[1,2,3].select{ |i| i % 2 == 0 }` 足以返回 `[2]`,对吗?map 部分没有意义,因为它返回元素本身 `map { |x| x }` 相当于输入数组。 (2认同)

Zig*_*ggy 90

尝试使用reduceinject!

[1, 2, 3].reduce([]) { |memo, i|
  if i % 2 == 0
    memo << i
  end

  memo
}
Run Code Online (Sandbox Code Playgroud)

我同意接受的答案,我们不应该映射和压缩,但不是出于同样的原因!

我深深感觉到地图 - 然后 - 紧凑等同于select-then-map.考虑:地图是一对一的功能.如果要从某些值集映射并映射,则输出集中的每个值的输出集中都需要一个值.如果您必须事先选择,那么您可能不希望在集合上有地图.如果你必须事后选择(或紧凑),那么你可能不希望在集合上有地图.在任何一种情况下,你都需要在整个集合上进行两次迭代,而reduce只需要进行一次.

另外,在英语中,您试图"将一组整数减少为一组偶数整数".

  • 可怜的Ziggy,不爱你的建议.大声笑.加一,其他人有数百个赞成! (3认同)
  • +1 当前接受的答案不允许您使用在选择阶段执行的操作的结果 (3认同)
  • 我相信有一天,在你的帮助下,这个答案将超过所接受的.^ O ^ // (2认同)

saw*_*awa 33

在你的例子中:

items.map! { |x| process_x url } # [1, 2, 3, 4, 5] => [1, nil, 3, nil, nil]
Run Code Online (Sandbox Code Playgroud)

除了被替换之外,它看起来并没有改变nil.如果是这样的话,那么:

items.select{|x| process_x url}
Run Code Online (Sandbox Code Playgroud)

就足够了.


Fre*_*ore 26

如果你想要一个更宽松的拒绝标准,例如,拒绝空字符串以及nil,你可以使用:

[1, nil, 3, 0, ''].reject(&:blank?)
 => [1, 3, 0] 
Run Code Online (Sandbox Code Playgroud)

如果您想进一步拒绝零值(或将更复杂的逻辑应用于流程),您可以传递一个块来拒绝:

[1, nil, 3, 0, ''].reject do |value| value.blank? || value==0 end
 => [1, 3]

[1, nil, 3, 0, '', 1000].reject do |value| value.blank? || value==0 || value>10 end
 => [1, 3]
Run Code Online (Sandbox Code Playgroud)

  • .空白?仅适用于铁轨. (5认同)

Evg*_*ova 22

@the Tin Man,很好 - 我不知道这个方法.嗯,绝对紧凑是最好的方法,但仍然可以通过简单的减法完成:

[1, nil, 3, nil, nil] - [nil]
 => [1, 3]
Run Code Online (Sandbox Code Playgroud)

  • 是的,set减法会起作用,但由于它的开销,它大约快一半. (4认同)

小智 9

您可以#compact对结果数组使用方法。

[10, nil, 30, 40, nil].compact => [10, 30, 40]
Run Code Online (Sandbox Code Playgroud)


pno*_*los 5

each_with_object可能是最干净的方式:

new_items = items.each_with_object([]) do |x, memo|
    ret = process_x(x)
    memo << ret unless ret.nil?
end
Run Code Online (Sandbox Code Playgroud)

在我看来,在条件情况下each_with_objectinject/更好reduce,因为你不必担心块的返回值。


SRa*_*ack 5

Ruby 2.7以上

现在有!

Ruby 2.7正是filter_map为此目的而引入的。它是惯用语言和高性能,我希望它很快会成为标准。

例如:

numbers = [1, 2, 5, 8, 10, 13]
enum.filter_map { |i| i * 2 if i.even? }
# => [4, 16, 20]
Run Code Online (Sandbox Code Playgroud)

在您的情况下,由于该块的评估结果为假,因此只需:

items.filter_map { |x| process_x url }
Run Code Online (Sandbox Code Playgroud)

Ruby 2.7增加了Enumerable#filter_map ”是关于该主题的不错的阅读,它针对一些较早的解决此问题的方法提供了性能基准:

N = 1_00_000
enum = 1.upto(1_000)
Benchmark.bmbm do |x|
  x.report("select + map")  { N.times { enum.select { |i| i.even? }.map{|i| i + 1} } }
  x.report("map + compact") { N.times { enum.map { |i| i + 1 if i.even? }.compact } }
  x.report("filter_map")    { N.times { enum.filter_map { |i| i + 1 if i.even? } } }
end

# Rehearsal -------------------------------------------------
# select + map    8.569651   0.051319   8.620970 (  8.632449)
# map + compact   7.392666   0.133964   7.526630 (  7.538013)
# filter_map      6.923772   0.022314   6.946086 (  6.956135)
# --------------------------------------- total: 23.093686sec
# 
#                     user     system      total        real
# select + map    8.550637   0.033190   8.583827 (  8.597627)
# map + compact   7.263667   0.131180   7.394847 (  7.405570)
# filter_map      6.761388   0.018223   6.779611 (  6.790559)
Run Code Online (Sandbox Code Playgroud)