我有一个地图,改变一个值或设置它为nil。然后我想从列表中删除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]

我知道我可以只做一个循环,并有条件地收集另一个数组,像这样:

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

但它看起来并不是那么地道。有没有一个很好的方法来映射一个函数在一个列表,删除/排除nils,因为你去?


当前回答

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添加了可枚举的#filter_map”是关于这个主题的一个很好的阅读,针对这个问题的一些早期方法提供了一些性能基准:

N = 100_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)

其他回答

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

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

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添加了可枚举的#filter_map”是关于这个主题的一个很好的阅读,针对这个问题的一些早期方法提供了一些性能基准:

N = 100_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)

如果你想要一个更宽松的拒绝标准,例如,拒绝空字符串以及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]

当然,紧凑是解决这个问题的最佳方法。然而,我们可以通过简单的减法得到相同的结果:

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

在你的例子中:

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

它看起来不像值发生了变化,只是被替换为nil。如果是这样的话,那么:

items.select{|x| process_x url}

就足够了。