如何在 Ruby 中映射和删除 nil 值

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13485468/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-06 05:31:53  来源:igfitidea点击:

How to map and remove nil values in Ruby

ruby

提问by Pete Hamilton

I have a mapwhich either changes a value or sets it to nil. I then want to remove the nil entries from the list. The list doesn't need to be kept.

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

This is what I currently have:

这是我目前拥有的:

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

I'm aware I could just do a loop and conditionally collect in another array like this:

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

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

But it doesn't seem that idiomatic. Is there a nice way to map a function over a list, removing/excluding the nils as you go?

但这似乎并不那么地道。有没有一种很好的方法可以将函数映射到列表上,随时删除/排除 nils?

采纳答案by SRack

Ruby 2.7+

红宝石 2.7+

There is now!

现在有!

Ruby 2.7 is introducing filter_mapfor this exact purpose. It's idiomatic and performant, and I'd expect it to become the norm very soon.

Ruby 2.7 正是filter_map为此目的而引入的。这是惯用的和高性能的,我希望它很快成为常态。

For example:

例如:

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

In your case, as the block evaluates to falsey, simply:

在您的情况下,当块评估为 falsey 时,只需:

items.filter_map { |x| process_x url }

"Ruby 2.7 adds Enumerable#filter_map" is a good read on the subject, with some performance benchmarks against some of the earlier approaches to this problem:

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)

回答by the Tin Man

You could use compact:

你可以使用compact

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


I'd like to remind people that if you're getting an array containing nils as the output of a mapblock, and that block tries to conditionally return values, then you've got code smell and need to rethink your logic.

我想提醒人们,如果您得到一个包含 nils 的数组作为map块的输出,并且该块尝试有条件地返回值,那么您就有代码异味,需要重新考虑您的逻辑。

For instance, if you're doing something that does this:

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

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

Then don't. Instead, prior to the map, rejectthe stuff you don't want or selectwhat you do want:

那就别了。相反,在 之前mapreject你不想要的东西或select你想要的东西:

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

I consider using compactto clean up a mess as a last-ditch effort to get rid of things we didn't handle correctly, usually because we didn't know what was coming at us. We should always know what sort of data is being thrown around in our program; Unexpected/unknown data is bad. Anytime I see nils in an array I'm working on, I dig into why they exist, and see if I can improve the code generating the array, rather than allow Ruby to waste time and memory generating nils then sifting through the array to remove them later.

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

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

回答by Ziggy

Try using reduceor inject.

尝试使用reduceinject

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

  memo
}

I agree with the accepted answer that we shouldn't mapand compact, but not for the same reasons.

我同意公认的答案,我们不应该mapcompact,但不是出于同样的原因。

I feel deep inside that mapthen compactis equivalent to selectthen map. Consider: mapis a one-to-one function. If you are mapping from some set of values, and you map, then you wantone value in the output set for each value in the input set. If you are having to selectbefore-hand, then you probably don't want a mapon the set. If you are having to selectafterwards (or compact) then you probably don't want a mapon the set. In either case you are iterating twice over the entire set, when a reduceonly needs to go once.

我内心深处觉得mapthencompact就等于selectthen map。考虑:map是一对一的函数。如果是从一些设定值的映射,你map,然后你在输入集中的每个值输出设定一个值。如果您必须select事先这样做,那么您可能不希望map在片场有一个。如果您select事后(或compact)必须这样做,那么您可能不希望map在片场有一个。在任何一种情况下,当 areduce只需要去一次时,你都会在整个集合上迭代两次。

Also, in English, you are trying to "reduce a set of integers into a set of even integers".

此外,在英语中,您试图“将一组整数减少为一组偶数整数”。

回答by sawa

In your example:

在你的例子中:

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

it does not look like the values have changed other than being replaced with nil. If that is the case, then:

除了被替换为nil. 如果是这种情况,那么:

items.select{|x| process_x url}

will suffice.

就足够了。

回答by Evgenia Manolova

Definitely compactis the best approach for solving this task. However, we can achieve the same result just with a simple subtraction:

绝对compact是解决此任务的最佳方法。但是,我们可以通过简单的减法获得相同的结果:

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

回答by Fred Willmore

If you wanted a looser criterion for rejection, for example, to reject empty strings as well as nil, you could use:

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

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

If you wanted to go further and reject zero values (or apply more complex logic to the process), you could pass a block to reject:

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

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

回答by pnomolos

each_with_objectis probably the cleanest way to go here:

each_with_object可能是去这里最干净的方式:

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

In my opinion, each_with_objectis better than inject/reducein conditional cases because you don't have to worry about the return value of the block.

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

回答by Wand Maker

One more way to accomplish it will be as shown below. Here, we use Enumerable#each_with_objectto collect values, and make use of Object#tapto get rid of temporary variable that is otherwise needed for nilcheck on result of process_xmethod.

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

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


Complete example for illustration:

完整示例:

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?}}


Alternate approach:

替代方法:

By looking at the method you are calling process_x url, it is not clear what is the purpose of input xin that method. If I assume that you are going to process the value of xby passing it some urland determine which of the xs really get processed into valid non-nil results - then, may be Enumerabble.group_byis a better option than Enumerable#map.

通过查看您正在调用的方法process_x url,尚不清楚x该方法中输入的目的是什么。如果我假设您将x通过传递一些值来处理s的值url并确定哪些xs 真正被处理为有效的非零结果 - 那么,可能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]