I have a list of objects something like this:
a = [
{
id: 0,
name: "ABC",
work: "ABC2"
},
{
id: 0,
name: "XYZ",
work: "XYZ2"
},
{
id: 1,
name: "ACD",
work: "ACD2"
}
]
And I want to convert it into something like this:
b = [
{
id: 0,
fields: [
{
name: "ABC",
work: "ABC2"
},
{
name: "XYZ",
work: "XYZ2"
}
]
},
{
id: 1,
fields: [
{
name: "ACD",
work: "ACD2"
}
]
}
]
The idea is to group the objects (by id) in one array.
The approach I tried is:
b = []
rest_object = []
a.each_with_index do |aa, idx|
aa.delete(:id)
rest_object << aa
if idx == 0
next
end
puts a[idx][:id], a[idx-1][:id]
if a[idx][:id] != a[idx-1][:id]
b << {id: a[idx-1][:id], names: rest_object}
rest_object = []
end
end
But I am getting an empty output.
Also, if it is possible to achieve the same in some cleaner way.
That would be helpful.
Something like this does the job. This deletes the :id key-value pair from each hash and uses the value to group the remainder of the hash. Then map the resulting hash to created an array and transform the data into {id: ..., fields: ...} format.
a = [{id: 0, name: "ABC", work: "ABC2"}, {id: 0, name: "XYZ", work: "XYZ2"}, {id: 1, name: "ACD", work: "ACD2"}]
b = a.group_by { |hash| hash.delete(:id) }
.map { |id, fields| {id: id, fields: fields} }
#=> [{:id=>0, :fields=>[{:name=>"ABC", :work=>"ABC2"}, {:name=>"XYZ", :work=>"XYZ2"}]}, {:id=>1, :fields=>[{:name=>"ACD", :work=>"ACD2"}]}]
Note: This mutates the hashes in the a array. If you don't want this change a.group_by to a.map(&:dup).group_by. Which first duplicates all hashes before doing any mutations.
Try following,
required_keys = a[0].except(:id).keys
b = a.group_by { |x| x[:id] }
b = b.inject([]) do |m,(k,v)|
arr = { id: k }
required_keys.each do |key|
arr[key.to_s.pluralize.to_sym] = v.map { |z| z.slice(key) }
end
m << arr
end
# => [{:id=>0, :names=>[{:name=>"ABC"}, {:name=>"XYZ"}]}, {:id=>1, :names=>[{:name=>"ACD"}]}]
a = [
{
id: 0,
name: "ABC"
},
{
id: 0,
name: "XYZ"
},
{
id: 1,
name: "ACD"
}
]
ary = []
a.each do|val|
ary[val[:id]] = {id: val[:id]} unless ary[val[:id]]
ary[val[:id]][:names] = [] unless ary[val[:id]][:names]
ary[val[:id]][:names].push({name: val[:name]})
end
If I understand, given a more general array like this:
a = [ { id: 0, name: "ABC", etc: "01" },
{ id: 0, name: "XYZ", etc: "02" },
{ id: 1, name: "ACD", etc: "11" } ]
The first idea I came up with is doing something like this:
require 'active_support/inflector' # for using pluralize
res = a.group_by{ |h| h[:id] }.values.map do |ary|
h = Hash.new{ |h,k| h[k] = [] }
ary.each { |hh| hh.each { |k,v| h[k] << v } }
h[:id] = h[:id].first
h.transform_keys do |k|
unless k == :id
k.to_s.pluralize.to_sym
else
k
end
end
end
res #=> [{:id=>0, :names=>["ABC", "XYZ"], :etcs=>["01", "02"]}, {:id=>1, :names=>["ACD"], :etcs=>["11"]}]
This is not exactly the format you require, but to get that format, just change this line
ary.each { |hh| hh.each { |k,v| h[k] << v } }
to
ary.each { |hh| hh.each { |k,v| h[k] << { k => v } } }
Related
I want to merge the ids of same name values
hashes = [{
id: 3456824,
name: 'John'
},{
id: 6578954,
name: 'Vicky'
},{
id: 987456,
name: 'John'
}]
Expected:
[{
id: [3456824,987456],
name: 'John'
},{
id: 6578954,
name: 'Vicky'
}]
how I can achieve this in ruby on rails?
Here is a one liner:
hashes = [{
id: 3456824,
name: 'John'
},{
id: 6578954,
name: 'Vicky'
},{
id: 987456,
name: 'John'
}]
result = hashes.group_by{|h| h[:name] }.map{|k, v| {id: v.map{|x| x[:id]}, name: k}}
puts result
Check this repl: https://repl.it/repls/ShadowyCornyVideogames
Here are two ways to compute the desired result.
Use the form of Hash::new that takes a block
hashes.each_with_object(Hash.new { |h,k| h[k] = [] }) do |g,h|
h[g[:name]] << g[:id]
end.map { |name,id| { id: id, name: name } }
#=> [{:id=>[3456824, 987456], :name=>"John"},
# {:id=>[6578954], :name=>"Vicky"}]
The first step of this calculation1 is
hashes.each_with_object(Hash.new { |h,k| h[k] = [] }) do |g,h|
h[g[:name]] << g[:id]
end
#=> {"John"=>[3456824, 987456], "Vicky"=>[6578954]}
If a hash is defined
h = Hash.new { |h,k| h[k] = [] }
and (possibly after having added key-value pairs) h has no key k, h[k] in
h[k] << v
causes the block { |h,k| h[k] = [] } to be executed, resulting in the key value pair k=>[] being added to h, then << v is executed, changing h[k] from [] to [k].
Notice that this returns :id=>[6578954], rather than :id=>6578954, which was asked for by the question. Having all values of :id return an array avoids the need to check if :id returns an array or integer in subsequent code that processes the return value of this operation.
If :id=>6578954, were desired, one could write
hashes.each_with_object(Hash.new { |h,k| h[k] = [] }) do |g,h|
h[g[:name]] << g[:id]
end.transform_values { |v| v.size==1 ? v.first : v }.
map { |name,id| { id: id, name: name } }
#=> [{:id=>[3456824, 987456], :name=>"John"},
# {:id=>6578954, :name=>"Vicky"}]
See Hash#transform_values.
Use the form of Hash#update (a.k.a. merge!) that employs a block to determine the values of keys that are present in both hashes being merged
arr.each_with_object({}) do |g,h|
h.update(g[:name]=>[g[:id]]) { |_,o,n| o+n }
end.map { |name,id| { id: id, name: name } }
#=> [{:id=>[3456824, 987456], :name=>"John"},
# {:id=>[6578954], :name=>"Vicky"}]
If :id=>6578954, rather than :id=>[6578954], were desired:
arr.each_with_object({}) do |g,h|
h.update(g[:name]=>g[:id]) { |_,o,n| [*o,n] }
end.map { |name,id| { id: id, name: name } }
#=> [{:id=>[3456824, 987456], :name=>"John"},
# {:id=>6578954, :name=>"Vicky"}]
Notice that here update's argument is g[:name]=>g[:id] whereas it was previously g[:name]=>[g[:id]].
The first step is as follows.
arr.each_with_object({}) do |g,h|
h.update(g[:name]=>g[:id]) { |_,o,n| [*o,n] }
end
#=> {"John"=>[3456824, 987456], "Vicky"=>6578954}
In general, one or both of these approaches can be taken when Enumerable#group_by can be used. The reverse if often true also. The choice among these methods is a matter of personal taste.
1. A variant of the first part of this calculation is hashes.each_with_object({}) { |g,h| (h[g[:name]] ||= []) << g[:id] } #=> {"John"=>[3456824, 987456], "Vicky"=>[6578954]}.
You can do it like this:
args = [{
id: 3456824,
name: 'John'
},{
id: 6578954,
name: 'Vicky'
},{
id: 987456,
name: 'John'
}]
value_pairs = args.map { |h| h.values_at(:name, :id) }
grouped_by_name = value_pairs.group_by(&:first).transform_values { |arr| arr.map(&:last) }
as_hashes = grouped_by_name.map { |name, ids| { id: ids, name: name } }
One more possible solution is:
array = [
{
id: 3456824,
name: "John"
},
{
id: 6578954,
name: "Vicky"
},
{
id: 987456,
name: "John"
}
]
grouped_by_name = array.each_with_object(Hash.new {|h,k| h[k] = [] }) do |hash, result|
result[hash[:name]] << hash[:id]
end
=> {"John"=>[3456824, 987456], "Vicky"=>[6578954]}
grouped_by_name.map do |grouped_hash|
{
id: grouped_hash.last,
name: grouped_hash.first
}
end
=> [{:id=>[3456824, 987456], :name=>"John"}, {:id=>[6578954], :name=>"Vicky"}]
My data is like:
h = { themes_data: {
Marketing: [
{
id: 68,
projectno: "15",
}
],
Produktentwicklung: [
{
id: 68,
projectno: "15",
},
{
id: 4,
projectno: "3",
}
],
Marketing_summary: [
{
ges: {
result: "47.6"
},
theme: "Marketing"
}
],
Produktentwicklung_summary: [
{
ges: {
result: "87.7"
},
theme: "Produktentwicklung"
}
]
}
}
And my output should be like:
{ "marketing" => [
{
id: 68,
projectno: "15",
},
{
ges: {
result: "47.6"
},
theme: "Marketing"
}
],
"Produktentwicklung" => [
{
id: 68,
projectno: "15"
},
{
id: 4,
projectno: "3",
},
{
ges: {
result: "87.7"
},
theme: "Produktentwicklung"
}
]
}
Code:
def year_overview_theme
branch_hash = {}
#themes_data.each do |td|
arr = []
td[1].map do |dt|
arr << [{content: dt[:projectno], size: 5, align: :right, background_color: 'D8E5FF'}]
end
branch_hash["#{td[0]}"] = arr
end
branch_hash
end
The problem is that it does not iterate for right hash key.
For example, i want like:
marketing + marketing_summary as 1 hash and similarly
Produktentwicklung = Produktentwicklung_summary as one hash but there is some problem in my logic.
Is there a way that I can check like after 2 iteration,
it should do arr << data with branch_hash["#{td[0]}"] = arr ?
The desired hash can be constructed as follows.
h[:themes_data].each_with_object({}) { |(k,v),g|
g.update(k.to_s[/[^_]+/]=>v) { |_,o,n| o+n } }
#=> { "Marketing"=>[
# {:id=>68, :projectno=>"15"},
# {:ges=>{:result=>"47.6"}, :theme=>"Marketing"}
# ],
# "Produktentwicklung"=>[
# {:id=>68, :projectno=>"15"},
# {:id=>4, :projectno=>"3"},
# {:ges=>{:result=>"87.7"}, :theme=>"Produktentwicklung"}
# ]
# }
This uses the form of Hash#update (aka merge) that employs a block to determine the values of keys that are present in both hashes being merged. Here that block is:
{ |_,o,n| o+n }
The first block variable, _, is the common key. I have represented it with an underscore (a valid local variable) to tell the reader that it is not used in the block calculation. That is common practice. The values of the other two block variables, o and n, are explained at the link for the method update.
The regular expression /[^_]+/, matches one or more characters from the start of the string that are not (^) underscores. When used with the method String#[], we obtain:
"Marketing"[/[^_]+/] #=> "Marketing"
"Marketing_summary"[/[^_]+/] #=> "Marketing"
Let me start with a note: This looks to me like something that should rather be solved in SQL (if it's coming from SQL) instead of Ruby.
With that out of the way, here's a solution that should work:
output = {}
themes_data.each do |theme, projects|
projects.each do |project|
key = project[:theme] || theme.to_s
output[key] ||= [] # make sure the target is initialized
output[key] << project
end
end
There would probably be more elegant solutions using reduce or each_with_object but this works and it's simple enough.
keys = themes_data.keys
summary_keys = themes_data.keys.grep(/_summary/)
result = {}.tap do |hash|
(keys - summary_keys).each do |key|
hash[key] = themes_data[key] + themes_data["#{key}_summary".to_sym]
end
end
I want to create a nested hash using four values type, name, year, value. ie, key of the first hash will be type, value will be another hash with key name, then value of that one will be another hash with key year and value as value.
The array of objects I'm iterating looks like this:
elements = [
{
year: '2018',
items: [
{
name: 'name1',
value: 'value1',
type: 'type1',
},
{
name: 'name2',
value: 'value2',
type: 'type2',
},
]
},
{
year: '2019',
items: [
{
name: 'name3',
value: 'value3',
type: 'type2',
},
{
name: 'name4',
value: 'value4',
type: 'type1',
},
]
}
]
And I'm getting all values together using two loops like this:
elements.each do |element|
year = element.year
element.items.each |item|
name = item.name
value = item.value
type = item.type
# TODO: create nested hash
end
end
Expected output is like this:
{
"type1" => {
"name1" => {
"2018" => "value1"
},
"name4" => {
"2019" => "value4"
}
},
"type2" => {
"name2" => {
"2018" => "value2"
},
"name3" => {
"2019" => "value3"
}
}
}
I tried out some methods but it doesn't seems to work out as expected. How can I do this?
elements.each_with_object({}) { |g,h| g[:items].each { |f|
h.update(f[:type]=>{ f[:name]=>{ g[:year]=>f[:value] } }) { |_,o,n| o.merge(n) } } }
#=> {"type1"=>{"name1"=>{"2018"=>"value1"}, "name4"=>{"2019"=>"value4"}},
# "type2"=>{"name2"=>{"2018"=>"value2"}, "name3"=>{"2019"=>"value3"}}}
This uses the form of Hash#update (aka merge!) that employs a block (here { |_,o,n| o.merge(n) } to determine the values of keys that are present in both hashes being merged. See the doc for definitions of the three block variables (here _, o and n). Note that in performing o.merge(n) o and n will have no common keys, so a block is not needed for that operation.
Assuming you want to preserve the references (unlike in your desired output,) here you go:
elements = [
{
year: '2018',
items: [
{name: 'name1', value: 'value1', type: 'type1'},
{name: 'name2', value: 'value2', type: 'type2'}
]
},
{
year: '2019',
items: [
{name: 'name3', value: 'value3', type: 'type2'},
{name: 'name4', value: 'value4', type: 'type1'}
]
}
]
Just iterate over everything and reduce into the hash. On the structures of known shape is’s a trivial task:
elements.each_with_object(
Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) } # for deep bury
) do |h, acc|
h[:items].each do |item|
acc[item[:type]][item[:name]][h[:year]] = item[:value]
end
end
#⇒ {"type1"=>{"name1"=>{"2018"=>"value1"},
# "name4"=>{"2019"=>"value4"}},
# "type2"=>{"name2"=>{"2018"=>"value2"},
# "name3"=>{"2019"=>"value3"}}}
I've written an API to organize my daily user data.
The origin format is like
"dau": {
"2017-05-02": 1,
"2017-05-04": 2,
"2017-05-05": 2,
}
"new_user": {
"2017-05-02": 1,
"2017-05-04": 0,
"2017-05-07": 0,
}
It's hard to display in HTML table row by row.
Therefore, I want the format to become this one.
However, I have no idea how to deal with.
info: {
"2017-05-02": {
dau: 1,
new_user: 1
},
"2017-05-04": {
dau: 2,
new_user: 0
},
"2017-05-05": {
dau: 2
},
"2017-05-07": {
new_user: 0
}
}
suppose you have data, then
(data['dau'].keys + data['new_user'].keys).uniq.map { |k| [k, { dau: data['dau'][k].to_i, new_user: data['new_user'][k].to_i } ] }.to_h
and if you don't want default/0 values then,
(data['dau'].keys + data['new_user'].keys).uniq.map { |k| [k, { dau: data['dau'][k], new_user: data['new_user'][k] }.compact ] }.to_h
Output:
{
"2017-05-02" => {
:dau => 1,
:new_user => 1
},
"2017-05-04" => {
:dau => 2,
:new_user => 0
},
"2017-05-05" => {
:dau => 2
},
"2017-05-07" => {
:new_user => 0
}
}
Hope it helps..
keys = (dau.keys + new_user.keys).uniq
# [:"2017-05-02", :"2017-05-04", :"2017-05-05", :"2017-05-07"]
keys.each_with_object({}) do |key, result|
result[key] = {dau: dau[key], new_user: new_user[key] }.compact
end
# {:"2017-05-02"=>{:dau=>1, :new_user=>1},
# :"2017-05-04"=>{:dau=>2, :new_user=>0},
# :"2017-05-05"=>{:dau=>2},
# :"2017-05-07"=>{:new_user=>0}}
Here's an alternative with each_with_object. It should work with any number of keys:
data = {"dau": {
"2017-05-02": 1,
"2017-05-04": 2,
"2017-05-05": 2
},
"new_user": {
"2017-05-02": 1,
"2017-05-04": 0,
"2017-05-07": 0
}
}
info = Hash.new { |h, k| h[k] = {} }
info = data.each_with_object(info) do |(key, sub_h), h|
sub_h.each do |date, i|
h[date][key] = i
end
end
p info
# {:"2017-05-02"=>{:dau=>1, :new_user=>1}, :"2017-05-04"=>{:dau=>2, :new_user=>0}, :"2017-05-05"=>{:dau=>2}, :"2017-05-07"=>{:new_user=>0}}
I am trying to left join the following arrays of hashes:
input:
a = [{id: 1, name: 'Bob'}, {id: 2, name: 'Jack'}, {id: 3, name: 'Tom'}]
b = [{id: 3, age: 12}, {id: 2, age: 7}]
output:
[{id: 1, name: 'Bob', age: nil}, {id: 2, name: 'Jack', age: 7}, {id: 3, name: 'Tom', age: 12}]
Currently I am doing something along the lines with:
a.map do |x|
{
id: x[:id],
name: x[:name],
age: (b.detect{|y| x[:id] == y[:id]} || {age: nil}).fetch(:age)
}
end
It works, but it is super slow when the data set is large.
Is there any better way to perform the "join" operation more efficiently?
[a, b].map { |a| a.group_by { |e| e[:id] } }
.reduce do |a, b|
a.merge(b) { |_, v1, v2| v1.first.merge v2.first }
end.values
.map do |e|
Array === e ? {age:nil, name:nil}.merge(e.first) : e
end
The whole preparation step takes O(N) and the merge then is done as O(N), plus the finalization takes O(N).
h = b.each_with_object({}) { |g,h| h[g[:id]] = g[:age] }
#=> {3=>12, 2=>7}
a.map { |g| g.merge(age: h[g[:id]]) }
#=> [{:id=>1, :name=>"Bob", :age=>nil},
# {:id=>2, :name=>"Jack", :age=>7},
# {:id=>3, :name=>"Tom", :age=>12}]
If a is to be modified in place, change the second line to
a.each { |g| g[:age] = h[g[:id]] }
a #=> [{:id=>1, :name=>"Bob", :age=>nil},
# {:id=>2, :name=>"Jack", :age=>7},
# {:id=>3, :name=>"Tom", :age=>12}]