Remove/delete object from array in Ruby based on attribute value - ruby-on-rails

I have an array
result = [{:cluster=>[0, 4], :id=>1, :units=>346, :num1=>0.161930681e7, :num2=>0.14223512616e9, "description"=>"Foo"}, { ...
And I want to take out any objects with number of units equal to 0. Looking for something similar to array.splice() but for Ruby
What is the best way to do this ?

You can use the #reject method to return the array without objects whose :units equals 0:
result.reject { |hash| hash[:units] == 0 }
There's also #reject! and #delete_if, which can be used in the same way as above but both modify the array in place.
Hope this helps!

You can achieve the same result using select method implemented on the Array class:
result.select { |el| el[:units] == 0 }

Related

Is there a way to check if a hash value in Ruby is the same throughout or compare only the values for equality?

I have the below code that returns the number of instances of an item in an array as a hash. I now need to check if the value is the same. For example if the hash is like this = {1=>3, 2=>3} i need to check if the value is the same, in this case it is but dont know how to check this.
arr.inject(Hash.new(0)) {|number,index| number[index] += 1 ;number}
Thanks
So, given h = { 1 => 3, 2 => 3 }, if I got you, you want to know if the values are ALL the same. If you knew the keys you could do
all_the_same = h[1] == h[2]
If there are more keys you want to check
all_the_same = h.values_at(1, 2, 3, 4).uniq.length == 1
If you don't know how many keys you have or which are these keys you could do
all_the_same = h.values.uniq.length == 1

Check if multiple (more than two) arrays match, regardless of element order in ruby

If I have two arrays a and b. I can compare them as a.uniq.sort == b.uniq.sort. This will tell me if the elements in a match those in b.
What if I have four arrays a, b, c, d? I need to make sure they are all equal to each other. What is the best way to do this?
[%w[a b c], %w[c b a], %w[a a b c], %w[b a b c]]
.group_by{|a| a.uniq.sort}.one? # => true
[%w[a b c], %w[c b a], %w[a a b c], %w[b a b c d]]
.group_by{|a| a.uniq.sort}.one? # => false
Take one array, sort it's unique elements and compare the other arrays with that as reference. Return false as soon as one is not equal.
ars = [a,b,c,d]
ref = ars.pop.uniq.sort #take care: pop mutates ars
p ars.all?{|ar| ar.uniq.sort == ref}
Here's a way that doesn't involve sorting.
Let arr be an array of the given arrays. If the arrays contained no duplicates, this would be simple:
first, *rest = arr
rest.all? { |a| (first-a).empty? && (a-first).empty? }
To deal with duplicates, we define a helper Array#difference:
class Array
def difference(other)
h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
reject { |e| h[e] > 0 && h[e] -= 1 }
end
end
In a nutshell, if n elements of an array a equal a given object and m elements of an array b equal the same object, the array a.difference(b) will contain (the first) [n-m,0].max elements of a that equal the object .
Then we can write the following.
first, *rest = arr
rest.all? { |a| first.difference(a).empty? && a.difference(first).empty? }
The block evaluates true if every element of a maps to a unique element of first, and vice-versa.
It may seem a waste of time to create Array#difference for just this one problem. I have found, however, it to be a valuable member of my tool kit, having wide application, so much so that I proposed it be added to the Ruby core. The link gives examples of its uses and also contains a link to an SO answer I gave that contains a longer list of problems where it has found application.
There's a nice reduce solution:
ars.reduce { |a, v| a.uniq.sort == v.uniq.sort ? v.uniq.sort : break }
Which ends up to pretty clean code used in conjunction with map:
ars.map { |v| v.uniq.sort }.reduce { |a, v| a == v ? v : break }
The map -> reduce version is not the the best for performance since all the array elements get uniq.sort'd, but it's readable and functional style.
Since you use uniq you can use intersection, without the uniq intersection couldn't be used to compare arrays.
arr.inject(:&).sort == arr.first.uniq.sort
This would mean that arrays like
[3,2,3,1]
[1,3,2]
[3,2,1]
[2,1,3,1]
would be the same
You can try:
a.uniq.sort == b.uniq.sort and b.uniq.sort == c.uniq.sort and c.uniq.sort == d.uniq.sort
To optimise it, you can store the result of b.uniq.sort and c.uniq.sort in some variable to prevent repeated computation before comparing.

Is there a way to apply multiple method by one liner in ruby?

There is an array like this:
a = [1,2,3,4]
I want to get the return values of size and sum like this.
size = a.size
sum = a.sum
Is there a way to get both values by a one-liner like this?
size, sum = a.some_method(&:size, &:sum)
In Ruby, you can do multiple assignments in one line:
size, sum = a.size, a.sum
It doesn't make it more readable, though.
You could do this:
a = [1,2,3,4]
methods = [:size, :max, :min, :first, :last]
methods.map { |m| a.send m }
#=> [4, 4, 1, 1, 4]
Another possible solution:
size, sum = a.size, a.reduce { |a,b| a = a + b }
Previous answers are correct, but if OP was actually concerned about walking the array multiple times, then array.size does not walk the array, it merely returns the length, thus there is no saving from a oneliner in that regard.
On the other hand, if size was just an example and the question is more about making multiple operations on an array in one go, then try something like this:
arr = [1,2,3,4,5,6]
product,sum = arr.inject([1,0]){|sums,el| [sums[0]*el, sums[1]+el]}
# => [720, 21]
That is, inject the array with multiple initial values for the results and then calculate new value for every element.

Rails active record maintain order from array after find

I'm passing find an array of ids, and i'd like to keep the objects in the same order i pass. I assume the order they get set is whatever the primary order set to in the model.
order of array
just items = [488800, 489404, 485616, 380112, 501101, 485606, 485612, 485619, 480304, 493609, 496200, 496203, 503000, 499111, 488802, 488825, 501700]
Order of what active record gives
#item_recomendations = CatalogItem.find(just_items)
#item_recomendations.map {|x| x.id }
=> [380112, 480304, 485606, 485612, 485616, 485619, 488800, 488802, 488825, 489404, 493609, 496200, 496203, 499111, 501101, 501700, 503000]
#item_recomendations = CatalogItem.find(just_items).sort_by{|x| just_items.index x.catalog_item_id }
If performance isn't an issue (and you should try this and benchmark first to prove it is), just iterate over the array yourself:
items.collect { |id| CatalogItem.find(id) }

How do you add an item into an array if passes the logical statement?

I would like to create a new array B_array based on an existing array A A_array. If that item in A_array has a certain field then add it into B_array.
Currently this is what I have and its putting everything into B_array:
B_array = A_array.map {|item| if item.name == 'Josh'}
A_array:
[id:0,name:"Josh",email:"josh#josh#gmail.com"],
[id:1,name:"Scott",email:"scott#josh#gmail.com"],
[id:2,name:"Josh",email:"dan#josh#gmail.com"]
Desired output for B_array:
[id:0,name:"Josh",email:"josh#josh#gmail.com"],
[id:2,name:"Josh",email:"dan#josh#gmail.com"]
Thanks!
Use .select:
a = [{id:0,name:"Josh",email:"josh#josh#gmail.com"},
id:1,name:"Scott",email:"scott#josh#gmail.com"}]
b = a.select { |i| i[:name] == 'Josh' }
.select will filter based on a condition you give it and return the array of elements that pass the test.

Resources