Ruby way of summing up dictionary values - ruby

I have something like this:
a = [{"group_id" => 1, "student_id" => 3, "candies" => 4},
{"group_id" => 2, "student_id" => 1, "candies" => 3},
{"group_id" => 1, "student_id" => 2, "candies" => 2},
{"group_id" => 3, "student_id" => 4, "candies" => 6},
{"group_id" => 1, "student_id" => 5, "candies" => 1},
{"group_id" => 3, "student_id" => 6, "candies" => 1},
{"group_id" => 4, "student_id" => 8, "candies" => 3}]
I have three groups of students and each student gets a certain number of candies. I wish to count the total number of candies in a group. For that, I need to know the students which belong to a certain group and accumulate their candy count. I can do it using loops and initializing counts to zero:
aa = a.group_by { |a| a["group_id"] }
# =>
{
1 => [
{"group_id"=>1, "student_id"=>3, "candies"=>4},
{"group_id"=>1, "student_id"=>2, "candies"=>2},
{"group_id"=>1, "student_id"=>5, "candies"=>1}
],
2 => [{"group_id"=>2, "student_id"=>1, "candies"=>3}],
3 => [
{"group_id"=>3, "student_id"=>4, "candies"=>6},
{"group_id"=>3, "student_id"=>6, "candies"=>1}
],
4 => [{"group_id"=>4, "student_id"=>8, "candies"=>3}]
}
But I'm not able to accumulate the values within the group_id. I wonder if there are any succinct ways of representing it. How do I sum the total number of candies that are present in a group?

The first your step (grouping) is correct. After that you can use the following:
a.group_by {|g| g['group_id']}.map do |g, students|
{group_id:g, candies:students.map {|st| st['candies']}.inject(&:+)}
end
map function is often used with collections instead of loops to make some operation on each element and return modified version of the collection.
Output:
[{:group_id=>1, :candies=>7},
{:group_id=>2, :candies=>3},
{:group_id=>3, :candies=>7},
{:group_id=>4, :candies=>3}]

Adding to #StasS answer, a more direct hash way to do (with a more cryptic code) is like this:
> Hash[a.group_by{|g| g['group_id']}.map{|g,s| [g, s.inject(0){|a,b| a + b["candies"]}]}]
=> {1=>7, 2=>3, 3=>7, 4=>3}
you can unfold the line like this:
groups = a.group_by{|g| g['group_id']}
id_candies_pairs = groups.map{|g,s| [g, s.inject(0){|a,b| a + b["candies"]}]}
id_candies_hash = Hash[id_candies_pairs]
return id_candies_hash

Riffing on the answer by #StasS, you can also just build a simpler looking hash like:
totals_by_group_id = {}
a.group_by {|g| g['group_id']}.map do |g, students|
totals_by_group_id[g] = students.map {|st| st['candies']}.inject(&:+)
end
The resulting totals_by_group_id hash is:
{1=>7, 2=>3, 3=>7, 4=>3}

Related

Sort hash by array

I have a hash and an array with same length like the following:
h = {:a => 1, :b => 2, :c => 3, :d => 4}
a = [2, 0, 1, 0]
I want to order the hash in increasing order of the values in the array. So the output would be something like:
h = {:b => 2, :d => 4, :c=> 3, :a => 1}
Ideally I want to introduce some randomness for ties. For the previous example, I want either the previous output or:
h = {:d => 4, :b => 2, :c=> 3, :a => 1}
This is what I tried.
b = a.zip(h).sort.map(&:last)
p Hash[b]
# => {:b=>2, :d=>4, :c=>3, :a=>1}
But I am not sure how to introduce the randomness.
h.to_a.sort_by.each_with_index{|el,i| [a[i], rand]}.to_h
You could modify what you have slightly:
def doit(h,a)
Hash[a.zip(h).sort_by { |e,_| [e,rand] }.map(&:last)]
end
doit(h,a) #=> { b=>2, d=>4, c=>3, a=>1 }
doit(h,a) #=> { d=>4, b=>2, c=>3, a=>1 }
doit(h,a) #=> { b=>2, d=>4, c=>3, a=>1 }
doit(h,a) #=> { b=>2, d=>4, c=>3, a=>1 }

Ruby - extracting the unique values per key from an array of hashes

From a hash like the below one, need to extract the unique values per key
array_of_hashes = [ {'a' => 1, 'b' => 2 , 'c' => 3} ,
{'a' => 4, 'b' => 5 , 'c' => 3},
{'a' => 6, 'b' => 5 , 'c' => 3} ]
Need to extract the unique values per key in an array
unique values for 'a' should give
[1,4,6]
unique values for 'b' should give
[2,5]
unique values for 'c' should give
[3]
Thoughts ?
Use Array#uniq:
array_of_hashes = [ {'a' => 1, 'b' => 2 , 'c' => 3} ,
{'a' => 4, 'b' => 5 , 'c' => 3},
{'a' => 6, 'b' => 5 , 'c' => 3} ]
array_of_hashes.map { |h| h['a'] }.uniq # => [1, 4, 6]
array_of_hashes.map { |h| h['b'] }.uniq # => [2, 5]
array_of_hashes.map { |h| h['c'] }.uniq # => [3]
This is more generic:
options = {}
distinct_keys = array_of_hashes.map(&:keys).flatten.uniq
distinct_keys.each do |k|
options[k] = array_of_hashes.map {|o| o[k]}.uniq
end

Accessing Array of Hashes

I am trying to access a value from an array of hash. An example array looks like this:
family = [
[
{ "Homer" => 1, "Marge" => 2, "Lisa" => 3, "Maggie" => 4,
"Abe" => 5, "Santa's Little Helper" => 6
}
],
[
{ "Homer" => 2, "Marge" => 4, "Lisa" => 6,
"Maggie" => 8, "Abe" => 10, "Santa's Little Helper" => 12
}
]
]
If I try to access the hash value for key "Homer" in array indexed 0 (family[0]) using the statement below and hoping to get the value 1:
family[0]["Homer"]
I get an error which says
"test.rb:4:in `[]': can't convert String into Integer (TypeError)"
Any suggestions on how one might be able to access a hash value in such an array, in a simple statement?
You should try family[0][0]["Homer"].
In your case family[0] gives you :
[{ "Homer" => 1, "Marge" => 2, "Lisa" => 3, "Maggie" => 4, "Abe" => 5,"Santa Little Helper" => 6}]
which is an array. The hash you want is inside it and can be got with family[0][0] :
{ "Homer" => 1, "Marge" => 2, "Lisa" => 3, "Maggie" => 4, "Abe" => 5,"Santa Little Helper" => 6}
So you can now use family[0][0]["Homer"] which will give you the value 1.
The array indices are always numeric values. If you get a can't convert String into Integer (TypeError) error message an exception is thrown because you are trying to access array element using a string that can't be converted to an integer.
You don't actually have an array of hashes. You have an array of arrays of hashes.
Your error occurs because you dereference your structure with [0] which gives you the first array of hashes, now you try to access the key 'homer' which doesnt exist because arrays are keyed by integers.
Here is an example of how you could see all of the values, see if you can get 'homer' on your own:
family.each do |a| # this is an array of arrays of hashes
a.each do |h| # this is an array of hashes
h.each do |k,v| # this is a hash
puts "#{k} => #{v}"
end
end
end
Output:
Homer => 1
Marge => 2
Lisa => 3
Maggie => 4
Abe => 5
Santa's Little Helper => 6
Homer => 2
Marge => 4
Lisa => 6
Maggie => 8
Abe => 10
Santa's Little Helper => 12
#Arup Rakshit is absolutely correct about how to get your value. But you should also know that you don't have an array of hashes, you have an array of arrays, and those sub-arrays contain hashes. Based on your title I'm concluding that you probably want a structure more like
family = [
{ "Homer" => 1, "Marge" => 2, "Lisa" => 3, "Maggie" => 4,
"Abe" => 5, "Santa's Little Helper" => 6
},
{ "Homer" => 2, "Marge" => 4, "Lisa" => 6,
"Maggie" => 8, "Abe" => 10, "Santa's Little Helper" => 12
}
]

wired ruby hash each function

Well I've tried this for many times and tried to figure out what'd happened...
Suppose I wish to sum the amount of a bunch of objects in hash:
orders = [{:price => 100, :qty => 5}, {:price => 120, :qty => 10}, {:price => 50, :qty => 5}]
I want to sum every hash object in the array items as above....
Assuming variable "sum" is the result:
sum = 0
sum = orders.each {|i| sum += i[:price] * i[:qty]}
but it returns the same hash object:
[{:price => 100, :qty => 5}, {:price => 120, :qty => 10}, {:price => 50, :qty => 5}]
I thought the result should be 0 + (100 * 5) + (120 * 10) + (50 * 5)
why is it so?
my solution for this is by doing the following:
sum = 0
total = []
orders.each {|i| total << i[:price] * i[:qty]}
total.each {|i| sum += i}
I think it is not intuitive at all
Because Hash#each returns itself, if block is given. See docs here: http://www.ruby-doc.org/core-1.9.3/Hash.html#method-i-each
The right way to do what you want is
sum = orders.map {|o| o[:price] * o[:qty] }.inject(0, :+)
Yes, Enumerable#each returns a collection it enumerated. You typically don't use its return value at all. You already are modifying sum in the block, so you're good.
sum = 0
orders.each {|i| sum += i[:price] * i[:qty]}
A better way would be to use a specialized method for this
orders = [{:price => 100, :qty => 5}, {:price => 120, :qty => 10}, {:price => 50, :qty => 5}]
sum = orders.reduce(0) do |memo, item|
memo + item[:price] * item[:qty]
end
sum # => 1950

Ruby 1.8.7: group_by with sum in an enumerable type

I have an array of records that is laid out in the following structure:
[{"some_id" => 2, "some_total => 250}, {"some_id" => 2, "some_total" => 100}, {"some_id" => 3, "some_total" => 50}, {"some_id" => 3, "some_total" => 50}, {"some_id" => 3, "some_total" => 25}, {"some_id" => 1, "some_total" => 10}]
What's the best way using Ruby's group_by/inject/sum or whatever is available with Enumerable, to have that return an ordered array of hashes, where each hash is keyed by "some_id" and the value is the sum of all that id's "some_total" ordered by the id with the highest total at the beginning of the array? The results would look like the following:
[{"some_id" => 2, "sum" => 350},
{"some_id" => 3, "sum => 125},
{"some_id" => 1, "sum" => 10}]
Functional approach:
hs.group_by { |h| h["some_id"] }.map do |id, hs|
sum = hs.map { |h| h["some_total"] }.inject(:+)
{:some_id => id, :sum => sum}
end.sort_by { |h| -h[:sum] }
#=> [{:some_id=>2, :sum=>350},
# {:some_id=>3, :sum=>125},
# {:some_id=>1, :sum=>10}]

Resources