Remove nil and blank string in an array in Ruby - ruby

I am new to Ruby and stuck with this issue. Let's say I have an array like this:
arr = [1, 2, 's', nil, '', 'd']
and I want to remove nil and blank string from it, i.e. final array should be:
arr = [1, 2, 's', 'd']
I tried compact but it gives this:
arr.compact!
arr #=> [1, 2, 's', '', 'd'] doesn't remove empty string.
I was wondering if there's a smart way of doing this in Ruby.

You could do this:
arr.reject { |e| e.to_s.empty? } #=> [1, 2, "s", "d"]
Note nil.to_s => ''.

Since you want to remove both nil and empty strings, it's not a duplicate of How do I remove blank elements from an array?
You want to use .reject:
arr = [1, 2, 's', nil, '', 'd']
arr.reject { |item| item.nil? || item == '' }
NOTE: reject with and without bang behaves the same way as compact with and without bang: reject! and compact! modify the array itself while reject and compact return a copy of the array and leave the original intact.
If you're using Rails, you can also use blank?. It was specifically designed to work on nil, so the method call becomes:
arr.reject { |item| item.blank? }

I tend to do:
arr = [1, 2, 's', nil, '', 'd']
arr.reject(&:blank?)
returns:
=> [1, 2, "s", "d"]

arr.reject(&:blank?)
Just use this, no need to anything else.

compact_blank (Rails 6.1+)
If you are using Rails (or a standalone ActiveSupport), starting from version 6.1, there is a compact_blank method which removes blank values from arrays.
It uses Object#blank? under the hood for determining if an item is blank.
[1, 2, 's', nil, '', 'd'].compact_blank
# => [1, 2, 's', 'd']
[1, "", nil, 2, " ", [], {}, false, true].compact_blank
# => [1, 2, true]
Here is a link to the docs and a link to the relative PR.
A destructive variant is also available. See Array#compact_blank!.

You can also use - to remove all nil and '' elements:
arr -= [nil, '']
#=> [1, 2, "s", "d"]
Demonstration
Or compact and reject with shortcut (in case you are not using Rails where you can just use arr.reject(&:blank?) ):
arr = arr.compact.reject(&''.method(:==))
#=> [1, 2, "s", "d"]
Demonstration

You can use compact with reject
arr = [1, 2, 's', nil, '', 'd']
arr = [1, 2, 's', 'd']
arr = arr.compact.reject { |h| h == "" }
or
arr = arr.compact.delete_if { |h| h == "" }

The simplest and fast way of doing this is :
arr = [1, 2, 's', nil, '', 'd'] - [nil,'']
==> arr = [1, 2, 's', 'd']

You can use compact and delete_if method to remove nil and blank string in an array in Ruby
arr = [1, 2, 's', nil, '', 'd']
arr.compact!.delete_if{|arrVal| arrVal.class == String and arrVal.empty?}
=> [1, 2, "s", "d"]

try this out:
[1, 2, "s", nil, "", "d"].compact.select{|i| !i.to_s.empty?}

Note: I am considering the array might have string with white spaces in it.
You can do:
arr = [1, 2, 's', nil, ' ', 'd']
arr.reject{|a| a.nil? || (a.to_s.gsub(' ', '') == '') }
#=> [1, 2, "s", "d"]
or:
arr.reject{|a| a.nil? || (a.to_s.gsub(' ', '').empty?) }
#=> [1, 2, "s", "d"]
or if you want to update arr object itself then:
arr.reject!{|a| a.nil? || (a.to_s.gsub(' ', '') == '') } # notice the ! mark, it'll update the object itself.
p arr #=> [1, 2, "s", "d"]

Hope this will work for your case :
arr = [1, 2, 's', nil, '', 'd']
arr.select{|x| x.to_s!="" }

I would probably add .strip to eliminate potential whitespace headaches (assuming its not a rails app).
array = [1, 2, "s", nil, " ", "d", "\n"]
array.reject!{|a| a.nil? || (a.to_s.strip.empty?) }
#=> [1, 2, "s", "d"]

Related

Joining multiple ordered arrays in Ruby

Lets say I have this:
a = [1, 2, 3, 4, 5]
b = ['a', 'b', 'c', 'd', 'e']
c = ['ABC', 'DEF', 'GHI', 'JKL', 'MNO']
And I want this:
d = [[1, 'a', 'ABC'], [2, 'b', 'DEF'], ...]
How can I accomplish this in Ruby?
I tried with .zip
r = []
r.zip(a, b, c)
puts r
But didn't work.
You need to do as below :-
a = [1, 2, 3, 4, 5]
b = ['a', 'b', 'c', 'd', 'e']
c = ['ABC', 'DEF', 'GHI', 'JKL', 'MNO']
a.zip(b,c)
# => [[1, "a", "ABC"], [2, "b", "DEF"], [3, "c", "GHI"], [4, "d", "JKL"], [5, "e", "MNO"]]
One thing to remember here - Array#zip returns an array of size, equal to the size of the receiver array object.
# returns an array of size 2, as the same as receiver array size.
[1,2].zip([1,5,7]) # => [[1, 1], [2, 5]]
# below returns empty array, as the receiver array object is also empty.
[].zip([1,2,3,4,5]) # => []
For the same reason as I explained above r.zip(a, b, c) returns [].
[a,b,c].reduce(:zip).map(&:flatten)
d = [a,b,c].transpose
[[1, "a", "ABC"], [2, "b", "DEF"], [3, "c", "GHI"], [4, "d", "JKL"], [5, "e", "MNO"]]

Reposition an element to the front of an array in Ruby

Even coming from javascript this looks atrocious to me:
irb
>> a = ['a', 'b', 'c']
=> ["a", "b", "c"]
>> a.unshift(a.delete('c'))
=> ["c", "a", "b"]
Is there a more legible way placing an element to the front of an array?
Edit my actual code:
if #admin_users.include?(current_user)
#admin_users.unshift(#admin_users.delete(current_user))
end
Maybe this looks better to you:
a.insert(0, a.delete('c'))
Maybe Array#rotate would work for you:
['a', 'b', 'c'].rotate(-1)
#=> ["c", "a", "b"]
This is a trickier problem than it seems. I defined the following tests:
describe Array do
describe '.promote' do
subject(:array) { [1, 2, 3] }
it { expect(array.promote(2)).to eq [2, 1, 3] }
it { expect(array.promote(3)).to eq [3, 1, 2] }
it { expect(array.promote(4)).to eq [1, 2, 3] }
it { expect((array + array).promote(2)).to eq [2, 1, 3, 1, 2, 3] }
end
end
sort_by proposed by #Duopixel is elegant but produces [3, 2, 1] for the second test.
class Array
def promote(promoted_element)
sort_by { |element| element == promoted_element ? 0 : 1 }
end
end
#tadman uses delete, but this deletes all matching elements, so the output of the fourth test is [2, 1, 3, 1, 3].
class Array
def promote(promoted_element)
if (found = delete(promoted_element))
unshift(found)
end
self
end
end
I tried using:
class Array
def promote(promoted_element)
return self unless (found = delete_at(find_index(promoted_element)))
unshift(found)
end
end
But that failed the third test because delete_at can't handle nil. Finally, I settled on:
class Array
def promote(promoted_element)
return self unless (found_index = find_index(promoted_element))
unshift(delete_at(found_index))
end
end
Who knew a simple idea like promote could be so tricky?
Adding my two cents:
array.select{ |item| <condition> } | array
Pros:
Can move multiple items to front of array
Cons:
This will remove all duplicates unless it's the desired outcome.
Example - Move all odd numbers to the front (and make array unique):
data = [1, 2, 3, 4, 3, 5, 1]
data.select{ |item| item.odd? } | data
# Short version:
data.select(&:odd?) | data
Result:
[1, 3, 5, 2, 4]
Another way:
a = [1, 2, 3, 4]
b = 3
[b] + (a - [b])
=> [3, 1, 2, 4]
If by "elegant" you mean more readable even at the expense of being non-standard, you could always write your own method that enhances Array:
class Array
def promote(value)
if (found = delete(value))
unshift(found)
end
self
end
end
a = %w[ a b c ]
a.promote('c')
# => ["c", "a", "b"]
a.promote('x')
# => ["c", "a", "b"]
Keep in mind this would only reposition a single instance of a value. If there are several in the array, subsequent ones would probably not be moved until the first is removed.
In the end I considered this the most readable alternative to moving an element to the front:
if #admin_users.include?(current_user)
#admin_users.sort_by{|admin| admin == current_user ? 0 : 1}
end
If all the elements in the array are unique you can use array arithmetic:
> a = ['a', 'b', 'c']
=> ["a", "b", "c"]
> a -= "c"
=> ["a", "b"]
> a = ["c"] + a
=> ["c", "a", "b"]
Building on above:
class Array
def promote(*promoted)
self - (tail = self - promoted) + tail
end
end
[1,2,3,4].promote(5)
=> [1, 2, 3, 4]
[1,2,3,4].promote(4)
=> [4, 1, 2, 3]
[1,2,3,4].promote(2,4)
=> [2, 4, 1, 3]

How to compact empty elements in an array

Ruby compacts the sequence only if it has nil value, how do I compact "" empty value
Something like this:
a = [1, nil, 2, '', 3]
a.reject{|x| x == '' || x.nil?} # => [1, 2, 3]
A similar way of doing it to Sergio's:
irb(main):006:0> a = [1,nil,2,''] => [1, nil, 2, ""]
irb(main):007:0> a.reject!{|x| x.nil? || x == ''} => [1, 2]
I know this has no Ruby on Rails tag, but should you be using the framework, the best solution IMHO is:
a = [1, nil, 2, '', 3]
a.select(&:present?)
#=> [1, 2, 3]
In plain Ruby I'd go with Sergio's answer or a.select { |x| x.empty? || x.nil? } if the array can only contain Strings and nils.

How do I do element-wise comparison of two arrays?

I have two arrays:
a = [1,2,3]
b = [1,4,3]
Is there an element-wise comparison method in Ruby such that I could do something like this:
a == b
returns:
[1,0,1] or something like [TRUE,FALSE,TRUE].
Here's one way that I can think of.
a = [1, 2, 3]
b = [1, 4, 3]
a.zip(b).map { |x, y| x == y } # [true, false, true]
You can also use .collect
a.zip(b).collect {|x,y| x==y }
=> [true, false, true]
a = [1,2,3]
b = [1,4,3]
a.zip(b).map { |pair| pair[0] <=> pair[1] }
=> [0, -1, 0]
The element-wise comparison is achieved with the zip Ruby Array object method.
a = [1,2,3]
b = [1,4,3]
a.zip(b)
=> [[1, 1], [2, 4], [3, 3]]
You can do something like this to get exactly what you want:
[1,2,3].zip([1,4,3]).map { |a,b| a == b }
=> [true, false, true]
This should do the trick:
array1.zip(array2).map { |a, b| a == b }
zip creates one array of pairs consisting of each element from both arrays at that position. Imagine gluing the two arrays side by side.
Try something like this :
#array1 = ['a', 'b', 'c', 'd', 'e']
#array2 = ['d', 'e', 'f', 'g', 'h']
#intersection = #array1 & #array2
#intersection should now be ['d', 'e'].
Intersection—Returns a new array containing elements common to the two arrays, with no duplicates.
You can even try some of the ruby tricks like the following :
array1 = ["x", "y", "z"]
array2 = ["w", "x", "y"]
array1 | array2 # Combine Arrays & Remove Duplicates(Union)
=> ["x", "y", "z", "w"]
array1 & array2 # Get Common Elements between Two Arrays(Intersection)
=> ["x", "y"]
array1 - array2 # Remove Any Elements from Array 1 that are
# contained in Array 2 (Difference)
=> ["z"]

Basic Array Iteration in Ruby

What's a better way to traverse an array while iterating through another array? For example, if I have two arrays like the following:
names = [ "Rover", "Fido", "Lassie", "Calypso"]
breeds = [ "Terrier", "Lhasa Apso", "Collie", "Bulldog"]
Assuming the arrays correspond with one another - that is, Rover is a Terrier, Fido is a Lhasa Apso, etc. - I'd like to create a dog class, and a new dog object for each item:
class Dog
attr_reader :name, :breed
def initialize(name, breed)
#name = name
#breed = breed
end
end
I can iterate through names and breeds with the following:
index = 0
names.each do |name|
Dog.new("#{name}", "#{breeds[index]}")
index = index.next
end
However, I get the feeling that using the index variable is the wrong way to go about it. What would be a better way?
dogs = names.zip(breeds).map { |name, breed| Dog.new(name, breed) }
Array#zip interleaves the target array with elements of the arguments, so
irb> [1, 2, 3].zip(['a', 'b', 'c'])
#=> [ [1, 'a'], [2, 'b'], [3, 'c'] ]
You can use arrays of different lengths (in which case the target array determines the length of the resulting array, with the extra entries filled in with nil).
irb> [1, 2, 3, 4, 5].zip(['a', 'b', 'c'])
#=> [ [1, 'a'], [2, 'b'], [3, 'c'], [4, nil], [5, nil] ]
irb> [1, 2, 3].zip(['a', 'b', 'c', 'd', 'e'])
#=> [ [1, 'a'], [2, 'b'], [3, 'c'] ]
You can also zip more than two arrays together:
irb> [1,2,3].zip(['a', 'b', 'c'], [:alpha, :beta, :gamma])
#=> [ [1, 'a', :alpha], [2, 'b', :beta], [3, 'c', :gamma] ]
Array#map is a great way to transform an array, since it returns an array where each entry is the result of running the block on the corresponding entry in the target array.
irb> [1,2,3].map { |n| 10 - n }
#=> [ 9, 8, 7 ]
When using iterators over arrays of arrays, if you give a multiple parameter block, the array entries will be automatically broken into those parameters:
irb> [ [1, 'a'], [2, 'b'], [3, 'c'] ].each { |array| p array }
[ 1, 'a' ]
[ 2, 'b' ]
[ 3, 'c' ]
#=> nil
irb> [ [1, 'a'], [2, 'b'], [3, 'c'] ].each do |num, char|
...> puts "number: #{num}, character: #{char}"
...> end
number 1, character: a
number 2, character: b
number 3, character: c
#=> [ [1, 'a'], [2, 'b'], [3, 'c'] ]
Like Matt Briggs mentioned, #each_with_index is another good tool to know about. It iterates through the elements of an array, passing a block each element in turn.
irb> ['a', 'b', 'c'].each_with_index do |char, index|
...> puts "character #{char} at index #{index}"
...> end
character a at index 0
character b at index 1
character c at index 2
#=> [ 'a', 'b', 'c' ]
When using an iterator like #each_with_index you can use parentheses to break up array elements into their constituent parts:
irb> [ [1, 'a'], [2, 'b'], [3, 'c'] ].each_with_index do |(num, char), index|
...> puts "number: #{num}, character: #{char} at index #{index}"
...> end
number 1, character: a at index 0
number 2, character: b at index 1
number 3, character: c at index 2
#=> [ [1, 'a'], [2, 'b'], [3, 'c'] ]
each_with_index leaps to mind, it is a better way to do it the way you are doing it. rampion has a better overall answer though, this situation is what zip is for.
This is adapted from Flanagan and Matz, "The Ruby Programming Language", 5.3.5 "External Iterators", Example 5-1, p. 139:
++++++++++++++++++++++++++++++++++++++++++
require 'enumerator' # needed for Ruby 1.8
names = ["Rover", "Fido", "Lassie", "Calypso"]
breeds = ["Terrier", "Lhasa Apso", "Collie", "Bulldog"]
class Dog
attr_reader :name, :breed
def initialize(name, breed)
#name = name
#breed = breed
end
end
def bundle(*enumerables)
enumerators = enumerables.map {|e| e.to_enum}
loop {yield enumerators.map {|e| e.next} }
end
bundle(names, breeds) {|x| p Dog.new(*x) }
+++++++++++++++++++++++++++++++++++++++++++
Output:
#<Dog:0x10014b648 #name="Rover", #breed="Terrier">
#<Dog:0x10014b0d0 #name="Fido", #breed="Lhasa Apso">
#<Dog:0x10014ab80 #name="Lassie", #breed="Collie">
#<Dog:0x10014a770 #name="Calypso", #breed="Bulldog">
which I think is what we wanted!
As well as each_with_index (mentioned by Matt), there's each_index. I sometimes use this because it makes the program more symmetrical, and therefore wrong code will look wrong.
names.each_index do |i|
name, breed = dogs[i], breeds[i] #Can also use dogs.fetch(i) if you want to fail fast
Dog.new(name, breed)
end

Resources