在Ruby中映射和删除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 

但是,这似乎不是ruby般的。 有没有一种很好的方式来运行一个列表中删除/排除尼尔的function,你去?

你可以使用compact

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

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

例如,如果你正在做这样的事情:

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

尝试使用#reduce#inject

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

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

我深深地感到,map-then-compact等同于select-then-map。 考虑一下:地图是一对一的function。 如果您是从一组值中进行映射,然后进行映射,那么您需要为input集中的每个值设置一个输出值。 如果你不得不事先select,那么你可能不需要在该集合上的地图。 如果你不得不select(或紧凑),那么你可能不希望在该集合上的地图。 无论哪种情况,您都要在整个集合上重复执行两次,而只需要执行一次reduce操作。

而且,在英文中,你正试图“将一组整数缩减成一组偶数整数”。

在你的例子中:

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

它看起来不像其他值被replacenil 。 如果是这样的话,那么:

 items.select{|x| process_x url} 

就足够了。

如果你想要一个宽松的拒绝标准,例如,拒绝空string以及零,你可以使用:

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

如果你想进一步去拒绝零值(或者对stream程应用更复杂的逻辑),你可以通过一个块来拒绝:

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

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

还有一种方法可以实现,如下所示。 在这里,我们使用Enumerable#each_with_object来收集值,并利用Object#tap去除临时variables,否则在process_x方法的结果上nil检查。

 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的方法,目前还不清楚在该方法中inputx的目的是什么。 如果我假定你要通过传递一些url来处理x的值,并确定哪些x真的被处理成有效的非零结果 – 那么可能是Enumerabble.group_byEnumerable#map更好。

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