Getting data from an array of hashes in ruby - ruby

I get an array of hashes from a google sheet, it looks like this
itemList = [ {:id => '1', :type => 'A', :category => 'Cat1' },
{:id => '2', :type => 'A', :category => 'Cat1' },
{:id => '3', :type => 'B', :category => 'Cat1' },
{:id => '4', :type => 'B', :category => 'Cat1' },
{:id => '1', :type => 'A', :category => 'Cat2' },
{:id => '2', :type => 'A', :category => 'Cat2' },
{:id => '3', :type => 'C', :category => 'Cat2' } ]
I would like to be able to print this on the terminal
Cat1
A
1, 2
B
3, 4
Cat2
A
1, 2
C
3
Is there an easy way to do it?
Thank you

Following will provide you required format,
items = itemList.group_by {|x| x[:category] }
val = items.inject({}) do |m,(k,v)|
tmp = v.group_by { |x| x[:type] }
m[k] = tmp.update(tmp) { |i,j| j.map { |x| x[:id] } }
m
end
# => {"Cat1"=>{"A"=>["1", "2"], "B"=>["3", "4"]}, "Cat2"=>{"A"=>["1", "2"], "C"=>["3"]}}
Display it like below,
val.each { |k,v| puts k; v.each { |i,j| puts i; puts j.join(', ') }; puts }
Cat1
A
1, 2
B
3, 4
Cat2
A
1, 2
C
3

Related

How to find the most frequent value in array of hashes

I have the array of objects "orders". I want to obtain a first most frequent value in the my array, who often takes a book:
orders = [
{'book' => '1', 'reader' => 'Denis' },
{'book' => '2', 'reader' => 'Mike' },
{'book' => '3', 'reader' => 'Denis' },
{'book' => '3', 'reader' => 'Mike' },
{'book' => '5', 'reader' => '2' }
]
I tried this method, but it's good only for arrays of strings: ['string', 'string'...]:
def most_common_value(a)
a.group_by(&:itself).values.max_by(&:size).first
end
Expected result:
=> "Denis"
I'd do it like this:
orders = [
{'book' => '1', 'reader' => 'Denis' },
{'book' => '2', 'reader' => 'Mike' },
{'book' => '3', 'reader' => 'Denis' },
{'book' => '3', 'reader' => 'Mike' },
{'book' => '5', 'reader' => '2' }
]
orders.each_with_object(Hash.new(0)) { |order, hash| hash[order['reader']] += 1 }.max_by { |k, v| v }
# => ["Denis", 2]
The problem with this is, if there are multiple "max" then the result will be returned based on the order the data is found. For instance, if the order is different:
orders.push(orders.shift)
# => [{"book"=>"2", "reader"=>"Mike"},
# {"book"=>"3", "reader"=>"Denis"},
# {"book"=>"3", "reader"=>"Mike"},
# {"book"=>"5", "reader"=>"2"},
# {"book"=>"1", "reader"=>"Denis"}]
the result changes:
orders.each_with_object(Hash.new(0)) { |order, hash| hash[order['reader']] += 1 }.max_by { |k, v| v }
# => ["Mike", 2]
key = 'reader'
x = orders.inject({}) do |a,i|
a[i[key]] = 0 unless a.has_key? i[key]
a[i[key]] +=1
a
end.max_by{|k,v| v}
Which returns:
=> ["Denis", 2]
I saw this a few days ago and thought this would be easy if Array or Enumerable had a mode_by! Well I finally got around to whipping one up.
Implementing mode_by
A true mode_by would probably return a subarray of the items matching a block:
orders.mode_by{|order| order['reader']}
#=> [{'book'=>'1', 'reader'=>'Denis'}, {'book'=>'3', 'reader'=>'Denis'}]
and to get your desired result:
orders.mode_by{|order| order['reader']}.first['reader']
#=> "Denis"
So let's implement a mode_by:
class Array
def mode_by(&block)
self.group_by(&block).values.max_by(&:size)
end
end
et voila!
A custom alternative
In your case, you don't need to return array elements. Let's simplify further by implementing something that returns exactly what you want, the first result of the block that appears the most. We'll call it mode_of:
class Array
def mode_of(&block)
self.group_by(&block).max_by{|k,v| v.size}.first
end
end
Now you can simply do this:
orders.mode_of{|order| order['reader']}
#=> "Denis"
using group_by and max_by :
orders.group_by { |h| h['reader']}.to_a.max_by {|x| x[1].length}.first
Output :
=> "Denis"

Compare two arrays of hashes and return new object

I have two arrays of hashes.
burgers = [
{:id => 1, :name => "cheese burger"},
{:id => 2, :name => "royale"},
{:id => 3, :name => "big mac"},
{:id => 4, :name => "angus beef"}
]
eaten = [
{:burger_id => 1},
{:burger_id => 2}
]
I would like to return an array or uneaten burgers, where burgers[:id] does not equal eaten[:burger_id]. In burgers_not_eaten_method, I have the expected return value.
def burgers_not_eaten
#Not sure how to compare burger[:id] with eaten[:burger_id]
burgers.reject { |burger| burger[:id] == #eaten burger_id }
# Expected: [{:id => 3, :name => "big mac"},{:id => 4, :name => "angus beef"}]
end
You're close, to make it easy I'd snag all the "eaten" ids into an array, and check for inclusion in that array, like so:
BURGERS = [
{:id => 1, :name => "cheese burger"},
{:id => 2, :name => "royale"},
{:id => 3, :name => "big mac"},
{:id => 4, :name => "angus beef"}
]
EATEN = [
{:burger_id => 1},
{:burger_id => 2}
]
def burgers_not_eaten
eaten_ids = EATEN.map { |e| e[:burger_id] }
BURGERS.reject { |burger| eaten_ids.include?(burger[:id]) }
end
burgers_not_eaten
# => [{:id=>3, :name=>"big mac"}, {:id=>4, :name=>"angus beef"}]

Find keep duplicates in Ruby hashes

I have an array of hashes where I need to find and store matches based on one matching value between the hashes.
a = [{:id => 1, :name => "Jim", :email => "jim#jim.jim"},
{:id => 2, :name => "Paul", :email => "paul#paul.paul"},
{:id => 3, :name => "Tom", :email => "tom#tom.tom"},
{:id => 1, :name => "Jim", :email => "jim#jim.jim"},
{:id => 5, :name => "Tom", :email => "tom#tom.tom"},
{:id => 6, :name => "Jim", :email => "jim#jim.jim"}]
So I would want to return
b = [{:id => 1, :name => "Jim", :email => "jim#jim.jim"},
{:id => 3, :name => "Tom", :email => "tom#tom.tom"},
{:id => 5, :name => "Tom", :email => "tom#tom.tom"},
{:id => 6, :name => "Jim", :email => "jim#jim.jim"}]
Notes: I can sort the data (csv) by :name after the fact so they don't have to be nicely grouped, just accurate. Also it's not necessary two of the same, it could be 3 or 10 or more.
Also, the data is about 22,000 rows.
I tested this and it will do exactly what you want:
b = a.group_by { |h| h[:name] }.values.select { |a| a.size > 1 }.flatten
However, you might want to look at some of the intermediate objects produced in that calculation and see if those are more useful to you.

hash containing an array to array of hashes in ruby

I've read through quite a few of posts, but none seem to do just this, which is a bit tricky.
Say I have a hash that contains an array as one of its values.
hash = {
:a => 'one',
:arr => [
{:id => 'ten', :amount => 10, :b => 'two'},
{:id => 'twenty', :amount => 20, :b => 'two'},
{:id => 'apple', :amount => 7, :b => 'applesauce'}
],
:c => 3
}
I want to convert this to an array of hashes (which would be of the size of the contained array), as follows:
# => [
{:a => 'one', :id => 'ten', :amount => 10, :b => 'two', :c => 3},
{:a => 'one', :id => 'twenty', :amount => 20, :b => 'two', :c => 3},
{:a => 'one', :id => 'apple', :amount => 7, :b => 'applesauce', :c => 3}
]
The conversion should maintain whatever key/value pairs are inside and outside the array, and ideally I could pass in the key of the array to ask it perform the action:
flatten_hash_array(hash, :arr)
I realize that the Ruby flatten in the Array class is not what we need. Grasping for a verb! Any help would be appreciated.
This should do the job, barring validity checks.
def flatten_hash_array(hash, key)
hash[key].map {|entry| entry.merge(hash.reject {|k| k == key})}
end

ruby array includes an id

I currently want to iterate over an array of Objects (2 properties: id & name) and check if the array contains a specific Id
How would I do this?
Enumerable#detect is ok, but I think that Enumerable#any? (which returns a boolean), is strictly what you asked for:
xs = [{:id => 1, :name => 'a'}, {:id => 2, :name => 'b'}]
puts xs.any? {|x| x[:id] == 1} # true
puts xs.any? {|x| x[:id] == 5} # false
Try detect
a = [{:id => 1, :name => 'a'}, {:id => 2, :name => 'b'}]
puts a.detect {|x| x[:id] == 1}

Resources