Merge hash of arrays into array of hashes - ruby

So, I have a hash with arrays, like this one:
{"name": ["John","Jane","Chris","Mary"], "surname": ["Doe","Doe","Smith","Martins"]}
I want to merge them into an array of hashes, combining the corresponding elements.
The results should be like that:
[{"name"=>"John", "surname"=>"Doe"}, {"name"=>"Jane", "surname"=>"Doe"}, {"name"=>"Chris", "surname"=>"Smith"}, {"name"=>"Mary", "surname"=>"Martins"}]
Any idea how to do that efficiently?
Please, note that the real-world use scenario could contain a variable number of hash keys.

Try this
h[:name].zip(h[:surname]).map do |name, surname|
{ 'name' => name, 'surname' => surname }
end

I suggest writing the code to permit arbitrary numbers of attributes. It's no more difficult than assuming there are two (:name and :surname), yet it provides greater flexibility, accommodating, for example, future changes to the number or naming of attributes:
def squish(h)
keys = h.keys.map(&:to_s)
h.values.transpose.map { |a| keys.zip(a).to_h }
end
h = { name: ["John", "Jane", "Chris"],
surname: ["Doe", "Doe", "Smith"],
age: [22, 34, 96]
}
squish(h)
#=> [{"name"=>"John", "surname"=>"Doe", "age"=>22},
# {"name"=>"Jane", "surname"=>"Doe", "age"=>34},
# {"name"=>"Chris", "surname"=>"Smith", "age"=>96}]
The steps for the example above are as follows:
b = h.keys
#=> [:name, :surname, :age]
keys = b.map(&:to_s)
#=> ["name", "surname", "age"]
c = h.values
#=> [["John", "Jane", "Chris"], ["Doe", "Doe", "Smith"], [22, 34, 96]]
d = c.transpose
#=> [["John", "Doe", 22], ["Jane", "Doe", 34], ["Chris", "Smith", 96]]
d.map { |a| keys.zip(a).to_h }
#=> [{"name"=>"John", "surname"=>"Doe", "age"=>22},
# {"name"=>"Jane", "surname"=>"Doe", "age"=>34},
# {"name"=>"Chris", "surname"=>"Smith", "age"=>96}]
In the last step the first value of b is passed to map's block and the block variable is assigned its value.
a = d.first
#=> ["John", "Doe", 22]
e = keys.zip(a)
#=> [["name", "John"], ["surname", "Doe"], ["age", 22]]
e.to_h
#=> {"name"=>"John", "surname"=>"Doe", "age"=>22}
The remaining calculations are similar.

If your dataset is really big, you can consider using Enumerator::Lazy.
This way Ruby will not create intermediate arrays during calculations.
This is how #Ursus answer can be improved:
h[:name]
.lazy
.zip(h[:surname])
.map { |name, surname| { 'name' => name, 'surname' => surname } }
.to_a

Other option for the case where:
[..] the real-world use scenario could contain a variable number of hash keys
h = {
'name': ['John','Jane','Chris','Mary'],
'surname': ['Doe','Doe','Smith','Martins'],
'whathever': [1, 2, 3, 4, 5]
}
You could use Object#then with a splat operator in a one liner:
h.values.then { |a, *b| a.zip *b }.map { |e| (h.keys.zip e).to_h }
#=> [{:name=>"John", :surname=>"Doe", :whathever=>1}, {:name=>"Jane", :surname=>"Doe", :whathever=>2}, {:name=>"Chris", :surname=>"Smith", :whathever=>3}, {:name=>"Mary", :surname=>"Martins", :whathever=>4}]
The first part, works this way:
h.values.then { |a, *b| a.zip *b }
#=> [["John", "Doe", 1], ["Jane", "Doe", 2], ["Chris", "Smith", 3], ["Mary", "Martins", 4]]
The last part just maps the elements zipping each with the original keys then calling Array#to_h to convert to hash.
Here I removed the call .to_h to show the intermediate result:
h.values.then { |a, *b| a.zip *b }.map { |e| h.keys.zip e }
#=> [[[:name, "John"], [:surname, "Doe"], [:whathever, 1]], [[:name, "Jane"], [:surname, "Doe"], [:whathever, 2]], [[:name, "Chris"], [:surname, "Smith"], [:whathever, 3]], [[:name, "Mary"], [:surname, "Martins"], [:whathever, 4]]]

[h[:name], h[:surname]].transpose.map do |name, surname|
{ 'name' => name, 'surname' => surname }
end

Related

Create new hash from original based on specific criteria

The code is as follows
fruits = { "mango": 5, "orange": 8, "apple": 2}
The selected ones are as follows
selected = ["apple", "orange"]
Now i need to get a hash with the selected ones. Currently, it is implemented as follows
h = {}
selected.each do |fruit|
h[fruit] = fruits[fruit.to_sym]
end
This will yield
#h = {"apple"=>2, "orange"=>8}
Is there any one liner to achieve this?
fruits = { mango: 5, orange: 8, apple: 2 }
selected = ["apple", "orange"]
fruits.slice(*selected.map(&:to_sym))
#=> {:apple=>2, :orange=>8}
See Hash#slice.
Use select method of the hash
[17] pry(main)> fruits = { "mango": 5, "orange": 8, "apple": 2}
=> {:mango=>5, :orange=>8, :apple=>2}
[18] pry(main)> selected = ["apple", "orange"]
=> ["apple", "orange"]
[19] pry(main)> fruits.select {|key, value| selected.include? key.to_s}
=> {:orange=>8, :apple=>2}
This is the one liner of your solution using Enumerable#each_with_object:
selected.each_with_object({}) { |fruit, h| h[fruit.to_sym] = fruits[fruit.to_sym] }
Notice that I added .to_sym also to the first member of the assignment, that's to keep consistency with the fact that keys are symbols:
fruits.keys.first.class #=> symbol
But it's on you.

Merge Ruby Hash values with same key

Is this possible to achieve with selected keys:
Eg
h = [
{a: 1, b: "Hello", c: "Test1"},
{a: 2, b: "Hey", c: "Test1"},
{a: 3, b: "Hi", c: "Test2"}
]
Expected Output
[
{a: 1, b: "Hello, Hey", c: "Test1"}, # See here, I don't want key 'a' to be merged
{a: 3, b: "Hi", c: "Test2"}
]
My Try
g = h.group_by{|k| k[:c]}.values
OUTPUT =>
[
[
{:a=>1, :b=>"Hello", :c=>"Test1"},
{:a=>2, :b=>"Hey", :c=>"Test1"}
], [
{:a=>3, :b=>"Hi", :c=>"Test2"}
]
]
g.each do |v|
if v.length > 1
c = v.reduce({}) do |s, l|
s.merge(l) { |_, a, b| [a, b].uniq.join(", ") }
end
end
p c #{:a=>"1, 2", :b=>"Hello, Hey", :c=>"Test1"}
end
So, the output I get is
{:a=>"1, 2", :b=>"Hello, Hey", :c=>"Test1"}
But, I needed
{a: 1, b: "Hello, Hey", c: "Test1"}
NOTE: This is just a test array of HASH I have taken to put my question. But, the actual hash has a lots of keys. So, please don't reply with key comparison answers
I need a less complex solution
I can't see a simpler version of your code. To make it fully work, you can use the first argument in the merge block instead of dismissing it to differentiate when you need to merge a and b or when you just use a. Your line becomes:
s.merge(l) { |key, a, b| key == :a ? a : [a, b].uniq.join(", ") }
Maybe you can consider this option, but I don't know if it is less complex:
h.group_by { |h| h[:c] }.values.map { |tmp| tmp[0].merge(*tmp[1..]) { |key, oldval, newval| key == :b ? [oldval, newval].join(' ') : oldval } }
#=> [{:a=>1, :b=>"Hello Hey", :c=>"Test1"}, {:a=>3, :b=>"Hi", :c=>"Test2"}]
The first part groups the hashes by :c
h.group_by { |h| h[:c] }.values #=> [[{:a=>1, :b=>"Hello", :c=>"Test1"}, {:a=>2, :b=>"Hey", :c=>"Test1"}], [{:a=>3, :b=>"Hi", :c=>"Test2"}]]
Then it maps to merge the first elements with others using Hash#merge
h.each_with_object({}) do |g,h|
h.update(g[:c]=>g) { |_,o,n| o.merge(b: "#{o[:b]}, #{n[:b]}") }
end.values
#=> [{:a=>1, :b=>"Hello, Hey", :c=>"Test1"},
# {:a=>3, :b=>"Hi", :c=>"Test2"}]
This uses the form of Hash#update that employs a block (here { |_,o,n| o.merge(b: "#{o[:b]}, #{n[:b]}") }) to determine the values of keys that are present in both hashes being merged. The first block variable holds the common key. I’ve used an underscore for that variable mainly to signal to the reader that it is not used in the block calculation. See the doc for definitions of the other two block variables.
Note that the receiver of values equals the following.
h.each_with_object({}) do |g,h|
h.update(g[:c]=>g) { |_,o,n| o.merge(b: "#{o[:b]}, #{n[:b]}") }
end
#=> { “Test1”=>{:a=>1, :b=>"Hello, Hey", :c=>"Test1"},
# “Test2=>{:a=>3, :b=>"Hi", :c=>"Test2"} }

Looking to convert information from a file into a hash Ruby

Hello I have been doing some research for sometime on this particular project I have been working on and I am at a loss. What I am looking to do is use information from a file and convert that to a hash using some of those components for my key. Within the file I have:1,Foo,20,Smith,40,John,55
An example of what I am looking for I am looking for an output like so {1 =>[Foo,20], 2 =>[Smith,40] 3 => [John,55]}
Here is what I got.
h = {}
people_file = File.open("people.txt") # I am only looking to read here.
until people_file.eof?
i = products_file.gets.chomp.split(",")
end
people_file.close
FName = 'test'
str = "1,Foo,20,Smith, 40,John,55"
File.write(FName, str)
#=> 26
base, *arr = File.read(FName).
split(/\s*,\s*/)
enum = (base.to_i).step
arr.each_slice(2).
with_object({}) {|pair,h| h[enum.next]=pair}
#=> {1=>["Foo", "20"], 2=>["Smith", "40"],
# 3=>["John", "55"]}
The steps are as follows.
s = File.read(FName)
#=> "1,Foo,20,Smith, 40,John,55"
base, *arr = s.split(/\s*,\s*/)
#=> ["1", "Foo", "20", "Smith", "40", "John", "55"]
base
#=> "1"
arr
#=> ["Foo", "20", "Smith", "40", "John", "55"]
a = base.to_i
#=> 1
I assume the keys are to be sequential integers beginning with a #=> 1.
enum = a.step
#=> (1.step)
enum.next
#=> 1
enum.next
#=> 2
enum.next
#=> 3
Continuing,
enum = a.step
b = arr.each_slice(2)
#=> #<Enumerator: ["Foo", "20", "Smith", "40", "John", "55"]:each_slice(2)>
Note I needed to redefine enum (or execute enum.rewind) to reinitialize it. We can see the elements that will be generated by this enumerator by converting it to an array.
b.to_a
#=> [["Foo", "20"], ["Smith", "40"], ["John", "55"]]
Continuing,
c = b.with_object({})
#=> #<Enumerator: #<Enumerator: ["Foo", "20", "Smith", "40", "John", "55"]
# :each_slice(2)>:with_object({})>
c.to_a
#=> [[["Foo", "20"], {}], [["Smith", "40"], {}], [["John", "55"], {}]]
The now-empty hashes will be constructed as calculations progress.
c.each {|pair,h| h[enum.next]=pair}
#=> {1=>["Foo", "20"], 2=>["Smith", "40"], 3=>["John", "55"]}
To see how the last step is performed, each initially directs the enumerator c to generate the first value, which it passes to the block. The block variables are assigned to that value, and the block calculation is performed.
enum = a.step
b = arr.each_slice(2)
c = b.with_object({})
pair, h = c.next
#=> [["Foo", "20"], {}]
pair
#=> ["Foo", "20"]
h #=> {}
h[enum.next]=pair
#=> ["Foo", "20"]
Now,
h#=> {1=>["Foo", "20"]}
The calculations are similar for the remaining two elements generated by the enumerator c.
See IO::write, IO::read, Numeric#step, Enumerable#each_slice, Enumerator#with_object, Enumerator#next and Enumerator#rewind. write and read respond to File because File is a subclass of IO (File.superclass #=> IO). split's argument, the regular expression, /\s*,\s*/, causes the string to be split on commas together with any spaces that surround the commas. Converting [["Foo", "20"], {}] to pair and h is a product of Array Decompostion.

Translate Ruby hash (key,value) to separate keys

I have a map function in ruby which returns an array of arrays with two values in each, which I want to have in a different format.
What I want to have:
"countries": [
{
"country": "Canada",
"count": 12
},
{and so on... }
]
But map obviously returns my values as array:
"countries": [
[
"Canada",
2
],
[
"Chile",
1
],
[
"China",
1
]
]
When using Array::to_h I am also able to bringt it closer to the format I actually want to have.
"countries": {
"Canada": 2,
"Chile": 1,
"China": 1,
}
I have tried reduce/inject, each_with_object but in both cases I do not understand how to access the incoming parameters. While searching here you find many many similar problems. But haven't found a way to adapt those to my case.
Hope you can help to find a short and elegant solution.
You are given two arrays:
countries= [['Canada', 2], ['Chile', 1], ['China', 1]]
keys = [:country, :count]
You could write
[keys].product(countries).map { |arr| arr.transpose.to_h }
#=> [{:country=>"Canada", :count=>2},
# {:country=>"Chile", :count=>1},
# {:country=>"China", :count=>1}]
or simply
countries.map { |country, cnt| { country: country, count: cnt } }
#=> [{:country=>"Canada", :count=>2},
# {:country=>"Chile", :count=>1},
# {:country=>"China", :count=>1}]
but the first has the advantage that no code need be changed in the names of the keys change. In fact, there would be no need to change the code if the arrays countries and keys both changed, provided countries[i].size == keys.size for all i = 0..countries.size-1. (See the example at the end.)
The initial step for the first calculation is as follows.
a = [keys].product(countries)
#=> [[[:country, :count], ["Canada", 2]],
# [[:country, :count], ["Chile", 1]],
# [[:country, :count], ["China", 1]]]
See Array#product. We now have
a.map { |arr| arr.transpose.to_h }
map passes the first element of a to the block and sets the block variable arr to that value:
arr = a.first
#=> [[:country, :count], ["Canada", 2]]
The block calculation is then performed:
b = arr.transpose
#=> [[:country, "Canada"], [:count, 2]]
b.to_h
#=> {:country=>"Canada", :count=>2}
So we see that a[0] (arr) is mapped to {:country=>"Canada", :count=>2}. The next two elements of a are then passed to the block and similar calculations are made, after which map returns the desired array of three hashes. See Array#transpose and Array#to_h.
Here is a second example using the same code.
countries= [['Canada', 2, 9.09], ['Chile', 1, 0.74],
['China', 1, 9.33], ['France', 1, 0.55]]
keys = [:country, :count, :area]
[keys].product(countries).map { |arr| arr.transpose.to_h }
#=> [{:country=>"Canada", :count=>2, :area=>9.09},
# {:country=>"Chile", :count=>1, :area=>0.74},
# {:country=>"China", :count=>1, :area=>9.33},
# {:country=>"France", :count=>1, :area=>0.55}]
Just out of curiosity:
countries = [['Canada', 2], ['Chile', 1], ['China', 1]]
countries.map(&%i[country count].method(:zip)).map(&:to_h)
#⇒ [{:country=>"Canada", :count=>2},
# {:country=>"Chile", :count=>1},
# {:country=>"China", :count=>1}]

Hash invert in Ruby?

I've got a hash of the format:
{key1 => [a, b, c], key2 => [d, e, f]}
and I want to end up with:
{ a => key1, b => key1, c => key1, d => key2 ... }
What's the easiest way of achieving this?
I'm using Ruby on Rails.
UPDATE
OK I managed to extract the real object from the server log, it is being pushed via AJAX.
Parameters: {"status"=>{"1"=>["1", "14"], "2"=>["7", "12", "8", "13"]}}
hash = {:key1 => ["a", "b", "c"], :key2 => ["d", "e", "f"]}
first variant
hash.map{|k, v| v.map{|f| {f => k}}}.flatten
#=> [{"a"=>:key1}, {"b"=>:key1}, {"c"=>:key1}, {"d"=>:key2}, {"e"=>:key2}, {"f"=>:key2}]
or
hash.inject({}){|h, (k,v)| v.map{|f| h[f] = k}; h}
#=> {"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2}
UPD
ok, your hash is:
hash = {"status"=>{"1"=>["1", "14"], "2"=>["7", "12", "8", "13"]}}
hash["status"].inject({}){|h, (k,v)| v.map{|f| h[f] = k}; h}
#=> {"12"=>"2", "7"=>"2", "13"=>"2", "8"=>"2", "14"=>"1", "1"=>"1"}
Lots of other good answers. Just wanted to toss this one in too for Ruby 2.0 and 1.9.3:
hash = {apple: [1, 14], orange: [7, 12, 8, 13]}
Hash[hash.flat_map{ |k, v| v.map{ |i| [i, k] } }]
# => {1=>:apple, 14=>:apple, 7=>:orange, 12=>:orange, 8=>:orange, 13=>:orange}
This is leveraging: Hash::[] and Enumerable#flat_map
Also in these new versions there is Enumerable::each_with_object which is very similar to Enumerable::inject/Enumerable::reduce:
hash.each_with_object(Hash.new){ |(k, v), inverse|
v.each{ |e| inverse[e] = k }
}
Performing a quick benchmark (Ruby 2.0.0p0; 2012 Macbook Air) using an original hash with 100 keys, each with 100 distinct values:
Hash::[] w/ Enumerable#flat_map
155.7 (±9.0%) i/s - 780 in 5.066286s
Enumerable#each_with_object w/ Enumerable#each
199.7 (±21.0%) i/s - 940 in 5.068926s
Shows that the each_with_object variant is faster for that data set.
Ok, let's guess. You say you have an array but I agree with Benoit that what you probably have is a hash. A functional approach:
h = {:key1 => ["a", "b", "c"], :key2 => ["d", "e", "f"]}
h.map { |k, vs| Hash[vs.map { |v| [v, k] }] }.inject(:merge)
#=> {"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2}
Also:
h.map { |k, vs| Hash[vs.product([k])] }.inject(:merge)
#=> {"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2}
In the case where a value corresponds to more than one key, like "c" in this example...
{ :key1 => ["a", "b", "c"], :key2 => ["c", "d", "e"]}
...some of the other answers will not give the expected result. We will need the reversed hash to store the keys in arrays, like so:
{ "a" => [:key1], "b" => [:key1], "c" => [:key1, :key2], "d" => [:key2], "e" => [:key2] }
This should do the trick:
reverse = {}
hash.each{ |k,vs|
vs.each{ |v|
reverse[v] ||= []
reverse[v] << k
}
}
This was my use case, and I would have defined my problem much the same way as the OP (in fact, a search for a similar phrase got me here), so I suspect this answer may help other searchers.
If you're looking to reverse a hash formatted like this, the following may help you:
a = {:key1 => ["a", "b", "c"], :key2 => ["d", "e", "f"]}
a.inject({}) do |memo, (key, values)|
values.each {|value| memo[value] = key }
memo
end
this returns:
{"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2}
new_hash={}
hash = {"key1" => ['a', 'b', 'c'], "key2" => ['d','e','f']}
hash.each_pair{|key, val|val.each{|v| new_hash[v] = key }}
This gives
new_hash # {"a"=>"key1", "b"=>"key1", "c"=>"key1", "d"=>"key2", "e"=>"key2", "f"=>"key2"}
If you want to correctly deal with duplicate values, then you should use the Hash#inverse
from Facets of Ruby
Hash#inverse preserves duplicate values,
e.g. it ensures that hash.inverse.inverse == hash
either:
use Hash#inverse from here: http://www.unixgods.org/Ruby/invert_hash.html
use Hash#inverse from FacetsOfRuby library 'facets'
usage like this:
require 'facets'
h = {:key1 => [:a, :b, :c], :key2 => [:d, :e, :f]}
=> {:key1=>[:a, :b, :c], :key2=>[:d, :e, :f]}
h.inverse
=> {:a=>:key1, :b=>:key1, :c=>:key1, :d=>:key2, :e=>:key2, :f=>:key2}
The code looks like this:
# this doesn't looks quite as elegant as the other solutions here,
# but if you call inverse twice, it will preserve the elements of the original hash
# true inversion of Ruby Hash / preserves all elements in original hash
# e.g. hash.inverse.inverse ~ h
class Hash
def inverse
i = Hash.new
self.each_pair{ |k,v|
if (v.class == Array)
v.each{ |x|
i[x] = i.has_key?(x) ? [k,i[x]].flatten : k
}
else
i[v] = i.has_key?(v) ? [k,i[v]].flatten : k
end
}
return i
end
end
h = {:key1 => [:a, :b, :c], :key2 => [:d, :e, :f]}
=> {:key1=>[:a, :b, :c], :key2=>[:d, :e, :f]}
h.inverse
=> {:a=>:key1, :b=>:key1, :c=>:key1, :d=>:key2, :e=>:key2, :f=>:key2}
One way to achieve what you're looking for:
arr = [{["k1"] => ["a", "b", "c"]}, {["k2"] => ["d", "e", "f"]}]
results_arr = []
arr.each do |hsh|
hsh.values.flatten.each do |val|
results_arr << { [val] => hsh.keys.first }···
end
end
Result: [{["a"]=>["k1"]}, {["b"]=>["k1"]}, {["c"]=>["k1"]}, {["d"]=>["k2"]}, {["e"]=>["k2"]}, {["f"]=>["k2"]}]

Resources