Ruby: Sum selected hash values - ruby

I've got an array of hashes and would like to sum up selected values. I know how to sum all of them or one of them but not how to select more than one key.
i.e.:
[{"a"=>5, "b"=>10, "active"=>"yes"}, {"a"=>5, "b"=>10, "active"=>"no"}, {"a"=>5, "b"=>10, "action"=>"yes"}]
To sum all of them I using:
t = h.inject{|memo, el| memo.merge( el ){|k, old_v, new_v| old_v + new_v}}
=> {"a"=>15, "b"=>30, "active"=>"yesnoyes"} # I do not want 'active'
To sum one key, I do:
h.map{|x| x['a']}.reduce(:+)
=> 15
How do I go about summing up values for keys 'a' and 'b'?

You can use values_at:
hs = [{:a => 1, :b => 2, :c => ""}, {:a => 2, :b => 4, :c => ""}]
keys = [:a, :b]
hs.map { |h| h.values_at(*keys) }.inject { |a, v| a.zip(v).map { |xy| xy.compact.sum }}
# => [3, 6]
If all required keys have values it will be shorter:
hs.map { |h| h.values_at(*keys) }.inject { |a, v| a.zip(v).map(&:sum) }
# => [3, 6]
If you want Hash back:
Hash[keys.zip(hs.map { |h| h.values_at(*keys) }.inject{ |a, v| a.zip(v).map(&:sum) })]
# => {:a => 3, :b => 6}

I'd do something like this:
a.map { |h| h.values_at("a", "b") }.transpose.map { |v| v.inject(:+) }
#=> [15, 30]
Step by step:
a.map { |h| h.values_at("a", "b") } #=> [[5, 10], [5, 10], [5, 10]]
.transpose #=> [[5, 5, 5], [10, 10, 10]]
.map { |v| v.inject(:+) } #=> [15, 30]

How is this ?
h = [{"a"=>5, "b"=>10, "active"=>"yes"}, {"a"=>5, "b"=>10, "active"=>"no"}, {"a"=>5, "b"=>10, "action"=>"yes"}]
p h.map{|e| e.reject{|k,v| %w(active action).include? k } }.inject{|memo, el| memo.merge( el ){|k, old_v, new_v| old_v + new_v}}
# >> {"a"=>15, "b"=>30}

Related

How many times is item in array? [duplicate]

I have an array:
a = [1, 2, 3, 3, 6, 8, 1, 9]
I want to display each unique element value and its associated element count like this:
1: 2
2: 1
3: 2
6: 1
8: 1
9: 1
So far I have:
a.sort.group_by { |x| x }
{
1 => [
[0] 1,
[1] 1
],
2 => [
[0] 2
],
3 => [
[0] 3,
[1] 3
],
6 => [
[0] 6
],
8 => [
[0] 8
],
9 => [
[0] 9
]
}
So each element of the Hash contains an array. I can use that array's count to get my answer, but I'm having trouble figuring out how to process the hash concisely.
Is this a horrible implementation?
a.sort.group_by { |x| x }.each {|x| puts "#{x[0]} #{x[1].count}" }
How about:
a.inject({}) { |a,e| a[e] = (a[e] || 0) + 1; a }
=> {1=>2, 2=>1, 3=>2, 6=>1, 8=>1, 9=>1}
For example:
h = a.inject({}) { |a,e| a[e] = (a[e] || 0) + 1; a }
=> {1=>2, 2=>1, 3=>2, 6=>1, 8=>1, 9=>1}
h.keys.sort.each { |k| puts "#{k}: #{h[k]}" }
1: 2
2: 1
3: 2
6: 1
8: 1
9: 1
From comments of others below:
a.each_with_object(Hash.new(0)) { |e,a| a[e] += 1 }
=> {1=>2, 2=>1, 3=>2, 6=>1, 8=>1, 9=>1}
Use uniq to get the unique array values and sort to sort them in ascending order. Then for each of these values x, display a.count(x).
a = [1, 2, 3, 3, 6, 8, 1, 9]
a.uniq.sort.each {|x| puts '%d: %d' % [x, a.count(x)] }
For greater efficiency, make a hash that maps a value to the number of times it appears in the array. An easy way to do this is to initialize a Hash object that maps keys to zero by default. Then you can increment each value's count by one as you iterate through the array.
counts = Hash.new(0)
a.each {|x| counts[x] += 1 }
counts.keys.sort.each {|x| puts '%d: %d' % [x, counts[x]] }
Consider this:
a = [1, 2, 3, 3, 6, 8, 1, 9]
a.group_by{ |n| n } # => {1=>[1, 1], 2=>[2], 3=>[3, 3], 6=>[6], 8=>[8], 9=>[9]}
a.group_by{ |n| n }.map{ |k, v| [k, v.size ] } # => [[1, 2], [2, 1], [3, 2], [6, 1], [8, 1], [9, 1]]
Finally:
a.group_by{ |n| n }.map{ |k, v| [k, v.size ] }.to_h # => {1=>2, 2=>1, 3=>2, 6=>1, 8=>1, 9=>1}
Try this
module Enumerable
def freq
hash = Hash.new(0)
each { |each| hash[each] += 1 }
hash
end
end
And then
[1, 2, 3, 3, 6, 8, 1, 9].freq
# => {1=>2, 2=>1, 3=>2, 6=>1, 8=>1, 9=>1}

Merging an array of hashes

I have the following array:
x = [ { a: [1,2] }, { a: [3,4] }, { a: [5,6] } ]
and I need to get
{ a: [[1,2], [3,4], [5,6]] }
I have tried to use (among other options) merge:
x.each_with_object({}) do |a, b|
b.merge!(a) {|k, o, n| o.zip(n) }
end
But unfortunately, I get an extra array around the result.
Any suggestions?
THANKS
x.flat_map(&:to_a).group_by(&:first).map{ |k, v| [k, v.map(&:last)] }.to_h
#=> [{:a=>[[1, 2], [3, 4], [5, 6]]}]
It is maybe not the most efficient way to get the expected result but you can do
h = Hash.new([])
x.each { |hash|
hash.each { |key, values|
h[key] = h[key] + [values]
}
}
That way, at the end h is {:a=>[[1, 2], [3, 4], [5, 6]]}
key = x.first.first.first
#=> :a
{ key=>x.map { |h| h[key] } }
#=> {:a=>[[1, 2], [3, 4], [5, 6]]}
Note
a = x.first
#=> {:a=>[1, 2]}
b = a.first
#=> [:a, [1, 2]]
b.first
#=> :a
Another way:
a = x.map { |h| h.merge(h) { |_,v,_| [v] } }
#=> [{:a=>[[1, 2]]}, {:a=>[[3, 4]]}, {:a=>[[5, 6]]}]
a.reduce { |t,h| t.merge(h) { |_,o,n| o+n } }
#=> {:a=>[[1, 2], [3, 4], [5, 6]]}
Both steps use the form of Hash#merge that employs a block to determine the values of keys that are present in both hashes being merged. See the doc for details.
These two steps could be combined into one as follows:
x.reduce { |t,h| t.merge(h.merge(h) { |_,v,_| [v] }) { |_,o,n| o+n } }
#=> {:a=>[1, 2, [3, 4], [5, 6]]}

Challenge: combine into an array only sequential keys of specific value in Ruby

I want a function that takes parameters like this
list = [{a:1},{a:2},{b:3},{b:4},{c:5},{a:6}]
key = :a
combine_only_sequential_occurances_of_specific_key(list,key)
and would return this
[{a:[1,2]},{b:3},{b:4},{c:5},{a:6}]
Basically, combine a list of key/value pairs that occur sequentially, but limited only to a specific key (or if you like, a set of keys) and preserve order.
Thanks to the power of Enumerable, this is a rather easy task:
def combine_only_sequential_occurances_of_specific_key(list, *keys)
list.
chunk {|h| if keys.include?(k = h.keys.first) then k else :_alone end }.
# split into chunks by key
map {|k, hs| if k == :_alone || hs.size == 1 then hs.first else {k => hs.map(&:values).reduce(:concat)} end}
# transform into hash from key to "sum" (i.e. concatenation) of the values
end
list = [{a: 1}, {a: 2}, {b: 3}, {b: 4}, {c: 5}, {a: 6}]
key = :a
combine_only_sequential_occurances_of_specific_key(list, key)
# => [{a: [1, 2]}, {b: 3}, {b: 4}, {c: 5}, {a: 6}]
Code
def combine_only_blah_blah_blah(list, key)
list.flat_map(&:to_a).
slice_when { |(k1,_),(k2,_)| k1 != k2 }.
flat_map do |a|
k = a.first.first
(a.size > 1 && k == key) ? { k=>a.map(&:last) } : a.map { |b| [b].to_h }
end
end
Example
list = [{a: 1}, {a: 2}, {b: 3}, {b: 4}, {c: 5}, {a: 6}]
key = :a
combine_only_blah_blah_blah(list, key)
#=> [{:a=>[1, 2]}, {:b=>3}, {:b=>4}, {:c=>5}, {:a=>6}]
Explanation
For list and key above, the steps are as follows.
b = list.flat_map(&:to_a)
#=> [[:a, 1], [:a, 2], [:b, 3], [:b, 4], [:c, 5], [:a, 6]]
e = b.slice_when { |(k1,_),(k2,_)| k1 != k2 }
#=> #<Enumerator: #<Enumerator::Generator:0x007f9bda968c50>:each>
We can see what elements will be generated by this enumerator by converting it to an array.
e.to_a
#=> [[[:a, 1], [:a, 2]], [[:b, 3], [:b, 4]], [[:c, 5]], [[:a, 6]]]
Continuing,
e.flat_map do |a|
k = a.first.first
(a.size > 1 && k == key) ? { k=>a.map(&:last) } : a.map { |b| [b].to_h }
end
#=> [{:a=>[1, 2]}, {:b=>3}, {:b=>4}, {:c=>5}, {:a=>6}]
The first element generated by e that is passed to flat_map's block is
a = e.next
#=> [[:a, 1], [:a, 2]]
and the block calculation is as follows.
k = a.first.first
#=> :a
(a.size > 1 && k == key)
#=> (2 > 1 && :a == :a)
#=> true
so
{ k=>a.map(&:last) }
#=> {:a=>[1, 2]}
is executed. The next element generated by e and passed to the block, and the subsequent block calculations are as follows.
a = e.next
#=> [[:b, 3], [:b, 4]]
k = a.first.first
#=> :b
(a.size > 1 && k == key)
#=> (2 > 1 && :b == :a)
#=> false
a.map { |b| [b].to_h }
#=> [{:b=>3}, {:b=>4}]
Note that when
b = [:b, 3]
[b].to_h
#=> [[:b, 3]].to_h
#=> {:b=>3}
For Ruby versions prior to v2.0, when Array#to_h made its debut, use Hash::[].
Hash[[b]]
#=> {:b=>3}

Reverse mapping an array of hash to a hash

I have an Array that contains Hash with only one key and value as anArray.
Eg:
a = [{1 => ["foo", "bar"]}, {2 => ["hello"]}, {3 => ["world", "bar"]}]
Now I want to create a Hash having values of above Hashes as key and their keys as values.
Eg: (desired result)
res = {"foo"=>[1], "bar"=>[1, 3], "hello"=>[2], "world"=>[3]}
I have solved this is following way:
b = Hash.new { |h, k| h[k] = [] }
a.each { |hash|
hash.each { |key, vals|
vals.each { |val|
b[val] << key
}
}
}
b
# => {"foo"=>[1], "bar"=>[1, 3], "hello"=>[2], "world"=>[3]}
It works fine but there should be a better, shorter way to do this than iterating so many times. Please suggest.
One could invert keys and values and then collect elements as needed:
a = [{1 => ["foo", "bar"]}, {2 => ["hello"]}, {3 => ["world", "bar"]}]
a.map(&:invert).inject({}) { |memo,el|
el = el.flatten # hash of size 1 ⇒ array [k ⇒ v]
el.first.each { |k| (memo[k] ||= []) << el.last }
memo
}
#⇒ {
# "bar" => [
# [0] 1,
# [1] 3
# ],
# "foo" => [
# [0] 1
# ],
# "hello" => [
# [0] 2
# ],
# "world" => [
# [0] 3
# ]
# }
Hope it helps.
I'd use group_by, but you need to massage your data:
a.flat_map(&:to_a) # => [[1, ["foo", "bar"]], [2, ["hello"]], [3, ["world", "bar"]]]
.flat_map{|key, values| values.map{|v| [key, v]}} # => [[1, "foo"], [1, "bar"], [2, "hello"], [3, "world"], [3, "bar"]]
.group_by(&:last) # => {"foo"=>[[1, "foo"]], "bar"=>[[1, "bar"], [3, "bar"]], "hello"=>[[2, "hello"]], "world"=>[[3, "world"]]}
.map{|key, values| [key, values.map(&:first)]} # => [["foo", [1]], ["bar", [1, 3]], ["hello", [2]], ["world", [3]]]
.to_h # => {"foo"=>[1], "bar"=>[1, 3], "hello"=>[2], "world"=>[3]}

How to quickly print Ruby hashes in a table format?

Is there a way to quickly print a ruby hash in a table format into a file?
Such as:
keyA keyB keyC ...
123 234 345
125 347
4456
...
where the values of the hash are arrays of different sizes. Or is using a double loop the only way?
Thanks
Try this gem I wrote (prints hashes, ruby objects, ActiveRecord objects in tables): http://github.com/arches/table_print
Here's a version of steenslag's that works when the arrays aren't the same size:
size = h.values.max_by { |a| a.length }.length
m = h.values.map { |a| a += [nil] * (size - a.length) }.transpose.insert(0, h.keys)
nil seems like a reasonable placeholder for missing values but you can, of course, use whatever makes sense.
For example:
>> h = {:a => [1, 2, 3], :b => [4, 5, 6, 7, 8], :c => [9]}
>> size = h.values.max_by { |a| a.length }.length
>> m = h.values.map { |a| a += [nil] * (size - a.length) }.transpose.insert(0, h.keys)
=> [[:a, :b, :c], [1, 4, 9], [2, 5, nil], [3, 6, nil], [nil, 7, nil], [nil, 8, nil]]
>> m.each { |r| puts r.map { |x| x.nil?? '' : x }.inspect }
[:a, :b, :c]
[ 1, 4, 9]
[ 2, 5, ""]
[ 3, 6, ""]
["", 7, ""]
["", 8, ""]
h = {:a => [1, 2, 3], :b => [4, 5, 6], :c => [7, 8, 9]}
p h.values.transpose.insert(0, h.keys)
# [[:a, :b, :c], [1, 4, 7], [2, 5, 8], [3, 6, 9]]
No, there's no built-in function. Here's a code that would format it as you want it:
data = { :keyA => [123, 125, 4456], :keyB => [234000], :keyC => [345, 347] }
length = data.values.max_by{ |v| v.length }.length
widths = {}
data.keys.each do |key|
widths[key] = 5 # minimum column width
# longest string len of values
val_len = data[key].max_by{ |v| v.to_s.length }.to_s.length
widths[key] = (val_len > widths[key]) ? val_len : widths[key]
# length of key
widths[key] = (key.to_s.length > widths[key]) ? key.to_s.length : widths[key]
end
result = ""
data.keys.each {|key| result += key.to_s.ljust(widths[key]) + " " }
result += "\n"
for i in 0.upto(length)
data.keys.each { |key| result += data[key][i].to_s.ljust(widths[key]) + " " }
result += "\n"
end
# TODO write result to file...
Any comments and edits to refine the answer are very welcome.

Resources