Map and Remove nil values in Ruby
我有一个地图,可以更改值或将其设置为零。 然后我想从列表中删除nil条目。 该列表不需要保留。
这就是我目前拥有的:
1 2 3 4 5 6 7 | # 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] |
我知道我可以做一个循环并有条件地收集另一个数组,如下所示:
1 2 3 4 5 6 | new_items = [] items.each do |x| x = transform(x) new_items.append(x) unless x.nil? end items = new_items |
但它似乎并不是惯用语。 有没有一种很好的方法可以将一个函数映射到一个列表上,当你去的时候删除/排除nils?
你可以使用
1 2 | [1, nil, 3, nil, nil].compact => [1, 3] |
我想提醒大家的是,如果你得到一个包含nils的数组作为
例如,如果您正在执行以下操作:
1 2 3 4 5 6 | [1,2,3].map{ |i| if i % 2 == 0 i end } # => [nil, 2, nil] |
然后不要。相反,在
1 2 3 4 | [1,2,3].select{ |i| i % 2 == 0 }.map{ |i| i } # => [2] |
我考虑使用
1 | 'Just my $%0.2f.' % [2.to_f/100] |
尝试使用
1 2 3 4 5 6 7 | [1, 2, 3].reduce([]) { |memo, i| if i % 2 == 0 memo << i end memo } |
我同意接受的答案,我们不应该映射和压缩,但不是出于同样的原因!
我深深感觉到地图 - 然后 - 紧凑等同于select-then-map。考虑:地图是一对一的功能。如果要从某些值集映射并映射,则输出集中的每个值的输出集中都需要一个值。如果您必须事先选择,那么您可能不希望在集合上有地图。如果您必须事后选择(或紧凑),那么您可能不希望在集合上有地图。在任何一种情况下,你都需要在整个集合上进行两次迭代,而reduce只需要进行一次。
另外,在英语中,你试图"将一组整数减少为一组偶数整数"。
在你的例子中:
1 | items.map! { |x| process_x url } # [1, 2, 3, 4, 5] => [1, nil, 3, nil, nil] |
除了被
1 | items.select{|x| process_x url} |
就足够了。
如果你想要一个更宽松的拒绝标准,例如,拒绝空字符串以及nil,你可以使用:
1 2 | [1, nil, 3, 0, ''].reject(&:blank?) => [1, 3, 0] |
如果您想进一步拒绝零值(或将更复杂的逻辑应用于流程),您可以传递一个块来拒绝:
1 2 3 4 5 | [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] |
@the Tin Man,很好 - 我不知道这个方法。嗯,绝对紧凑是最好的方法,但仍然可以通过简单的减法完成:
1 2 | [1, nil, 3, nil, nil] - [nil] => [1, 3] |
1 2 3 4 | new_items = items.each_with_object([]) do |x, memo| ret = process_x(x) memo << ret unless ret.nil? end |
在我看来,
Ruby 2.7+
现在有!
Ruby 2.7正在为此目的引入
例如:
1 2 3 | numbers = [1, 2, 5, 8, 10, 13] enum.filter_map { |i| i * 2 if i.even? } # => [4, 16, 20] |
在您的情况下,当块评估为falsey时,只需:
1 | items.filter_map { |x| process_x url } |
这里有一个关于这个主题的很好的阅读,并针对这个问题的一些早期方法提供了一些性能基准:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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) |
希望对某人有用!
完成它的另一种方法如下所示。在这里,我们使用
1 | items.each_with_object([]) {|x, obj| (process x).tap {|r| obj << r unless r.nil?}} |
完整的插图示例:
1 2 3 4 5 6 | 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?}} |
替代方法:
通过查看您调用
1 2 3 4 5 | h = items.group_by {|x| (process x).nil? ?"Bad" :"Good"} #=> {"Bad"=>[1, 2],"Good"=>[3, 4, 5]} h["Good"] #=> [3,4,5] |