How to map ruby hashes correctly based on key provided - ruby-on-rails

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

Related

Merge a single array into nested array in rails

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

Iterate through a hash. However, my value is changing every time

I'm currently working on a simple hash loop, to manipulate some json data. Here's my Json data:
{
"polls": [
{ "id": 1, "question": "Pensez-vous utiliser le service de cordonnerie/pressing au moins 2 fois par mois ?" },
{ "id": 2, "question": "Avez-vous passé une bonne semaine ?" },
{ "id": 3, "question": "Le saviez-vous ? Il existe une journée d'accompagnement familial." }
],
"answers": [
{ "id": 1, "poll_id": 1, "value": true },
{ "id": 2, "poll_id": 3, "value": false },
{ "id": 3, "poll_id": 2, "value": 3 }
]
}
I want to have the poll_id value and the value from the answers hash. So here's what I code :
require 'json'
file = File.read('data.json')
datas = JSON.parse(file)
result = Hash.new
datas["answers"].each do |answer|
result["polls"] = {"id" => answer["poll_id"], "value" => answer["value"]}
end
polls_json = result.to_json
However, it returns me :
{
"polls": {
"id": 2,
"value": 3
}
}
Here's the output i am looking for :
{
"polls": [
{
"id": 1,
"value": true
},
{
"id": 2,
"value": 3
},
{
"id": 3,
"value": false
}
]
}
It seems that the value is not saved into my loop. I've tried different method but I still cannot find a solution .. Any suggestions?
You should be using reduce here, i.e.
datas["answers"].reduce({ polls: [] }) do |hash, data|
hash[:polls] << { id: data["poll_id"], value: data["value"] }
hash
end
This method iterates through the answers, making available the object supplied to reduce (in this case a hash with a :polls array) to which we pass each data hash.
I'd personally, um, reduce this a little further with the following, although it's at some cost to readability:
datas["answers"].reduce({ polls: [] }) do |hash, data|
hash.tap { |h| h[:polls] << { id: data["poll_id"], value: data["value"] } }
end
It's the cleanest method to achieve what you're looking for, using a built-for-purpose method.
Docs for reduce here: https://ruby-doc.org/core-2.1.0/Enumerable.html#method-i-reduce
(I'd also be inclined to update the variable names - data is already plural, so 'datas' is a little confusing to anyone else coming to your code.)
Edit: #max makes a great point re symbol / string keys from your data - keep that in mind if you attempt to apply this.
try the below:
require 'json'
file = File.read('data.json')
datas = JSON.parse(file)
result = Hash.new
poll_json = []
datas["answers"].each do |answer|
poll_json << {"id" => answer["poll_id"], "value" => answer["value"]}
end
p "json = "#{poll_json}"
{
polls: datas["answers"].map do |a|
{ id: a["poll_id"], value: a["value"] }
end
}
In general use .map to iterate through arrays and hashes and return new objects. .each should only be used when you are only concerned about the side effects (like in a view when you are outputting values).
require 'json'
json = JSON.parse(File.read('data.json'))
result = {
polls: json["answers"].map do |a|
{ id: a["poll_id"], value: a["value"] }
end
}
puts result.to_json
The output is:
{"polls":[{"id":1,"value":true},{"id":3,"value":false},{"id":2,"value":3}]}

Hash modification issue in hash, replacing value in ruby

I would like to get rid of the value: <value> key value inside each of the attributes in the hash. And make it like this: "total_interactions": 493.667
Below is the incorrect format followed by the expected good format I hope to achieve in json.
{
"3": {
"total_interactions": {
"value": 493.667
},
"shares": {
"value": 334
},
"comments": {
"value": 0
},
"likes": {
"value": 159.66666666666666
},
"total_documents": 6
},
"4": {
"total_interactions": {
"value": 701
},
"shares": {
"value": 300
},
"comments": {
"value": 0
},
"likes": {
"value": 401
},
"total_documents": 1
}
}
I want it to be like this:
{
"3": {
"total_interactions": 493.6666666666667,
"shares": 334,
"comments": 0,
"likes": 159.66666666666666,
"total_documents": 6
},
"4": {
"total_interactions": 701,
"shares": 300,
"comments": 0,
"likes": 401,
"total_documents": 1
}
}
Here is the code that is supposed to do this but is not working. Nothing affected. Not sure what is wrong
# the result_hash variable is the first hash with value: <value>
result_hash.each do |hash_item|
hash_item.each do |key,value_hash|
if( !value_hash.nil? )
value_hash.each do |k,v|
hash_item[key] = v
end
end
end
end
Max Williams' code is perfect for in-place. You can also do this in functional style to get a new, corrected hash:
hash.merge(hash) {|k,v| v.merge(v) {|kk,vv| vv.is_a?(Hash) && vv['value'] ? vv['value'] : vv }}
hash = {"3"=>{"total_documents"=>6, "comments"=>{"value"=>0}, "total_interactions"=>{"value"=>493.667}, "shares"=>{"value"=>334}, "likes"=>{"value"=>159.666666666667}},
"4"=>{"total_documents"=>1, "comments"=>{"value"=>0}, "total_interactions"=>{"value"=>701}, "shares"=>{"value"=>300}, "likes"=>{"value"=>401}}}
hash.each do |k,v|
v.each do |k2, v2|
if v2.is_a?(Hash) && v2["value"]
hash[k][k2] = v2["value"]
end
end
end
after this:
hash = {"3"=>{"total_documents"=>6, "comments"=>0, "total_interactions"=>493.667, "shares"=>334, "likes"=>159.666666666667},
"4"=>{"total_documents"=>1, "comments"=>0, "total_interactions"=>701, "shares"=>300, "likes"=>401}}
If you do not wish to mutate your initial hash, h,you can do this:
h.each_with_object({}) { |(k,v),g|
g[k] = v.each_with_object({}) { |(kk,vv),f|
f[kk] = (Hash === vv) ? vv[:value] : vv } }
#=> {:"3"=>{:total_interactions=>493.667,
# :shares=>334,
# :comments=>0,
# :likes=>159.66666666666666,
# :total_documents=>6},
# :"4"=>{:total_interactions=>701,
# :shares=>300,
# :comments=>0,
# :likes=>401,
# :total_documents=>1}}

Adding elements to an array in ruby

I receive the following object
#list =
{
"total": 2,
"items": [
{
"name": "hello",
"values": [
{
"value": "100"
}
]
},
{
"name": "SOME NAME",
"values": [
{
"value": "120"
}
]
}
]
}
I want to create and array of all the values. Something like [100,120]. How do I do that.
I tried,
- list.items.each do |item, arr = [], name = []|
-arr.push(item.values); name.push(item.name)
- y = arr
- x = names
In this case,
I get only one value at a time. in the first iteration I have [100] and in the second iteration I have [120]. What am I doing wrong.?
Update: I've edited what I was trying to do. Thanks!
Both y and x should contain the array of values and names respectively.
Here we need 4 things:
Getting a key from a hash: hash[:key]
Array#map: [1, 2, 3].map { |number| number**2 } # => [1, 4, 9]
Array#flatten: [[1, 2], [3, 4]].flatten # => [1, 2, 3, 4]
String#to_i: '44'.to_i # => 44
#list[:items].map { |item| item[:values].map { |value| value[:value] } }.flatten.map(&:to_i)
You could do that like so:
#list[:items].map { |h| h[:values].first[:value].to_i }
#=> [100, 120]
If, unlike the example, h[:values] could have more than one element:
#list[:items].flat_map { |h| h[:values].map { |g| g[:value].to_i } }

Hash remove all except specific keys

I would like to remove every key from a hash except a given key.
For example:
{
"firstName": "John",
"lastName": "Smith",
"age": 25,
"address":
{
"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY",
"postalCode": "10021"
},
"phoneNumber":
[
{
"type": "home",
"number": "212 555-1234"
},
{
"type": "fax",
"number": "646 555-4567"
}
]
}
I want to remove everything except "firstName" and/or "address".
What about slice?
hash.slice('firstName', 'lastName')
# => { 'firstName' => 'John', 'lastName' => 'Smith' }
Available in Ruby since 2.5
Some other options:
h.select {|k,v| ["age", "address"].include?(k) }
Or you could do this:
class Hash
def select_keys(*args)
select {|k,v| args.include?(k) }
end
end
So you can now just say:
h.select_keys("age", "address")
If you use Rails, please consider ActiveSupport except() method: http://apidock.com/rails/Hash/except
hash = { a: true, b: false, c: nil}
hash.except!(:c) # => { a: true, b: false}
hash # => { a: true, b: false }
Hash#select does what you want:
h = { "a" => 100, "b" => 200, "c" => 300 }
h.select {|k,v| k > "a"} #=> {"b" => 200, "c" => 300}
h.select {|k,v| v < 200} #=> {"a" => 100}
Edit (for comment):
assuming h is your hash above:
h.select {|k,v| k == "age" || k == "address" }
hash = { a: true, b: false, c: nil }
hash.extract!(:c) # => { c: nil }
hash # => { a: true, b: false }
Inspired by Jake Dempsey's answer, this one should be faster for large hashes, as it only peaks explicit keys rather than iterating through the whole hash:
class Hash
def select_keys(*args)
filtered_hash = {}
args.each do |arg|
filtered_hash[arg] = self[arg] if self.has_key?(arg)
end
return filtered_hash
end
end
No Rails needed to get a very concise code:
keys = [ "firstName" , "address" ]
# keys = hash.keys - (hash.keys - keys) # uncomment if needed to preserve hash order
keys.zip(hash.values_at *keys).to_h

Resources