Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to map and remove nil values in Ruby

Tags:

ruby

I have a map which 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.

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?

like image 357
Pete Hamilton Avatar asked Nov 21 '12 02:11

Pete Hamilton


People also ask

How do you get rid of nil in Ruby?

Syntax: Array. compact() Parameter: Array to remove the 'nil' value from. Return: removes all the nil values from the array.

How do you remove an empty string from an array in Ruby?

Active Support is a library of “useful” things that support (!) the other parts of Rails. It includes ways to extend the base Ruby classes, such as Hash , String and Integer .

What does != Nil in Ruby mean?

In Ruby, nil is a special value that denotes the absence of any value. Nil is an object of NilClass. nil is Ruby's way of referring to nothing or void.

How do you delete an element from an array in Ruby?

Ruby | Array delete() operation It can also delete a particular element in the array. Syntax: Array. delete() Parameter: obj - specific element to delete Return: last deleted values from the array.


2 Answers

You could use 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 map block, and that block tries to conditionally return values, then you've got code smell and need to rethink your logic.

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, reject the stuff you don't want or select what you do want:

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

I consider using compact to 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.

'Just my $%0.2f.' % [2.to_f/100] 
like image 70
the Tin Man Avatar answered Sep 19 '22 14:09

the Tin Man


Try using reduce or inject.

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

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

I feel deep inside that map then compact is equivalent to select then map. Consider: map is a one-to-one function. If you are mapping from some set of values, and you map, then you want one value in the output set for each value in the input set. If you are having to select before-hand, then you probably don't want a map on the set. If you are having to select afterwards (or compact) then you probably don't want a map on the set. In either case you are iterating twice over the entire set, when a reduce only needs to go once.

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

like image 32
Ziggy Avatar answered Sep 20 '22 14:09

Ziggy