Convert array of array into array of hash - Ruby - ruby

I want to be able to turn this array of array:
[["Cargo Freight", 37],
["Semiconductor", 35]]
Into this array of hash:
[
{
name: "Cargo Freight",
id: 37
},
{
name: "Semiconductor",
id: 35
}]
Any help would be much appreciated.

Here is a simple solution:
array = [["Cargo Freight", 37],["Semiconductor", 35]]
array.map { |a| { name: a[0], id: a[1] } }

It can be done with the ruby reduce method
a = [["Cargo Freight", 37], ["Semiconductor", 35]]
a.reduce([]) { |array, el| array << {name: el.first, id: el.last} }
OR
a.reduce([]) { |array, el| array << [[:name, :id], el].to_h}

I would do this:
KEYS = %i[name id]
array = [["Cargo Freight", 37],["Semiconductor", 35]]
array.map { |e| KEYS.zip(e).to_h }
#=> [{:name=>"Cargo Freight", :id=>37}, {:name=>"Semiconductor", :id=>35}]

Related

Average of an array of nested hashes with same structure

Let's assume I have an array of nested hash like this one :
array = [
{
"id": 8444,
"version": "2.1.0",
"data": {
"data1": {
"data1-1": {
"a": 132.6,
"b": 128.36,
"c": 153.59,
"d": 136.48
}
},
"data2": {
"data2-1": {
"a": 1283.0,
"b": 1254.0,
"c": 1288.5,
"d": 1329.0
}
}
}
},
{
"id": 8443,
"version": "2.1.0",
"data": {
"data1": {
"data1-1": {
"a": 32.6,
"b": 28.36,
"c": 53.59,
"d": 36.48
}
},
"data2": {
"data2-1": {
"a": 283.0,
"b": 254.0,
"c": 288.5,
"d": 329.0
}
}
}
},
{
"id": 8442,
"version": "2.1.0",
"data": {
"data1": {
"data1-1": {
"a": 32.6,
"b": 28.36,
"c": 53.59,
"d": 36.48
}
},
"data2": {
"data2-1": {
"a": 283.0,
"b": 254.0,
"c": 288.5,
"d": 329.0
}
}
}
}
]
Each hash of the array has the same map structure.
I would like to create a new hash with the same hash map structure than data and for each values of a, b, c, d to have the average.
What is the best approach for this ? Because I cannot group_by key since I have the same key in different subkey (data1-1 and data2-1)
The result would then be :
{
"data1": {
"data1-1": {
"a": 65.9,
"b": 61.7,
"c": 86.9,
"d": 69.8
}
},
"data2": {
"data2-1": {
"a": 616.3,
"b": 587.3,
"c": 621.8,
"d": 662.3
}
}
}
I have tried this:
array.reduce({}) do |acc, hash|
hash[:data].each do |k,v|
acc[k] = v
end
end
# => {:data1=>{:"data1-1"=>{:a=>32.6, :b=>28.36, :c=>53.59, :d=>36.48}},
# :data2=>{:"data2-1"=>{:a=>283.0, :b=>254.0, :c=>288.5, :d=>329.0}}}
Let's group the hashes using .
grouped = array.each_with_object({}) do |h, acc|
h[:data].each do |k, v|
acc[k] ||= []
acc[k] << v
end
end
Result:
{:data1 => [{:"data1-1" => {:a=>132.6, :b=>128.36, :c=>153.59, :d=>136.48}},
{:"data1-1" => {:a=>32.6, :b=>28.36, :c=>53.59, :d=>36.48}},
{:"data1-1" => {:a=>32.6, :b=>28.36, :c=>53.59, :d=>36.48}}],
:data2 => [{:"data2-1" => {:a=>1283.0, :b=>1254.0, :c=>1288.5, :d=>1329.0}},
{:"data2-1" => {:a=>283.0, :b=>254.0, :c=>288.5, :d=>329.0}},
{:"data2-1" => {:a=>283.0, :b=>254.0, :c=>288.5, :d=>329.0}}]}
Now, let's transform those values.
grouped = grouped.transform_values do |arr|
k = arr.first.keys.first
arr.collect { |a| {k => a[k]} }.each_with_object({}) do |h, acc|
h.each do |k, v|
acc[k] ||= []
acc[k] << v
end
end
end
# => {:data1=>{:"data1-1"=>[{:a=>132.6, :b=>128.36, :c=>153.59, :d=>136.48},
# {:a=>32.6, :b=>28.36, :c=>53.59, :d=>36.48},
# {:a=>32.6, :b=>28.36, :c=>53.59, :d=>36.48}]},
# :data2=>{:"data2-1"=>[{:a=>1283.0, :b=>1254.0, :c=>1288.5, :d=>1329.0},
# {:a=>283.0, :b=>254.0, :c=>288.5, :d=>329.0},
# {:a=>283.0, :b=>254.0, :c=>288.5, :d=>329.0}]}}
This is much closer to your stated goal.
Let's transform some values again.
grouped = grouped.transform_values do |v|
k = v.keys.first
values = v.values.first.each_with_object({}) do |h, acc|
h.each do |hk, hv|
acc[hk] ||= []
acc[hk] << hv
end
end
{ k => values }
end
# => {:data1=>{:"data1-1"=>{:a=>[132.6, 32.6, 32.6],
# :b=>[128.36, 28.36, 28.36],
# :c=>[153.59, 53.59, 53.59],
# :d=>[136.48, 36.48, 36.48]}},
# :data2=>{:"data2-1"=>{:a=>[1283.0, 283.0, 283.0],
# :b=>[1254.0, 254.0, 254.0],
# :c=>[1288.5, 288.5, 288.5],
# :d=>[1329.0, 329.0, 329.0]}}}
Even closer. Averaging an array of numbers is easy. We just need to transform values.
Replacing the previous bit of code with:
grouped = grouped.transform_values do |v|
k = v.keys.first
values = v.values.first.each_with_object({}) do |h, acc|
h.each do |hk, hv|
acc[hk] ||= []
acc[hk] << hv
end
end
{ k => values.transform_values { |v| v.sum / v.size } }
end
# => {:data1=>{:"data1-1"=>{:a=>65.93333333333334,
# :b=>61.693333333333335,
# :c=>86.92333333333333,
# :d=>69.81333333333333}},
# :data2=>{:"data2-1"=>{:a=>616.3333333333334,
# :b=>587.3333333333334,
# :c=>621.8333333333334,
# :d=>662.3333333333334}}}
You could write that as follows.
fac = 1.fdiv(array.size)
#=> 0.3333333333333333
array.each_with_object({}) do |g,h|
g[:data].each do |k,v|
h[k] ||= {}
v.each do |kk,vv|
h[k][kk] ||= Hash.new(0)
vv.each { |kkk,vvv| h[k][kk][kkk] += fac * vvv }
end
end
end
#=> { :data1=> {
# :"data1-1"=> {
# :a=>65.93333333333334,
# :b=>61.693333333333335,
# :c=>86.92333333333333,
# :d=>69.81333333333332
# }
# },
# :data2=> {
# :"data2-1"=> {
# :a=>616.3333333333334,
# :b=>587.3333333333333,
# :c=>621.8333333333333,
# :d=>662.3333333333333
# }
# }
# }
Note that if arr is an array of numbers the average can be computed as
(arr.sum).fdiv(arr.size)
or as
fac = 1.fdiv(arr.size)
arr.sum { |m| fac * m }
I have employed the second method to avoid the need to construct intermediate arrays.
One may alternatively write the following.
array.each_with_object(Hash.new { |h,k| h[k] = {} }) do |g,h|
g[:data].each do |k,v|
v.each do |kk,vv|
h[k][kk] ||= Hash.new(0)
vv.each { |kkk,vvv| h[k][kk][kkk] += fac * vvv }
end
end
end
See Hash::new for explanations of Hash.new(0) and Hash.new { |h,k| h[k] = {} }.

Group array of hashes by value and retain structure (hash) in Ruby

I have a hash like this:
hash = {
'en-us': {
where: 'USA'
},
'en-us-zone2': {
where: 'USA'
},
'en-nl': {
where: 'Netherlands'
},
'en-pt': {
where: 'Portugal'
},
}
And I tried to group them using group_by:
result = hash.group_by { |k,v| v[:where] }
However, It returns full of array not by array of hashes. So here is expected and actual results:
Actual Result:
{ "USA"=>
[[:"en-us", {:where=>"USA"}], [:"en-us-zone2", {:where=>"USA"}]],
"Netherlands"=>
[[:"en-nl", {:where=>"Netherlands"}]],
"Portugal"=>
[[:"en-pt", {:where=>"Portugal"}]]
}
Expected Result:
{ "USA"=>
[{:"en-us" => {:where=>"USA"}}, {:"en-us-zone2" => {:where=>"USA"}}]
"Netherlands"=>
[{:"en-nl" => {:where=>"Netherlands"}}]
"Portugal"=>
[{:"en-pt" => {:where=>"Portugal"}}]
}
See, Actual is Array of arrays, instead of array of hashes. Hash keys becomes first array element.
How can I group my hash based on :where ?
This should work:
hash.group_by { |k,v| v[:where] }.each { |_, v| v.map! { |array| { array[0] => array[1] } } }
Or with transform_values
hash.group_by { |k,v| v[:where] }.transform_values { |v| v.map { |array| {array[0] => array[1] } } }
https://ruby-doc.org/core-2.4.0/Hash.html#method-i-transform_values
It's ugly but works:
hash = {
'en-us': {
where: 'USA'
},
'en-us-zone2': {
where: 'USA'
},
'en-nl': {
where: 'Netherlands'
},
'en-pt': {
where: 'Portugal'
},
}
hash.
group_by { |_, v| v[:where] }.
map do |k, v|
[
k,
v.map { |a, b| {a => b} }
]
end.to_h
# => {"USA"=>[{:"en-us"=>{:where=>"USA"}}, {:"en-us-zone2"=>{:where=>"USA"}}], "Netherlands"=>[{:"en-nl"=>{:where=>"Netherlands"}}], "Portugal"=>[{:"en-pt"=>{:where=>"Portugal"}}]}

Populating a hash from an array

I have this array:
params[:types] = [type1, type2, type3...]
I would like to populate my hash the following way using the above array:
params[:hash] = {
"type1" => {
something: something
},
"type2" => {
something: something
},
}
Using a for loop like for index in i ...params[:types] just populates the hash with the last value in the array.
You can use the each_with_object method to do this:
params = {}
params[:types] = ["type1", "type2", "type3"]
params[:types].each_with_object({}) { |k, h| h[k] = { "something" => "something" } }
That last line will return:
=> {"type1"=>{"something"=>"something"}, "type2"=>{"something"=>"something"}, "type3"=>{"something"=>"something"}}
Here is a code snippet example that does what you need.
hash = {}
array.each do |a|
hash[a.to_s] = { "something" => "something" }
end
output:
hash
=> {
"type1" => {
"something" => "something"
},
"type2" => {
"something" => "something"
},
"type3" => {
"something" => "something"
}
}
You could do this:
params = { types: ["type1", "type2", "type3"] }
Hash[params[:types].product([{"something" => "something"}])]
#=> {"type1"=>{"something"=>"something"},
# "type2"=>{"something"=>"something"},
# "type3"=>{"something"=>"something"}}
or with Ruby 2.1,
params[:types].product([{"something" => "something"}]).to_h
If you want a different hash for each element of params[:types]:
hashes = [{ "something1"=>"something1" }, { "something2"=>"something2" },
{ "something3"=>"something3" }]
then
Hash[params[:types].zip(hashes)]
#=> {"type1"=>{"something1"=>"something1"},
# "type2"=>{"something2"=>"something2"},
# "type3"=>{"something3"=>"something3"}}

Split an array of hashes in two based on the value of a key

How can split this array of hashes in two based on the value of the ate key?
array = [
{ name: "Gad", ate: true },
{ name: "Lad", ate: false },
{ name: "Bad", ate: true },
{ name: "Sad", ate: false }
]
Example output
array_1 = [
{ name: "Gad", ate: true },
{ name: "Bad", ate: true }
]
array_2 = [
{ name: "Lad", ate: false },
{ name: "Sad", ate: false }
]
Use the Enumerable#partition method:
array.partition { |x| x[:ate] }
# => [[{:name=>"Gad", :ate=>true}, {:name=>"Bad", :ate=>true}],
# [{:name=>"Lad", :ate=>false}, {:name=>"Sad", :ate=>false}]]
Or:
array_1, array_2 = array.partition { |x| x[:ate] }
array_1
# => [{:name=>"Gad", :ate=>true}, {:name=>"Bad", :ate=>true}]
array_2
# => [{:name=>"Lad", :ate=>false}, {:name=>"Sad", :ate=>false}]
array_one, array_two = *array.group_by { |x| x[:ate] }.map(&:last)
=> array_one
=> # [{:name=>"Gad", :ate=>true}, {:name=>"Bad", :ate=>true}]
=> array_two
=> # [{:name=>"Lad", :ate=>false}, {:name=>"Sad", :ate=>false}]
thx #CarySwoveland
I can't compete with partition, but here's another way:
trues = array.select { |h| h[:ate] }
falses = array - trues

How to sort an array by prioritizing specific attributes

So I'm getting records from ActiveRecord and I'd like to do something like:
VIP_LIST = ['John', 'Larry', 'Dan']
records = [
{ name: "Adam" },
{ name: "Larry" },
{ name: "John" },
{ name: "Eric" },
{ name: "Dan" }
]
# This is what I want to end up with:
sort_please(records, VIP_LIST)
=> [
{ name: "John" },
{ name: "Larry" },
{ name: "Dan" },
{ name: "Adam" },
{ name: "Eric" }
]
How can i achieve this?
P.S. There could be values in VIP_LIST that are not even in records
It's not a clever one liner, but it works:
VIP_LIST = ['John', 'Larry', 'Dan', 'Fred']
records = [
{ name: "Adam" },
{ name: "Larry" },
{ name: "John" },
{ name: "Eric" },
{ name: "Dan" }
]
sorted = records.sort_by do |record|
name = record[:name]
if VIP_LIST.include?(name)
VIP_LIST.index(name)
else
records.index(record) + VIP_LIST.length
end
end
p sorted # => [{:name=>"John"}, {:name=>"Larry"}, {:name=>"Dan"}, {:name=>"Adam"}, {:name=>"Eric"}]
try this:
records.sort_by do |x|
[VIP_LIST.index(x[:name]) || VIP_LIST.length, records.index(x)]
end
# => [{:name=>"John"}, {:name=>"Larry"}, {:name=>"Dan"}, {:name=>"Adam"}, {:name=>"Eric"}]
this one?
new_arr = []
VIP_LIST.each do |v|
new_arr << records.select {|r| r[:name] == v } unless nil?
end
new_arr.flatten!
new_arr = new_arr + (records - new_arr)
I think this is both clear and concise:
vip = records.select{ |hash| VIP_LIST.include? hash[:name] }
vip = vip.sort_by{|x| VIP_LIST.find_index(x[:name])} #reorder vip array
sorted_records = (vip + records).uniq

Resources