How to merge two arrays of hashes - arrays

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) }

Related

In ruby how do you rearrange an array of objects with id keys, give a new order of the array as an array?

I have an object with an array that looks like this:
some_object = {
some_array: [
{ id: "foo0" },
{ id: "foo1" },
{ id: "foo2" },
{ id: "foo3" },
]
}
And I have an input of another array that I want to rearrange the array in
target_order = [
{ id: "foo0", new_position: 3 },
{ id: "foo3", new_position: 0 },
{ id: "foo1", new_position: 2 },
{ id: "foo2", new_position: 1 }
]
How do I go about using the second target_order array to modify the order of the first some_object[:some_array]?
I recommend you use sort_by with a custom block that finds the position of the item in the new array.
new_array = some_object[:some_array].sort_by do |item|
order = target_order.detect { |order| order[:id] == item[:id] }
next unless order
order[:new_position]
end
This returns the following value.
=> [{:id=>"foo2"}, {:id=>"foo1"}, {:id=>"foo0"}, {:id=>"foo3"}]
Further considerations
Perhaps you wanted to give each item a position in a list instead of just sorting them. For instance
target_order = [
{ id: "foo0", new_position: 0 },
{ id: "foo1", new_position: 2 }
]
would give
=> [{ id: "foo0" }, nil, { id: "foo1" }]
To do this, you should use each_with_object instead of sort_by.
new_array = target_order.each_with_object([]) do |order, memo|
item = some_object[:some_array].detect { |item| item[:id] == order[:id] }
next unless item
memo[order[:new_position]] = item
end
Just to be simplistic, this is what I would do ...
temp_arr = []
target_order.each do |o|
x = some_json_object[:some_array].find { |i| o[:id] == i[:id] }
temp_arr[o[:new_position] - 1] = x
end
some_json_object = {
"some_array": temp_arr
}
If there is one to one correspondence between some_array and target_order elements, maybe you can do a direct assignment, something like:
some_object[:some_array] = target_order.sort_by{ |h| h[:new_position] }.map { |h| h.delete_if { |k, _| k == :new_position } }
So, you'll end up with
some_object #=> {:some_array=>[{:id=>"foo3"}, {:id=>"foo2"}, {:id=>"foo1"}, {:id=>"foo0"}]}
There is no need to sort, which has time-complexity of O(n*log(n)). Here is a O(n) solution.
{ some_array: target_order.each_with_object([]) { |h,a|
a[h[:new_position]] = h.slice(:id) } }
#=> {:some_array=>[{:id=>"foo3"}, {:id=>"foo2"}, {:id=>"foo1"}, {:id=>"foo0"}]}
Note that there is no reference to some_object.
If some_object is to be modified in place:
some_object[:some_array] = target_order.each_with_object([]) { |h,a|
a[h[:new_position]] = h.slice(:id) }
some_object
#=> {:some_array=>[{:id=>"foo3"}, {:id=>"foo2"}, {:id=>"foo1"}, {:id=>"foo0"}]}
Using Enumerable#sort_by, though less efficient, one could write:
{ some_array: target_order.sort_by { |h| h[:new_position] }.map { |h| h.slice(:id) } }
#=> {:some_array=>[{:id=>"foo3"}, {:id=>"foo2"}, {:id=>"foo1"}, {:id=>"foo0"}]}

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.

Ruby Array of hashes to a Hash

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"}}

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.

Ruby Hash Values is Array, need to convert to string

I have a hash of integers as keys and arrays of strings as values. I need to convert this to a new hash that inverts this relationship with each item from the array of strings in the original hash values becoming a key in the new hash and each original key becoming the associated value. For example:
original = {1 => ['a', 'b', 'c'], 2 => ['g', 'm', 'z']}
new_hash = {'a' => 1, 'b' => 1, 'c' => 1, 'g' => 2, 'm' => 2, 'z' => 2}
I'm struggling to extract the items from the original array values. It's easy enough to do
original.each { |k, v| new_hash[v] = k }
but this keeps the original array as the new key. I've tried doing something like
original.each { |k, v| new_hash[v.each { |i| i }] = k }
but this also returns the original array for some reason.
Another one, via Array#product:
original.flat_map { |k, v| v.product([k]) }.to_h
#=> {"a"=>1, "b"=>1, "c"=>1, "g"=>2, "m"=>2, "z"=>2}
original.flat_map { |k, vs| vs.map { |v| {v => k} } }.reduce(&:merge)
the below snippet will give what you want, but let me think on a more readable and elegant solution.
newhash = {}
original.each do |k,v|
v.each do |v2|
newhash[v2] = k
end
end
#=> {1=>["a", "b", "c"], 2=>["g", "m", "z"]}
newhash
#=> {"a"=>1, "b"=>1, "c"=>1, "g"=>2, "m"=>2, "z"=>2}
Your approach is close. You'll have to iterate each element in the values array when assigning the new key/value pair to the newHash
newHash = {}
original.each { |k, v| v.each {|i| newHash[i] = k}}
original.map { |number, ary| Hash[ary.map { |char| [char, number] }] }.reduce(&:merge)

Resources