在Ruby中映射和删除nil值

时间:2012-11-21 02:29:13

标签: ruby

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

这就是我目前所拥有的:

items.map! { |x| process_x url } # [1, 2, 3, 4, 5] => [1, nil, 3, nil, nil]
items.select! { |x| !x.nil? } # [1, nil, 3, nil, nil] => [1, 3]

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

new_items = []
items.each do |x|
    x = process_x x
    new_items.append(x) unless x.nil?
end
items = new_items

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

9 个答案:

答案 0 :(得分:831)

您可以使用compact

[1, nil, 3, nil, nil].compact
=> [1, 3] 

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

例如,如果你正在做一些事情:

[1,2,3].map{ |i|
  if i % 2 == 0
    i
  end
}
# => [nil, 2, nil]

然后不要。相反,在map之前,reject您不想要的内容或select您想要的内容:

[1,2,3].select{ |i| i % 2 == 0 }.map{ |i|
  i
}
# => [2]

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

'Just my $%0.2f.' % [2.to_f/100]

答案 1 :(得分:78)

尝试使用#reduce#inject

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

  memo
}

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

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

另外,在英语中,你试图将一组整数减少为一组偶数整数&#34;。

答案 2 :(得分:33)

在你的例子中:

items.map! { |x| process_x url } # [1, 2, 3, 4, 5] => [1, nil, 3, nil, nil]

除了被nil替换之外,它看起来似乎没有变化。如果是这种情况,那么:

items.select{|x| process_x url}

就足够了。

答案 3 :(得分:25)

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

[1, nil, 3, 0, ''].reject(&:blank?)
 => [1, 3, 0] 

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

[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]

答案 4 :(得分:21)

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

[1, nil, 3, nil, nil] - [nil]
 => [1, 3]

答案 5 :(得分:6)

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]

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

items.filter_map { |x| process_x url }

Ruby 2.7 adds 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)

答案 6 :(得分:3)

each_with_object可能是最干净的方式:

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

在我看来,each_with_object在有条件的情况下优于inject / reduce,因为您不必担心该块的返回值。

答案 7 :(得分:0)

实现它的另一种方法如下所示。在这里,我们使用Enumerable#each_with_object来收集值,并使用Object#tap来删除nil检查process_x方法结果所需的临时变量。< / p>

items.each_with_object([]) {|x, obj| (process x).tap {|r| obj << r unless r.nil?}}

完整的插图示例:

items = [1,2,3,4,5]
def process x
    rand(10) > 5 ? nil : x
end

items.each_with_object([]) {|x, obj| (process x).tap {|r| obj << r unless r.nil?}}

替代方法:

通过查看您调用process_x url的方法,不清楚该方法中输入x的目的是什么。如果我假设你要通过传递x来处理url的值并确定哪个x真正被处理成有效的非零结果 - 那么,可能Enumerabble.group_by是比Enumerable#map更好的选择。

h = items.group_by {|x| (process x).nil? ? "Bad" : "Good"}
#=> {"Bad"=>[1, 2], "Good"=>[3, 4, 5]}

h["Good"]
#=> [3,4,5]

答案 8 :(得分:0)

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

[10, nil, 30, 40, nil].compact => [10, 30, 40]