transform keys and values with an array as one of them - ruby

I have the following Ruby hash
a = {
7 => [1469, 2283],
5 => [1469, 5464],
3 => [7561],
6 => [7952, 8114],
4 => []
}
and would like to get the keys that a number shows up in as a value
b = {
1469 => [7,5],
2283 => [7],
5464 => [5]
...
}
How would I do this? I'm sure there's some super slick way of getting it done.

Given:
a = {
7 => [1469, 2283],
5 => [1469, 5464],
3 => [7561],
6 => [7952, 8114],
4 => [1469, 2283],
2 => []
}
Use a default value of a new array in b:
b=Hash.new {|hsh, key| hsh[key] = [] }
a.each{|k,v| v.each{|n| b[n] << k} }
Or include the object creation with each_with_object:
b=a.each_with_object(Hash.new {|h,k| h[k] = []}) { |(k,v), h|
v.each{ |e| h[e] << k } }
Result b is:
{1469=>[7, 5, 4], 2283=>[7, 4], 5464=>[5], 7561=>[3], 7952=>[6], 8114=>[6]}

Using #each_with_object to iterate over the original hash and the values stored in each array and build up a new hash based on it.
a.each_with_object({}) do |(k, v), h|
v.each do |val|
h[val] ||= []
h[val] << k
end
end
Result:
{
1469 => [7, 5],
2283 => [7],
5464 => [5],
7561 => [3],
7952 => [6],
8114 => [6]
}

#dawg's answer is the way I would go
a.each_with_object(Hash.new {|h,k| h[k] = []}) do |(k,v),obj|
v.each{|e| obj[e] << k}
end
But here are a few other options: (a lot of allocations though)
Array#product and Hash#merge
a.each_with_object({}) do |(k,v),obj|
obj.merge!(v.product([[k]]).to_h) {|_,o,v| [*o,*v]}
end
#map then #reduce
a.map {|k,v| v.product([[k]]).to_h }
.reduce {|memo, h| memo.merge(h) {|_,o,n| o.concat(n)}}
This works too (don't ask questions)
a.map {|k,v| v.each_with_object(k)}
.reduce(&:+)
.group_by(&:shift)
.transform_values(&:flatten)

Related

map_values() for Ruby hashes?

I miss a Hash method in Ruby to transform/map only the values of the hash.
h = { 1 => [9,2,3,4], 2 => [6], 3 => [5,7,1] }
h.map_values { |v| v.size }
#=> { 1 => 4, 2 => 1, 3 => 3 }
How do you archive this in Ruby?
Update: I'm looking for an implementation of map_values().
# more examples
h.map_values { |v| v.reduce(0, :+) }
#=> { 1 => 18, 2 => 6, 3 => 13 }
h.map_values(&:min)
#=> { 1 => 2, 2 => 6, 3 => 1 }
Ruby 2.4 introduced the methods Hash#transform_values and Hash#transform_values! with the desired behavoir.
h = { 1=>[9, 2, 3, 4], 2=>[6], 3=>[5, 7, 1] }
h.transform_values { |e| e.size }
#=> {1=>4, 2=>1, 3=>3}
You can monkey-patch the hash class, like this
class Hash
def map_values
map { |k, v|
[k, yield(v)]
}.to_h
end
end
p ({1 => [1,1,1,1], 2 => [2], 3 => [3,3,3]}.map_values { |e| e.size })
You can also use Hash#update for this:
h = { 1 => [9, 2, 3, 4], 2 => [6], 3 => [5, 7, 1] }
h.update(h) { |_, v| v.size }
#=> { 1 => 4, 2 => 1, 3 => 3 }
It replaces all values that have duplicate keys in one hash with that of another, or, if a block is given, with the result of calling the block. You can pass the original hash as the argument to ensure all values are replaced.
Note that this modifies the hash in place! If you want to preserve the original hash, dup it first:
h.dup.update(h) { |_, v| v.size }
#=> { 1 => 4, 2 => 1, 3 => 3 }
h
#=> { 1 => [9, 2, 3, 4], 2 => [6], 3 => [5, 7, 1] }
This will do the trick for you
h = { 1 => [1,1,1,1], 2 => [2], 3 => [3,3,3] }
h.map {|k,v| [k, v.size] }.to_h
No map, just each
h = { 1 => [1,1,1,1], 2 => [2], 3 => [3,3,3] }
h.each{|k,v| h[k] = v.size}
You can achieve this by:
h.map { |a, b| [a, b.size] }.to_h
#=> {1=>4, 2=>1, 3=>3}
Here is one more way to achieve it:
h = { 1 => [1,1,1,1], 2 => [2], 3 => [3,3,3] }
p h.keys.zip(h.values.map(&:size)).to_h
#=> {1=>4, 2=>1, 3=>3}
There's an implementation of this method in the DeepEnumerable library: https://github.com/dgopstein/deep_enumerable/
It's called shallow_map_values:
>> require 'deep_enumerable'
>> h = { 1 => [9,2,3,4], 2 => [6], 3 => [5,7,1] }
>> h.shallow_map_values { |v| v.size }
=> {1=>4, 2=>1, 3=>3}

Concatenating tree key names [duplicate]

This question is the inverse of this question.
Given a nested hash like
{
:a => {
:b => {:c => 1, :d => 2},
:e => 3,
},
:f => 4,
}
what is the best way to convert it into a flat hash like
{
[:a, :b, :c] => 1,
[:a, :b, :d] => 2,
[:a, :e] => 3,
[:f] => 4,
}
Another way:
def flat_hash(h,f=[],g={})
return g.update({ f=>h }) unless h.is_a? Hash
h.each { |k,r| flat_hash(r,f+[k],g) }
g
end
h = { :a => { :b => { :c => 1,
:d => 2 },
:e => 3 },
:f => 4 }
flat_hash(h) #=> {[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}
Very similar to Adiel Mittmann's solution
def flat_hash(h, k = [])
new_hash = {}
h.each_pair do |key, val|
if val.is_a?(Hash)
new_hash.merge!(flat_hash(val, k + [key]))
else
new_hash[k + [key]] = val
end
end
new_hash
end
Edit: Refactored for elegance. Should be almost as fast.
def flat_hash(hash, k = [])
return {k => hash} unless hash.is_a?(Hash)
hash.inject({}){ |h, v| h.merge! flat_hash(v[-1], k + [v[0]]) }
end
My attempt:
def flatten_hash(h)
return { [] => h } unless h.is_a?(Hash)
Hash[h.map { |a,v1| flatten_hash(v1).map { |b,v2| [[a] + b, v2] } }.flatten(1)]
end
Sorry for the bad variables names, had to fit it in one line.
This is not an attempt to give you the best way to do it, but it is a way :P
def flatten(hash)
return {[] => hash} if !hash.is_a?(Hash)
map = {}
hash.each_pair do |key1, value1|
flatten(value1).each_pair do |key2, value2|
map[[key1] + key2] = value2
end
end
return map
end
It works for your example, producing this result:
{[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}
It may not produce the result you expect if there are empty hashes.
A functional approach (see the history for an alternative implementations):
def recursive_flatten(hash)
hash.flat_map do |key, value|
if value.is_a?(Hash)
recursive_flatten(value).map { |ks, v| [[key] + ks, v] }
else
[[[key], value]]
end
end.to_h
end
Inspired by #cary-swoveland way, but in Hash class :
class Hash
def deep_flatten(previous_key=[])
flat_hash = {}
self.each do |key, value|
next_key = previous_key+[key]
flat_hash.update(value.is_a?(Hash) ? value.deep_flatten(next_key) : {next_key=>value})
end
return flat_hash
end
end
h = { :a => { :b => { :c => 1, :d => 2 }, :e => 3 }, :f => 4 }
h.deep_flatten #=> {[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}
A declarative solution using DeepEnumerable:
require 'deep_enumerable'
h = { :a => { :b => { :c => 1, :d => 2 }, :e => 3 }, :f => 4 }
h.deep_each.map do |k, v|
[DeepEnumerable.deep_key_to_array(k), v]
end.to_h
or, for those who prefer point-free style
h.deep_each.to_h.shallow_map_keys(&DeepEnumerable.method(:deep_key_to_array))
Array support / readable names / no update for speed / stringified results keys
def flat_hash(input, base = nil, all = {})
if input.is_a?(Array)
input = input.each_with_index.to_a.each(&:reverse!)
end
if input.is_a?(Hash) || input.is_a?(Array)
input.each do |k, v|
flat_hash(v, base ? "#{base}.#{k}" : k, all)
end
else
all[base] = input
end
all
end

How to merge array of hash based on the same keys in ruby?

How to merge array of hash based on the same keys in ruby?
example :
a = [{:a=>1},{:a=>10},{:b=>8},{:c=>7},{:c=>2}]
How to get result like this?
a = [{:a=>[1, 10]},{:b=>8},{:c=>[7, 2]}]
Try
a.flat_map(&:entries)
.group_by(&:first)
.map{|k,v| Hash[k, v.map(&:last)]}
Another alternative:
a = [{:a=>1},{:a=>10},{:b=>8},{:c=>7},{:c=>2}]
p a.each_with_object({}) { |h, o| h.each { |k,v| (o[k] ||= []) << v } }
# => {:a=>[1, 10], :b=>[8], :c=>[7, 2]}
It also works when the Hashes have multiple key/value combinations, e.g:
b = [{:a=>1, :b=>5, :x=>10},{:a=>10, :y=>2},{:b=>8},{:c=>7},{:c=>2}]
p b.each_with_object({}) { |h, o| h.each { |k,v| (o[k] ||= []) << v } }
# => {:a=>[1, 10], :b=>[5, 8], :x=>[10], :y=>[2], :c=>[7, 2]}
Minor addition to answer by Arie Shaw to match required answer:
a.flat_map(&:entries)
.group_by(&:first)
.map{|k,v| Hash[k, v.size.eql?(1) ? v.last.last : v.map(&:last) ]}
#=> [{:a=>[1, 10]}, {:b=>8}, {:c=>[7, 2]}]
I'd do :
a = [{:a=>1},{:a=>10},{:b=>8},{:c=>7},{:c=>2}]
merged_hash = a.each_with_object({}) do |item,hsh|
k,v = item.shift
hsh[k] = hsh.has_key?(k) ? [ *Array( v ), hsh[k] ] : v
end
merged_hash.map { |k,v| { k => v } }
# => [{:a=>[10, 1]}, {:b=>8}, {:c=>[2, 7]}]
update
A better taste :
a = [{:a=>1},{:a=>10},{:b=>8},{:c=>7},{:c=>2}]
merged_hash = a.each_with_object({}) do |item,hsh|
k,v = item.shift
(hsh[k] ||= []) << v
end
merged_hash.map { |k,v| { k => v } }
# => [{:a=>[10, 1]}, {:b=>8}, {:c=>[2, 7]}]

Given an array, returns a hash

Write a method, which given an array, returns a hash whose keys are words in the array and whose values are the number of times each word appears.
arr=["A", "man", "a", "plan", "a", "canal","Panama"]
# => {'a' => 3, 'man' => 1, 'canal' => 1, 'panama' => 1, 'plan' => 1}
How do I achieve that? Here's my code:
hash={}
arr.each do |i|
hash.each do |c,v|
hash[c]=v+1
end
end
hash = arr.inject({}) do |hash, element|
element.downcase!
hash[element] ||= 0
hash[element] += 1
hash
end
arr.inject­(Hash.new(­0)){|h,k| k.dow­ncase!; h[k] += 1; h}
arr = ["A", "man", "a", "plan", "a", "canal","Panama"]
r = {}
arr.each { |e| e.downcase!; r[e] = arr.count(e) if r[e].nil? }
Output
p r
#==> {"a"=>3, "man"=>1, "plan"=>1, "canal"=>1, "panama"=>1}

Converting a nested hash into a flat hash

This question is the inverse of this question.
Given a nested hash like
{
:a => {
:b => {:c => 1, :d => 2},
:e => 3,
},
:f => 4,
}
what is the best way to convert it into a flat hash like
{
[:a, :b, :c] => 1,
[:a, :b, :d] => 2,
[:a, :e] => 3,
[:f] => 4,
}
Another way:
def flat_hash(h,f=[],g={})
return g.update({ f=>h }) unless h.is_a? Hash
h.each { |k,r| flat_hash(r,f+[k],g) }
g
end
h = { :a => { :b => { :c => 1,
:d => 2 },
:e => 3 },
:f => 4 }
flat_hash(h) #=> {[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}
Very similar to Adiel Mittmann's solution
def flat_hash(h, k = [])
new_hash = {}
h.each_pair do |key, val|
if val.is_a?(Hash)
new_hash.merge!(flat_hash(val, k + [key]))
else
new_hash[k + [key]] = val
end
end
new_hash
end
Edit: Refactored for elegance. Should be almost as fast.
def flat_hash(hash, k = [])
return {k => hash} unless hash.is_a?(Hash)
hash.inject({}){ |h, v| h.merge! flat_hash(v[-1], k + [v[0]]) }
end
My attempt:
def flatten_hash(h)
return { [] => h } unless h.is_a?(Hash)
Hash[h.map { |a,v1| flatten_hash(v1).map { |b,v2| [[a] + b, v2] } }.flatten(1)]
end
Sorry for the bad variables names, had to fit it in one line.
This is not an attempt to give you the best way to do it, but it is a way :P
def flatten(hash)
return {[] => hash} if !hash.is_a?(Hash)
map = {}
hash.each_pair do |key1, value1|
flatten(value1).each_pair do |key2, value2|
map[[key1] + key2] = value2
end
end
return map
end
It works for your example, producing this result:
{[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}
It may not produce the result you expect if there are empty hashes.
A functional approach (see the history for an alternative implementations):
def recursive_flatten(hash)
hash.flat_map do |key, value|
if value.is_a?(Hash)
recursive_flatten(value).map { |ks, v| [[key] + ks, v] }
else
[[[key], value]]
end
end.to_h
end
Inspired by #cary-swoveland way, but in Hash class :
class Hash
def deep_flatten(previous_key=[])
flat_hash = {}
self.each do |key, value|
next_key = previous_key+[key]
flat_hash.update(value.is_a?(Hash) ? value.deep_flatten(next_key) : {next_key=>value})
end
return flat_hash
end
end
h = { :a => { :b => { :c => 1, :d => 2 }, :e => 3 }, :f => 4 }
h.deep_flatten #=> {[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}
A declarative solution using DeepEnumerable:
require 'deep_enumerable'
h = { :a => { :b => { :c => 1, :d => 2 }, :e => 3 }, :f => 4 }
h.deep_each.map do |k, v|
[DeepEnumerable.deep_key_to_array(k), v]
end.to_h
or, for those who prefer point-free style
h.deep_each.to_h.shallow_map_keys(&DeepEnumerable.method(:deep_key_to_array))
Array support / readable names / no update for speed / stringified results keys
def flat_hash(input, base = nil, all = {})
if input.is_a?(Array)
input = input.each_with_index.to_a.each(&:reverse!)
end
if input.is_a?(Hash) || input.is_a?(Array)
input.each do |k, v|
flat_hash(v, base ? "#{base}.#{k}" : k, all)
end
else
all[base] = input
end
all
end

Resources