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
Related
I'm doing an exercise now where I'm looking for all of the zeros in an array.
The input is:
numbers = [1, 3, 500, 200, 4000, 3000, 10000, 90, 20, 500000]
I want to sort them into a hash by the number of zeros. The expected output is:
expected = {0=>[1, 3], 2=>[500, 200], 3=>[4000, 3000], 4=>[10000], 1=>[90, 20], 5=>[500000]}
I have the structure built but I'm not sure how to count the number of zeros:
grouped = Hash.new {|hash, key| hash[key] = []}
numbers.each do |num|
grouped[num] << num
end
EDITED for clarity:
Any advice would be appreciated. Also, a lot of the advice I read on this recommended converting the array of integers to a string in order to solve the problem. Is there a way to count the number of digits (not just zeros) without converting the array to a string? The expected output in this case would look like:
expected = {1=>[1, 3], 2=>[90, 20], 3=>[500, 200], 4=>[4000, 3000], 5=>[10000], 6=>[500000]
Thanks in advance.
Like many transformations you'll want to do, this one's found in Enumerable.
Grouping by number of digits:
grouped = numbers.group_by { |n| Math.log10(n).to_i + 1 }
# => {1=>[1, 3], 3=>[500, 200], 4=>[4000, 3000], 5=>[10000], 2=>[90, 20], 6=>[500000]}
Grouping by number of zeroes:
grouped = numbers.group_by { |n| n.to_s.match(/0+$/) ? $&.length : 0 }
# => {0=>[1, 3], 2=>[500, 200], 3=>[4000, 3000], 4=>[10000], 1=>[90, 20], 5=>[500000]}
The group_by method is a handy way to convert an Array to a Hash with things organized into pigeon-holes.
I wound up using
grouped = Hash.new {|hash, key| hash[key] = []}
numbers.each do |num|
grouped[num.to_s.count('0')] << num
end
but I really liked the variation in responses. I didn't realize there were so many ways to go about this. Thank you everyone.
If you wish to group non-negative integers by the number of zero digits they contain, you can do this:
def nbr_zeroes(n)
return 1 if n == 0
m = n
i = 0
while m > 0
i += 1 if m % 10 == 0
m /= 10
end
i
end
numbers = [1, 3, 500, 200, 4000, 3000, 10000, 90, 20, 500000]
numbers.group_by { |i| nbr_zeroes(i) }
#=> { 0=>[1, 3], 2=>[500, 200], 3=>[4000, 3000], 4=>[10000] }
numbers = [100000, 100001, 304070, 3500040, 314073, 2000, 314873, 0]
numbers.group_by { |i| nbr_zeroes(i) }
#=> { 5=>[100000], 4=>[100001, 3500040], 3=>[304070, 2000],
# 1=>[314073, 0], 0=>[314873] }
Group by floor of log base 10?
1.9.3-p484 :014 > numbers.each {|n| grouped[Math.log10(n).floor] << n}
=> [1, 3, 500, 200, 4000, 3000, 10000, 90, 20, 500000]
1.9.3-p484 :016 > grouped
=> {0=>[1, 3], 2=>[500, 200], 3=>[4000, 3000], 4=>[10000], 1=>[90, 20], 5=>[500000]}
Or try 1 + Math.log10(n).floor if you need the keys to be the actual number of digits.
I have these hashes:
{"a" => 1, "b" => 2, "c" => 3, "k" => 14}
{"b" => 51, "c" => 2, "d" => 8}
I need to write code, so that after manipulation, the result would be:
{"a" => 1, "b" => 51, "c" => 2, "k" => 14}
I tried:
h1.each do |h, j|
h2.each do |hh, jj|
if h == hh
j = jj
end
end
end
but it doesn't work. Also I think this is ugly code, so how would could it be written better/right?
I though I should compare the two hashes, and, if the second key is the same as the first, change the first hash value to the second hash's value.
Just iterate over the entries in h2 and update the corresponding entry in h1 only if it already exists:
h2.each { |k,v| h1[k]=v if h1.include?(k) }
h1 # => {"a"=>1, "b"=>51, "c"=>2, "k"=>14 }
Also, if you want to update the entries as above and also add new entries from h2 you can simply use the Hash#merge! method:
h1.merge!(h2)
h1 # => {"a"=>1, "b"=>51, "c"=>2, "k"=>14, "d"=>8}
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}
I know for-loops should be avoided and i guess the better way to iterate through an array is instead of doing
for i in 0..array.size-1 do
puts array[i]
end
do
array.each{ |x|
puts x
}
But what if i had an array of hashes like
array = [{:value0 => 1, :value1 => 0}, {:value0 => 2, :value1 => 1}, {:value0 => 1, :value1 => 2}]
and wanted to check if :value0 is unique in all hashes.. intuitively I would do something like
for i in 0..array.size-1 do
_value_buffer = array[i][:value0]
for j in i+1..array.size-1 do
if _value_buffer == array[j][:value0]
puts "whatever"
end
end
end
Is there a better way to do this?
Why not just get all the values in question and see if they’re unique?
!array.map { |h| h[:value0] }.uniq!
(uniq! returns nil when there are no duplicates)
As what Andrew Marshall says, you can use uniq!, but with a more concise way:
!array.uniq!{|a| a[:value0]}
Here is how I would do it:
2.0.0p195 :001 > array = [{:value0 => 1, :value2 => 0}, {:value0 => 2, :value2 => 1}, {:value0 => 1, :value2 => 2}]
=> [{:value0=>1, :value2=>0}, {:value0=>2, :value2=>1}, {:value0=>1, :value2=>2}]
2.0.0p195 :002 > val0 = array.map { |hash| hash[:value0] }
=> [1, 2, 1]
2.0.0p195 :003 > puts val0.uniq == val0
false
=> nil
I would collect the values of :value0 and then compare them to the array of unique values.
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}]