I am wondering how to merge these two arrays into one clean array in Ruby
Both arrays share one similar key:value pair. I am trying to merge information from these two separate arrays that have information for the same person. One array has his name. The other array has his job and age. Both arrays have an id matching to the same person.
An example of what I am trying to do
array1 = [ {:id => 1, :name => "Bob"}, {:id => 2, :name => "Tim"}]
array2 = [ {:id => 1, :job => "firefighter", :age => 25}, { :id => 2, :job => "accountant", :age => 30} ]
new_array = [ {:id=> 1, name => "Bob", :job => "firefighter", :age => 25}, { :id => 2, :name => "Tim", :job => "accountant", :age => 30} ]
You could do something like this:
new_array = array1.each_with_index.map { |x, i| x.merge array2[i] }
# => [{:id=>1, :name=>"Bob", :job=>"firefighter", :age=>25}, {:id=>2, :name=>"Tim", :job=>"accountant", :age=>30}]
If you want a solution that is not dependent on the order of the array, and instead uses the :id to match the hashes:
array1.map { |x| x.merge (array2.find { |h| h[:id] == x[:id] } || {}) }
If the two arrays contain the same :id values at the same locations:
array1.zip(array2).map { |g,h| g.merge(h) }
#=> [{:id=>1, :name=>"Bob", :job=>"firefighter", :age=>25},
# {:id=>2, :name=>"Tim", :job=>"accountant", :age=>30}]
or equivalently:
[array1, array2].transpose.map { |g,h| g.merge(h) }
If the two arrays contain the same :id values but not necessarily at the same locations:
(array1 + array2).group_by { |h| h[:id] }
.values
.map { |g,h| g.merge(h) }
or
array1.sort_by { |h| h[:id] }
.zip(array2.sort_by { |h| h[:id] } )
.map { |g,h| g.merge(h) }
Related
Hash
data = {
:recordset => {
:row => {
:property => [
{:name => "Code", :value => "C0001"},
{:name => "Customer", :value => "ROSSI MARIO"}
]
}
},
:#xmlns => "http://localhost/test"
}
Code Used
result = data[:recordset][:row].each_with_object([]) do |hash, out|
out << hash[:property].each_with_object({}) do |h, o|
o[h[:name]] = h[:value]
end
end
I cannot get the following output:
[{"Code"=>"C0001", "Customer"=>"ROSSI MARIO", "Phone1"=>"1234567890"}
Error message:
TypeError no implicit conversion of Symbol into Integer
It works correctly in case of multi records
data = {
:recordset => {
:row => [{
:property => [
{:name => "Code", :value => "C0001"},
{:name => "Customer", :value => "ROSSI MARIO"},
{:name => "Phone1", :value => "1234567890"}
]
}, {
:property => [
{:name => "Code", :value => "C0002"},
{:name => "Customer", :value => "VERDE VINCENT"},
{:name => "Phone1", :value => "9876543210"},
{:name => "Phone2", :value => "2468101214"}
]
}]
},
:#xmlns => "http://localhost/test"
}
Code used
data.keys
#=> [:recordset, :#xmlns]
data[:recordset][:row].count
#=> 2 # There are 2 set of attribute-value pairs
result = data[:recordset][:row].each_with_object([]) do |hash, out|
out << hash[:property].each_with_object({}) do |h, o|
o[h[:name]] = h[:value]
end
end
#=> [
# {"Code"=>"C0001", "Customer"=>"ROSSI MARIO", "Phone1"=>"1234567890"},
# {"Code"=>"C0002", "Customer"=>"VERDE VINCENT", "Phone1"=>"9876543210", "Phone2"=>"2468101214"}
# ]
In the first case data[:recordset][:row] is not an Array, it's a Hash, so when you iterate it, the hash variable becomes the array:
[:property, [{:name=>"Code", :value=>"C0001"}, {:name=>"Customer", :value=>"ROSSI MARIO"}]]
In the second case, it's an Array, not a Hash, so when you iterate it, it becomes the hash:
{:property=>[{:name=>"Code", :value=>"C0001"}, {:name=>"Customer", :value=>"ROSSI MARIO"}, {:name=>"Phone1", :value=>"1234567890"}]}
You're always assuming it's the second format. You could force it into an array, and then flatten by 1 level to treat both instances the same:
result = [data[:recordset][:row]].flatten(1).each_with_object([]) do |hash, out|
out << hash[:property].each_with_object({}) do |h, o|
o[h[:name]] = h[:value]
end
end
# => [{"Code"=>"C0001", "Customer"=>"ROSSI MARIO"}] # result from example 1
# => [{"Code"=>"C0001", "Customer"=>"ROSSI MARIO", "Phone1"=>"1234567890"},
# {"Code"=>"C0002", "Customer"=>"VERDE VINCENT",
# "Phone1"=>"9876543210", "Phone2"=>"2468101214"}] # result from example 2
It's tempting to try and use Kernal#Array() instead of [].flatten(1), but you have to remember that Hash implements to_a to return a nested array of keys and values, so Kernal#Array() doesn't work like you'd want it to:
Array(data[:recordset][:row]) # using the first example data
# => [[:property, [{:name=>"Code", :value=>"C0001"}, {:name=>"Customer", :value=>"ROSSI MARIO"}]]]
You can create an array if it's not an array to normalize the input before processing it.
info = data[:recordset][:row]
info = [info] unless info.is_an? Array
result = info.each_with_object([]) do ....
I have a huge array full of a bunch of hashes. What I need to do is single out one index hash from the array that meets a specific criteria. (doing this due to an rspec test, but having trouble singling out one of them)
My array is like this
[
{
"name" => "jon doe",
"team" => "team2",
"price" => 2000,
"eligibility_settings" => {}
},
{
"name" => "jonny doe",
"team" => "team1",
"value" => 2000,
"eligibility_settings" => {
"player_gender" => male,
"player_max_age" => 26,
"player_min_age" => 23,
"established_union_only" => true
}
},
{
"name" => "jonni doe",
"team" => "team3",
"price" => 2000,
"eligibility_settings" => {}
},
]
I need to single out the second one, based on its eligibility settings. I just took three of them from my array, have lots more, so simple active record methods like (hash.second) won't work in this instance.
I've tried things like
players.team.map(&:hash).find{ |x| x[ 'eligibility_settings?' ] == true}
However when I try this, I get a nil response. (which is odd)
I've also looked into using the ruby detect method, which hasn't gotten me anywhere either
Players.team.map(&:hash).['hash.seligibiltiy_settings'].detect { true }
Would anybody have any idea what to do with this one?
Notes
players.team.map(&:hash).find{ |x| x[ 'eligibility_settings?' ] == true}
Players.team.map(&:hash).['hash.seligibiltiy_settings'].detect { true }
Is is players or Players ?
Why is it plural?
If you can call map on team, it probably should be plural
Why do you convert to a hash?
eligibility_settings? isn't a key in your hash. eligibility_settings is
eligibility_settings can be a hash, but it cannot be true
If you want to check if it isn't empty, use !h['eligibility_settings'].empty?
Possible solution
You could use :
data = [
{
'name' => 'jon doe',
'team' => 'team2',
'price' => 2000,
'eligibility_settings' => {}
},
{
'name' => 'jonny doe',
'team' => 'team1',
'value' => 2000,
'eligibility_settings' => {
'player_gender' => 'male',
'player_max_age' => 26,
'player_min_age' => 23,
'established_union_only' => true
}
},
{
'name' => 'jonni doe',
'team' => 'team3',
'price' => 2000,
'eligibility_settings' => {}
}
]
p data.find { |h| !h['eligibility_settings'].empty? }
# {"name"=>"jonny doe", "team"=>"team1", "value"=>2000, "eligibility_settings"=>{"player_gender"=>"male", "player_max_age"=>26, "player_min_age"=>23, "established_union_only"=>true}}
If h['eligibility_settings'] can be nil, you can use :
data.find { |h| !h['eligibility_settings'].blank? }
or
data.find { |h| h['eligibility_settings'].present? }
I have data like this:
hash_data = [
{:key1 => 'value4', :sortby => 4},
{:key1 => 'valuesds6', :sortby => 6},
{:key1 => 'valuedsd', :sortby => 1},
{:key1 => 'value2_data_is_here', :sortby => 2}
]
I want to sort it to this by the key sortby
hash_data = [
{:key1 => 'valuedsd', :sortby => 1},
{:key1 => 'value2_data_is_here', :sortby => 2},
{:key1 => 'value4', :sortby => 4},
{:key1 => 'valuesds6', :sortby => 6}
]
I have tried using bubble sort, but is there any inbuilt function in a Hash class for such purposes?
Enumerable#sort_by to the rescue:
hash_data.sort_by { |hash| hash[:sortby] }
#=> [{:key1=>"valuedsd", :sortby=>1}, {:key1=>"value2_data_is_here", :sortby=>2}, {:key1=>"value4", :sortby=>4}, {:key1=>"valuesds6", :sortby=>6}]
If you don't care about initial object, I would suggest using Array#sort_by! to modify inplace - it is more resource-efficient:
hash_data.sort_by! { |hash| hash[:sortby] }
If you have different types of data as values to sortby key, you should first unify the data type and only then perform sorting.
To have array sorted in descending order, use Enumerable#reverse (or reverse!):
hash_data.sort_by {|hash| hash[:sortby] }.reverse
#=> [{:key1=>"valuesds6", :sortby=>6}, {:key1=>"value4", :sortby=>4}, {:key1=>"value2_data_is_here", :sortby=>2}, {:key1=>"valuedsd", :sortby=>1}]
Another option for sorting in descending order is the following - note minus sign (credits to #sagarpandya82):
hash_data.sort_by {|hash| -hash[:sortby] }
Say I have this:
[
{ :user_id => 1, :search_id => a},
{ :user_id => 1, :search_id => b},
{ :user_id => 2, :search_id => c},
{ :user_id => 2, :search_id => d}
]
and I want to end up with:
[
{ :user_id => 1, :search_id => [a,b]},
{ :user_id => 2, :search_id => [c,d]}
]
What is the best way to do that?
Very strange requirement indeed. Anyway
[ { :user_id => 1, :search_id => "a"},
{ :user_id => 1, :search_id => "b"},
{ :user_id => 2, :search_id => "c"},
{ :user_id => 2, :search_id => "d"} ] \
.map{ |h| h.values_at(:user_id, :search_id) } \
.group_by(&:first) \
.map{ |k, v| { :user_id => k, :search_id => v.map(&:last) } }
array.group_by{|x| x[:user_id] }.values.map do |val|
{ user_id: val.first[:user_id],
search_id: val.inject([]){|me, el| me << el[:search_id]} }
end
First off, I think the cleaner output structure here is to allow the user IDs to be the hash keys and the list of search IDs to be the values:
{
1 => [a, b],
2 => [c, d]
}
There might be a clever way using Rails helpers to get this structure, but it's not too bad to do it manually, either:
output = {}
input.each do |row|
key = row[:user_id]
value = row[:search_id]
output[key] ||= []
output[key] << value
end
I agree with Matchus representation of the data and just want to suggest a shorter version for it (input being the initial array).
input.each.with_object(Hash.new {|h,k| h[k] = []}) do |k,o|
o[k[:user_id]] << k[:search_id]
end
EDIT: this is Ruby > 1.9
input.inject({}) do
|m, h| (m[h[:user_id]] ||= []) << h[:search_id]; m
end.inject([]) { |m, (k, v)| m << { :user_id => k, :search_id => v }; m }
I have that deep Hash of hashes:
my_hash = {
:category_1 => {
:solution_1 => { :order => 1 },
:solution_2 => { :order => 2 }
},
:category_2 => {
:solution_3 => { :order => 3 },
:solution_4 => { :order => 4 }
}
}
I want to sort :solution_* hashes under :category_* hashes by key :order. Any suggestions?
(fixed)
Let's say you have the following hash of people to ages:
people = {
:fred => { :name => "Fred", :age => 23 },
:joan => { :name => "Joan", :age => 18 },
:pete => { :name => "Pete", :age => 54 }
}
use sort_by to get where we want to go:
people.sort_by { |k, v| v[:age] }
# => [[:joan, {:name=>"Joan", :age=>18}],
[:fred, {:name=>"Fred", :age=>23}],
[:pete, {:name=>"Pete", :age=>54}]]
Ok, you didn't specify your question, so I'm assuming you want one layer removed. I changed the starting hash a bit to actually see if the sorting works:
my_hash = {
:category_1 => {
:solution_1 => { :order => 2 },
:solution_2 => { :order => 3 }
},
:category_2 => {
:solution_3 => { :order => 4 },
:solution_4 => { :order => 1 }
}
}
Hash[my_hash.inject({}) { |h, (k, v)| h.merge(v) }.sort_by { |k,v| v[:order] }]
#=> {:solution_4=>{:order=>1}, :solution_1=>{:order=>2}, :solution_2=>{:order=>3}, :solution_3=>{:order=>4}}
EDIT:
Taking into account your clarification (and still starting from the modified unsorted hash I posted above):
sorted = my_hash.inject({}) do |h, (k, v)|
h[k] = Hash[v.sort_by { |k1, v1| v1[:order] }]
h
end
#=> {:category_1=>{:solution_1=>{:order=>2}, :solution_2=>{:order=>3}}, :category_2=>{:solution_4=>{:order=>1}, :solution_3=>{:order=>4}}}