Ruby Array of hashes to a Hash - arrays

I have an array that looks something like this
array = [{ a: 123, b: 'foo', c: 'bar' }, { a: 456, b: 'baz', c: 'qux' }]
I would like to convert this to a hash whose keys are the values of :a in the hashes in array, and whose values consist of hashes with :b and :c.
{ 123 => { b: 'foo', c: 'bar' }, 456 => { b: 'baz', c: 'qux' } }
Is this doable using ruby?

array.each_with_object({}){|e, h| e = e.dup; h[e.delete(:a)] = e}
# => {123=>{:b=>"foo", :c=>"bar"}, 456=>{:b=>"baz", :c=>"qux"}}
If you don't care about side effects:
array.each_with_object({}){|e, h| h[e.delete(:a)] = e}
# => {123=>{:b=>"foo", :c=>"bar"}, 456=>{:b=>"baz", :c=>"qux"}}

I'm not sure what problem you're trying to solve. This is what I come out with:
def group_by_key(array, key)
array.map { |x| [x.delete(key), x] }.to_h
end
It works good on your example:
array = [{ a: 123, b: 'foo', c: 'bar' }, { a: 456, b: 'baz', c: 'qux' }]
group_by_key(array, :a)
#=> {123=>{:b=>"foo", :c=>"bar"}, 456=>{:b=>"baz", :c=>"qux"}}

Assuming that all elements of array (hashes) have a key :a and that array cannot be mutated, use Hash#reject:
array.each_with_object({}) { |g,h| h[g[:a]] = g.reject { |k,_| k == :a } }
#=> {123=>{:b=>"foo", :c=>"bar"}, 456=>{:b=>"baz", :c=>"qux"}}

Using Hash#select method.
array.map { |v| [v.fetch(:a), v.select { |k,_| k != :a }] }.to_h
Exact :a, :b & :c
array.map { |v| [v.fetch(:a), v.select { |k,_| [:b, :c].include? k }] }.to_h
Output
{123=>{:b=>"foo", :c=>"bar"}, 456=>{:b=>"baz", :c=>"qux"}}

Related

Ruby - use mutated array for next iteration in loop

Given the following arrays:
arr1 = [
{ nested_test1: [1,2] },
{ nested_test2: [true, false] }
]
arr2 = [
{ test1: 'a', test2: 'b' }
]
I wanna loop over array arr2 and mutate it depending on the contents of arr1. The final result should look like this:
res_arr = [
{ test1: 'a', test2: 'b', nested_test1: 1, nested_test2: true },
{ test1: 'a', test2: 'b', nested_test1: 2, nested_test2: true },
{ test1: 'a', test2: 'b', nested_test1: 1, nested_test2: false },
{ test1: 'a', test2: 'b', nested_test1: 2, nested_test2: false },
]
So my initial setup looks like this:
res_arr = []
arr1.each do |hash1|
arr2.each do |hash2|
hash1.values.first.each do |value|
hash2[hash1.keys.first] = value
res_arr << hash2.clone
end
end
end
But this gives me the following:
res_arr = [
{:test1=>"a", :test2=>"b", :nested_test1=>1},
{:test1=>"a", :test2=>"b", :nested_test1=>2},
{:test1=>"a", :test2=>"b", :nested_test1=>2, :nested_test2=>true},
{:test1=>"a", :test2=>"b", :nested_test1=>2, :nested_test2=>false}
]
So what I wanna do then, is to use the mutated version for each iteration of arr2. How can this be achieved?
I'm not entirely clear what your goal here is. For instance, in arr2, can values of the hash be arrays? In arr1 can the hashes have more than one key value pair? Or what happens if arr2 has multiple hashes in it? Are they merged, or is the result expanded further somehow?
So it's hard to say exactly what the best course is.
Nonetheless, given the data you've given us and the result you say you want, you can get it like this using Array#product:
m = arr1.inject(&:merge)
m.values[0].product(m.values[1]).map {|a| Hash[m.keys.zip(a)].merge(arr2[0]) }
The intuition here is that I think you're trying to create all possible combinations of a couple of arrays, to generate all possible test cases? Depending on what you're trying to do, a more natural way to store the data and get the result might be:
m = {
test1: ['a'],
test2: ['b'],
nested_test1: [1, 2],
nested_test2: [true, false]
}
m.values.inject(&:product).map {|v| Hash[m.keys.zip(v.flatten)] }
Code
The following method permits its arguments to be arrays of arbitrary size.
def expand(arr1, arr2)
arr2.product(
arr1.map { |h| h.flatten.then { |k,v| v.map { |e| { k=>e } } } }.
then { |first, *rest| first.product(*rest) }
).map { |first, (*rest)| first.merge(*rest.flatten) }
end
Example
arr1 = [{ nested_test1: [1,2] }, { nested_test2: [true, false] }]
arr2 = [{ test1: 'a', test2: 'b' }, { test3: 'c', test4: 'd' }]
expand(arr1, arr2)
#=> [{:test1=>"a", :test2=>"b", :nested_test1=>1, :nested_test2=>true},
# {:test1=>"a", :test2=>"b", :nested_test1=>1, :nested_test2=>false},
# {:test1=>"a", :test2=>"b", :nested_test1=>2, :nested_test2=>true},
# {:test1=>"a", :test2=>"b", :nested_test1=>2, :nested_test2=>false},
# {:test3=>"c", :test4=>"d", :nested_test1=>1, :nested_test2=>true},
# {:test3=>"c", :test4=>"d", :nested_test1=>1, :nested_test2=>false},
# {:test3=>"c", :test4=>"d", :nested_test1=>2, :nested_test2=>true},
# {:test3=>"c", :test4=>"d", :nested_test1=>2, :nested_test2=>false}]
Explanation
The following steps are performed for the example.
Noting that:
arr1.map(&:flatten)
#=> [[:nested_test1, [1, 2]], [:nested_test2, [true, false]]]
the first step is the following:
a = arr1.map { |h| h.flatten.then { |k,v| v.map { |e| { k=>e } } }
#=> [[{:nested_test1=>1}, {:nested_test1=>2}],
# [{:nested_test2=>true}, {:nested_test2=>false}]]
See Hash#flatten and Object#then. The latter method made its debut in Ruby v2.6. It is an alias of Object#yield_self, which was new in v2.5.
Then:
b = a.then { |first, *rest| first.product(*rest) }
#=> [[{:nested_test1=>1}, {:nested_test2=>true}],
# [{:nested_test1=>1}, {:nested_test2=>false}],
# [{:nested_test1=>2}, {:nested_test2=>true}],
# [{:nested_test1=>2}, {:nested_test2=>false}]]
See Array#product. Here and especially below I made heavy use of Array decomposition. See also this article.
Continuing,
c = arr2.product(b)
#=> [[{:test1=>"a", :test2=>"b"}, [{:nested_test1=>1}, {:nested_test2=>true}]],
# [{:test1=>"a", :test2=>"b"}, [{:nested_test1=>1}, {:nested_test2=>false}]],
# [{:test1=>"a", :test2=>"b"}, [{:nested_test1=>2}, {:nested_test2=>true}]],
# [{:test1=>"a", :test2=>"b"}, [{:nested_test1=>2}, {:nested_test2=>false}]],
# [{:test3=>"c", :test4=>"d"}, [{:nested_test1=>1}, {:nested_test2=>true}]],
# [{:test3=>"c", :test4=>"d"}, [{:nested_test1=>1}, {:nested_test2=>false}]],
# [{:test3=>"c", :test4=>"d"}, [{:nested_test1=>2}, {:nested_test2=>true}]],
# [{:test3=>"c", :test4=>"d"}, [{:nested_test1=>2}, {:nested_test2=>false}]]]
and lastly:
c.map { |first, (*rest)| first.merge(*rest.flatten) }
#=> <as shown above>
See Hash#merge.

How to convert array like this ["John,Doe,11222019", "Mark,King,11232019", "Angle,Darma,11242019"] to Array of hash Ruby

How to convert array:
["John,Doe,11222019", "Mark,King,11232019", "Angle,Darma,11242019"]
to Array of hash like this using Ruby :
[
{ :name => "John Doe", :number => 11222019 },
{ :name => "Mark King", :number => 11232019 },
{ :name => "Angle Darma", :number => 11242019 },
]
Thank you very much!
You can do it simply as below,
array.map { |x| [:name, :number].zip(x.sub(',', ' ').split(',')).to_h }
# => [{:name=>"John Doe", :number=>11222019}, {:name=>"Mark King", :number=>11232019}, {:name=>"Angle Darma", :number=>11242019}]
Option using Ruby 2.6.1 Object#then:
ary = ["John,Doe,11222019", "Mark,King,11232019", "Angle,Darma,11242019"]
ary.map { |s| s.split(',').then{ |a| {name: a.first(2).join(' ') , number: a.last.to_i } } }
For Ruby 2.5.2 Object#yield_self:
ary.map { |s| s.split(',').yield_self{ |a| {name: a.first(2).join(' ') , number: a.last.to_i } } }
Both returning:
#=> [{:name=>"John Doe", :number=>11222019}, {:name=>"Mark King", :number=>11232019}, {:name=>"Angle Darma", :number=>11242019}]
arr = ["John,Doe,11222019", "Mark,King,11232019",
"Angle,Darma,11242019"]
arr.map do |s|
{name: s[/.+(?=,)/].tr(',',' '), number: s[/\d+/].to_i}
end
#=> [{:name=>"John Doe", :number=>11222019},
# {:name=>"Mark King", :number=>11232019},
# {:name=>"Angle Darma", :number=>11242019}]
The steps are as follows. Enumerable#map passes the first element of arr to the block and the block variable s is set equal to its value:
s = arr.first
#=> "John,Doe,11222019"
The block calculations are then performed:
a = s[/.+(?=,)/]
#=> "John,Doe"
This uses the method String#[] with the regular expression /.+(?=,)/. (?=,) is a positive lookahead that requires the match to be followed immediately by a comma. Because matches are by default greedy the lookahead matches the last comma in s.
b = a.tr(',',' ')
#=> "John Doe"
This uses the method String#tr. Alternatively, a.sub(',',' ') could be used.
c = s[/\d+/]
#=> "11222019"
d = c.to_i
#=> 11222019
The block then returns:
{ name: c, number: d }
#=> {:name=>"11222019", :number=>11222019}
which is the object to which s is mapped. The remaining two values of arr are passed to the block and similar calculations are performed.
a = ["John,Doe,11222019", "Mark,King,11232019", "Angle,Darma,11242019"]
Something like this
a.map do |f|
f = f.split(',')
{ name: "#{f[0]} #{f[1]}", number: f[2].to_i }
end
arr = ["John,Doe,11222019", "Mark,King,11232019", "Angle,Darma,11242019"]
arr.map do |item|
chunks = item.split(",")
{name: chunks[0...-1].join(" "), number: chunks[-1]}
end
Indexing by [0...-1] allows you to have variable number of items in the name part (middle name, or 2 piece last names) which is pretty common.

Manipulate array of hashes into grouped hashes with arrays

I have an array of hashes:
[
{
"June" => { "A" => { 3 => 48.4 } }
},
{
"January" => { "C" => { 2 => 88.0} }
},
{
"January"=> { "B" => { 2 => 44.0} }
},
{
"January"=> { "C" => { 4 => 48.8} }
}
]
I need to group each similar hash key into an array of the subsequent values like the following:
{
"June" => [{ "A" => [{ 3 => 48.4 }]] },
"January" => [
{ "B" => [{ 2 => 44.0}],
{ "C" => [{ 2 => 88.0}, { 4 => 48.8}],
] }
}
I am looking for an efficient method of grouping these elements. Can anyone help me master this hash of hashes?
I am trying to avoid looping through the base array and grouping manually. I was hoping that map (or some other enumerable method) might give what I want. When I used reduce(Hash.new, :merge), it came close but it used the last hash for each month key instead of adding it to an array.
Note: I added the following after gaining a clearer understanding of the question. My original answer is below.
Here is the OP's array of hashes, modified slightly.
arr = [{ "June" =>{ "A"=>{ 3=>48.4 } } },
{ "January"=>{ "C"=>{ 2=>88.0 } } },
{ "January"=>{ "B"=>{ "D"=>{ 2=>44.0 } } } },
{ "January"=>{ "C"=>{ 2=>10.0 } } },
{ "January"=>{ "C"=>{ 4=>48.8 } } }]
The hash to be constructed appears to be the following.
{ "June" =>[{ "A"=>[{ 3=>48.4 }] }],
"January"=>[{ "B"=>[{ "D"=>[{ 2=>44.0 }] }] }],
"C"=>[{ 2=>98.0, 4=>48.8 }] }] }
Note that 88.0 + 10.0 #=> 98.0 in 2=>98.0.
Observe that all the arrays within arr contain a single element, a hash. That being the case, those arrays serve no useful purpose. I therefore suggest the following hash be constructed instead:
{ "June" =>{ "A"=>{ 3=>48.4 } },
"January"=>{ "B"=>{ "D"=>{ 2=>44.0 } } },
"C"=>{ 2=>98.0, 4=>48.8 } } }
This can be produced with the following recursive method.
def recurse(arr)
arr.map(&:flatten).
group_by(&:first).
each_with_object({}) do |(k,v),h|
o = v.map(&:last)
h.update(k=>o.first.is_a?(Hash) ? recurse(o) : o.sum )
end
end
recurse(arr)
#=> {"June"=>{"A"=>{3=>48.4}},
# "January"=>{"C"=>{2=>98.0, 4=>48.8}, "B"=>{"D"=>{2=>44.0}}}}
(Original answer follows)
Here are two ways to obtain the desired hash. I assume that arr is your array of hashes.
#1 Use the form of Hash::new that takes a block
arr.each_with_object(Hash.new { |h,k| h[k] = [] }) do |g,h|
k, v = g.to_a.first
h[k] << v
end
# => {"June"=>[{"A"=>{3=>48.4}}],
# "January"=>[{"C"=>{2=>88.0}}, {"B"=>{2=>44.0}}, {"C"=>{4=>48.8}}]}
#2 Use Enumerable#group_by
arr.map(&:first).
group_by(&:first).
tap { |h| h.keys.each { |k| h[k] = h[k].map(&:last) } }
# => {"June"=>[{"A"=>{3=>48.4}}],
# "January"=>[{"C"=>{2=>88.0}}, {"B"=>{2=>44.0}}, {"C"=>{4=>48.8}}]}
The steps are as follows.
a = arr.map(&:first)
#=> [["June", {"A"=>{3=>48.4}}], ["January", {"C"=>{2=>88.0}}],
# ["January", {"B"=>{2=>44.0}}], ["January", {"C"=>{4=>48.8}}]]
b = a.group_by(&:first)
#=> {"June"=>[["June", {"A"=>{3=>48.4}}]],
# "January"=>[["January", {"C"=>{2=>88.0}}], ["January", {"B"=>{2=>44.0}}],
# ["January", {"C"=>{4=>48.8}}]]}
c = b.tap { |h| h.keys.each { |k| h[k] = h[k].map(&:last) } }
#=> {"June"=>[{"A"=>{3=>48.4}}],
# "January"=>[{"C"=>{2=>88.0}}, {"B"=>{2=>44.0}}, {"C"=>{=>48.8}}]}
Let me elaborate the last step. Inside tap's block, we compute the following.
h = b
d = h.keys
#=> ["June", "January"]
The first element of d is passed to each's block and the block variable is assigned to that element.
k = d.first
#=> "June"
The block calculation is as follows.
e = h[k]
#=> [["June", {"A"=>{3=>48.4}}]]
f = e.map(&:last)
#=> [{"A"=>{3=>48.4}}]
h[k] = f
#=> [{"A"=>{3=>48.4}}]
b #=> {"June"=>[{"A"=>{3=>48.4}}],
# "January"=>[["January", {"C"=>{2=>88.0}}],
# ["January", {"B"=>{2=>44.0}}],
# ["January", {"C"=>{4=>48.8}}]]}
Next, d[1] ("January") is passed to each's block and similar calculations are performed.
Rather than using Object#tap I could have written
h = arr.map(&:first).
group_by(&:first)
h.keys.each { |k| h[k] = h[k].map(&:last) }
h
tap merely avoids the creation of local variable h and the need to have a final line equal to h.

How to merge two arrays of hashes

I have two arrays of hashes:
a = [
{
key: 1,
value: "foo"
},
{
key: 2,
value: "baz"
}
]
b = [
{
key: 1,
value: "bar"
},
{
key: 1000,
value: "something"
}
]
I want to merge them into one array of hashes, so essentially a + b except I want any duplicated key in b to overwrite those in a. In this case, both a and b contain a key 1 and I want the final result to have b's key value pair.
Here's the expected result:
expected = [
{
key: 1,
value: "bar"
},
{
key: 2,
value: "baz"
},
{
key: 1000,
value: "something"
}
]
I got it to work but I was wondering if there's a less wordy way of doing this:
hash_result = {}
a.each do |item|
hash_result[item[:key]] = item[:value]
end
b.each do |item|
hash_result[item[:key]] = item[:value]
end
result = []
hash_result.each do |k,v|
result << {:key => k, :value => v}
end
puts result
puts expected == result # prints true
uniq would work if you concatenate the arrays in reverse order:
(b + a).uniq { |h| h[:key] }
#=> [
# {:key=>1, :value=>"bar"},
# {:key=>1000, :value=>"something"},
# {:key=>2, :value=>"baz"}
# ]
It doesn't however preserve the order.
[a, b].map { |arr| arr.group_by { |e| e[:key] } }
.reduce(&:merge)
.flat_map(&:last)
Here we use hash[:key] as a key to build the new hash, then we merge them overriding everything with the last value and return values.
I would rebuild your data a bit, since there are redundant keys in hashes:
thin_b = b.map { |h| [h[:key], h[:value]] }.to_h
#=> {1=>"bar", 1000=>"something"}
thin_a = b.map { |h| [h[:key], h[:value]] }.to_h
#=> {1=>"bar", 1000=>"something"}
Then you can use just Hash#merge:
thin_a.merge(thin_b)
#=> {1=>"bar", 2=>"baz", 1000=>"something"}
But, if you want, you can get exactly result as mentioned in question:
result.map { |k, v| { key: k, value: v } }
#=> [{:key=>1, :value=>"bar"},
# {:key=>2, :value=>"baz"},
# {:key=>1000, :value=>"something"}]
using Enumerable#group_by and Enumerable#map
(b+a).group_by { |e| e[:key] }.values.map {|arr| arr.first}
If you need to merge two arrays of hashes that should be merged also and there is more than two keys, then next snippet should help:
[a, b].flatten
.compact
.group_by { |v| v[:key] }
.values
.map { |e| e.reduce(&:merge) }

How to deep merge two multi-root tree structure made of array and hashes

I have a nested structure made of Hashes and Arrays that we can call multi-root nth-ary Tree.
I'm representing this structure
Here's an example:
[:o, {
a: :b,
c: [:d, :e, { f: { h: :i, l: [:m, :n] } }]
}]
That represents can be represented with this schema:
o a c
/ /|\
b d e f
/\
h l
/ / \
i m n
I want to deep merge it with another similar structure. Here's an example:
[:z, {
o: :s,
c: [:f, { d: :w }]
}]
z o c
| / \
s f d
\
w
The resulting tree should be something like this:
[:z, {
a: :b,
c: [:e, { f: { h: :i, l: [:m, :n] }, d: :w }],
o: :s
}]
z a c o
| /|\ |
b e f d s
/| \
h l w
| |\
i m n
How can I do this?
I started with this, but I got struck:
def deep_merge(a, b)
case a
when Hash
case b
when Hash
merge_hashes(a, b)
when Array
merge_array_hash(b, a)
else
[b, a]
end
when Array
case b
when Array
merge_arrays(a, b)
when Hash
merge_array_hash(a, b)
else
[b] + a
end
else
case b
when Hash
[a, b]
when Array
[a] + b
else
a == b ? a : [a, b]
end
end
end
# EXAMPLES:
# merge_array_hash([:a, :b], { f: :w })
# => [:a, :b, { f: :w }]
# merge_array_hash([:a, :b, { c: :d }], { f: :w })
# => [:a, :b, { c: :d, f: :w }]
def merge_array_hash(a, b)
if a.last.is_a? Hash
a[0...-1] + [merge_hashes(a.last, b)]
else
a + [b]
end
end
# EXAMPLES:
# merge_hashes({ a: :b }, { c: :d })
# => { a: :b, c: :d }
# merge_hashes({ a: :b }, { d: e, a: :f })
# => { a: [:b, :f], d: :e }
def merge_hashes(a, b)
a.deep_merge(b) do |_, this_val, other_val|
deep_merge(this_val, other_val)
end
end
# EXAMPLES:
# merge_arrays([:a, :b], [:c])
# => [:a, :b, :c]
# merge_arrays([:a, :b { c: :d }], [:f, { c: :e }])
# => [:a, :b, :f, { c: [:d, :e] }]
# merge_arrays([:a, :b { c: :d }], [:f, { c: :e }])
# => [:a, :b, :f, { c: [:d, :e] }]
def merge_arrays(a, b)
keys = merge_array_keys(a, b)
hashes = merge_hashes(a.last.is_a?(Hash) ? a.last : {}, b.last.is_a?(Hash) ? a.last : {})
hashes.empty? keys : (keys + [hashes])
end
# EXAMPLES:
# merge_array_keys([:a, :b], [:f])
# => [:a, :b, :f]
# merge_array_keys([:a, :b { c: :d }], [:f, { c: :e }])
# => [:a, :b, :f]
def merge_array_keys(a, b)
a.reject { |e| e.is_a?(Hash) } + b.reject { |e| e.is_a?(Hash) }
end
As you can see, my solution was a little bit too complex, I think there's a more intelligent way, but I can't find it. Any ideas?
NB: the key order doesn't metter
NB: If there is an Array that contains an Hash, the hash must be the last item of the array
NB: I'm not interested in performances, it could be also very slow for big trees
NB: If you have better ideas to represent this structure in a more intelligent way, for me is ok!

Resources