如何在Ruby中Map和删除nil值

fruv7luv  于 2023-08-04  发布在  Ruby
关注(0)|答案(9)|浏览(136)

我有一个map,它要么改变一个值,要么将其设置为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


但好像不太习惯。有没有一种很好的方法来Map一个函数到一个列表上,在你去的时候删除/排除nils?

y0u0uwnf

y0u0uwnf1#

可以使用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]


那么就不要。相反,在mapreject你不想要的东西或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]

webghufk

webghufk2#

尝试使用reduceinject

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

  memo
}

字符串
我同意公认的答案,我们不应该mapcompact,但原因不同。
我内心深处觉得map然后compact等价于select然后map。考虑:map是一对一函数。如果你是从某个值集Map的,并且你是map,那么你希望在输出集中为输入集中的每个值都有一个值。如果你必须事先select,那么你可能不希望在设置上使用map。如果你必须在之后使用select(或compact),那么你可能不希望在设置中使用map。在这两种情况下,您都在整个集合上迭代了两次,而reduce只需要执行一次。
同样,在英语中,你试图“将一组整数减少到一组偶数”。

qeeaahzv

qeeaahzv3#

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]

字符串
在你的例子中,当块的计算结果为falsey时,只需:

items.filter_map { |x| transform(x) }


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

rt4zxlrg

rt4zxlrg4#

毫无疑问,compact是解决这个任务的最佳方法。然而,我们可以通过简单的减法来实现相同的结果:

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

字符串

ryevplcw

ryevplcw5#

在您的示例中:

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

字符串
除了被替换为nil之外,这些值看起来没有改变。如果是这种情况,则:

items.select{|x| transform(x) }


就足够了

lp0sw83n

lp0sw83n6#

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

twh00eeo

twh00eeo7#

你可以在结果数组上使用#compact方法。

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

字符串

0ejtzxu1

0ejtzxu18#

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更好,因为你不必担心块的返回值。

mutmk8jj

mutmk8jj9#

实现它的另一种方式将如下所示。在这里,我们使用Enumerable#each_with_object来收集值,并使用Object#tap来删除临时变量,否则nil检查transform x方法的结果时需要临时变量。

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

字符串
完整的示例说明:

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

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

替代方法:

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

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

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

相关问题