Ruby - Convert Hash with array values to array of hashes - ruby

Let's say I have this hash:
def aliases
{
blond: [
'dark blond',
'dirty blond',
'honey blond',
'sandy blond',
'stawberry blond'
],
brown: [
'dark brown',
'light brown'
],
gray: [
'grey'
]
}
end
What is the most elegant way to convert it to:
[ { blond: 'dark blond' }, { blond: 'dirty blond' }, { brown: 'dark brown' } ... ]

aliases.flat_map { |k,v| v.map { |s| { k=>s } } }
#=> [{:blond=>"dark blond"}, {:blond=>"dirty blond"}, {:blond=>"honey blond"},
# {:blond=>"sandy blond"}, {:blond=>"stawberry blond"}, {:brown=>"dark brown"},
# {:brown=>"light brown"}, {:gray=>"grey"}]

Related

How to change output of the array hash using the map or group_by in ruby?

I have this array of hash where i have to separate same value object and group it by country base on date.
I try to use group_by but i only manage to get the countries i cannot separate the reported_date
outside of array hash.
notice one of the object is thesame
"reported_date": "2020-04-01"
i want it to be outside of every hash so it only print once.
heres my hash
[
{
"reported_date": "2020-04-01",
"country": "Italy",
"confirmed": 110574,
"deaths": 13155,
"recovered": 16847
},
{
"reported_date": "2020-04-01",
"country": "Spain",
"confirmed": 104118,
"deaths": 9387,
"recovered": 22647
},
{
"reported_date": "2020-04-01",
"country": "US",
"confirmed": 83948,
"deaths": 1941,
"recovered": 0
}
]
and i want to change something like this
{
"reported_date": "2020-04-01",
"countries": [
{
"country": "Italy",
"confirmed": 110574,
"deaths": 13155,
"recovered": 16847
},
{
"country": "Spain",
"confirmed": 104118,
"deaths": 9387,
"recovered": 22647
},
{
"country": "US",
"confirmed": 83948,
"deaths": 1941,
"recovered": 0
}
]
}
[
{
"reported_date": "2020-04-01",
"country": "Italy",
"confirmed": 110574,
"deaths": 13155,
"recovered": 16847
},
{
"reported_date": "2020-04-01",
"country": "Spain",
"confirmed": 104118,
"deaths": 9387,
"recovered": 22647
},
{
"reported_date": "2020-04-01",
"country": "US",
"confirmed": 83948,
"deaths": 1941,
"recovered": 0
}
].group_by { |h| h[:reported_date] }.map do |date, rest|
{
reported_date: date,
countries: rest.map { |h| h.reject! { |k| k == :reported_date } }
}
end
Returns
[
{
:reported_date => "2020-04-01",
:countries => [
{
:country => "Italy",
:confirmed => 110574,
:deaths => 13155,
:recovered => 16847
},
{
:country => "Spain",
:confirmed => 104118,
:deaths => 9387,
:recovered => 22647
},
{
:country => "US",
:confirmed => 83948,
:deaths => 1941,
:recovered => 0
}
]
}
]
Suppose your array were:
arr = [
{ date: "2020-04-01", country: "Italy", confirmed: 110574, deaths: 13155},
{ date: "2020-04-02", country: "Spain", confirmed: 104118, deaths: 9387},
{ date: "2020-04-01", country: "US", confirmed: 83948, deaths: 1941},
{ date: "2020-04-02", country: "Italy", confirmed: 120431, deaths: 13394}
]
Depending on your needs you might find it more convenient to reorganize your data, possibly in one of the following two ways.
#1
keeper_keys = arr.first.keys - [:date, :country]
#=> [:confirmed, :deaths]
h = arr.each_with_object({}) { |g,h| h[g[:date]] =
(h[g[:date]] || {}).merge(g[:country]=>g.slice(*keeper_keys)) }
#=> {"2020-04-01"=>{
# "Italy"=>{:confirmed=>110574, :deaths=>13155},
# "US"=>{:confirmed=>83948, :deaths=>1941}
# },
# "2020-04-02"=>{
# "Spain"=>{:confirmed=>104118, :deaths=>9387},
# "Italy"=>{:confirmed=>120431, :deaths=>13394}
# }
# }
h[g[:date]] || {} returns an empty hash if h has no key g[:date].
This allows us to easily compute, for example:
Deaths by date and country
p = h.transform_values { |g| g.transform_values { |f| f[:deaths] } }
#=> {"2020-04-01"=>{"Italy"=>13155, "US"=>1941},
# "2020-04-02"=>{"Spain"=>9387, "Italy"=>13394}}
Total deaths by date
p.transform_values { |g| g.values.sum }
#=> {"2020-04-01"=>15096, "2020-04-02"=>22781}
#2
keeper_keys = arr.first.keys - [:date, :country]
#=> [:confirmed, :deaths]
h = arr.each_with_object({}) { |g,h| h[g[:country]] =
(h[g[:country]] || {}).merge(g[:date]=>g.slice(*keeper_keys)) }
#=> {"Italy"=>{
# "2020-04-01"=>{:confirmed=>110574, :deaths=>13155},
# "2020-04-02"=>{:confirmed=>120431, :deaths=>13394}
# },
# "Spain"=>{
# "2020-04-02"=>{:confirmed=>104118, :deaths=>9387}
# },
# "US"=>{
# "2020-04-01"=>{:confirmed=>83948, :deaths=>1941}
# }
# }
h[g[:country]] || {} returns an empty hash if h has no key g[:country].
This allows us to easily compute:
Deaths by country and date
p = h.transform_values { |g| g.transform_values { |f| f[:deaths] } }
#=> {"Italy"=>{"2020-04-01"=>13155, "2020-04-02"=>13394},
# "Spain"=>{"2020-04-02"=>9387},
# "US"=>{"2020-04-01"=>1941}}
Notice that this expression is the same as the one used earlier to obtain deaths by date and country.
Total deaths by date
p.transform_values { |g| g.values.sum }
#=> {"Italy"=>26549, "Spain"=>9387, "US"=>1941}
Again, this is the same expression as the one used earlier to compute total deaths by country.
Better yet, use a database!

Algorithm to transform tree data in Ruby

How can i change my tree made of Array of hashes into another structure such as:
My data looks like :
{
"A": [
{ "A1": [] },
{ "A2": [] },
{
"A3": [
{
"A31": [
{ "A311": [] },
{ "A312": [] }
]
}
]
}
]
}
into something like :
{
"name": "A",
"children": [
{ "name": "A1" },
{ "name": "A2" },
{
"name": "A3",
"children": [
{
"name": "A31",
"children": [
{ "name": "A311" },
{ "name": "A312" }
]
}
]
}
]
}
I tried a few things but nothing worked as I hoped.
This is how i move into my tree
def recursive(data)
return if data.is_a?(String)
data.each do |d|
keys = d.keys
keys.each do |k|
recursive(d[k])
end
end
return data
end
I tried my best to follow how to ask so to clarify :
The tree can have a unlimited deeph
Names are more complexe than A1, A2 ...
λ = ->(h) { [h[:name], h[:children] ? h[:children].map(&λ).to_h : []] }
[λ.(inp)].to_h
#⇒ {
# "A" => {
# "A1" => [],
# "A2" => [],
# "A3" => {
# "A31" => {
# "A311" => [],
# "A312" => []
# }
# }
# }
# }
This solution returns hashes that are not wrapped in arrays inside. If you really want to wrap nested hashes with arrays, map them in λ.
When you don't know how to implement something, always think the simplest case first.
Step 1: Convert {"A1" => []} to{"name" => "A1", "children" => []}
This is simple
def convert(hash)
pair = hash.each_pair.first
["name", "children"].zip(pair).to_h
end
Step2: Recursively convert all hashes in children
def convert(hash)
pair = hash.each_pair.first
pair[1] = pair[1].map{|child| convert(child)}
["name", "children"].zip(pair).to_h
end
Step 3: Handle corner cases
If children is empty then omit it.
def convert(hash)
pair = hash.each_pair.first
pair[1] = pair[1].map{|child| convert(child)}
result = {"name" => pair[0]}
result.merge!("children" => pair[1]) if pair[1].any?
result
end

Generate a tree from string split

I have an array of strings
["ana_ola_una",
"ana_ola_ina",
"ana_asta",
"ana_ena_ola",
"ana_ena_cala",
"ana_ena_cina",
"ana_ena_cina_ula"]
I need to reformat it as a hash of hashes of hashes of ... to make it a tree. The expected result would be:
{ana:
{
ola: {
una: {},
ina: {}
},
asta: {},
ena: {
ola: {},
cala:{},
cina:
{
ula: {}
}
}
}
}
EDIT:
I edit this issue because I have a related question, finally I want it in a JSON with this format. How could I do:
var tree = [
{
text: "Parent 1",
nodes: [
{
text: "Child 1",
nodes: [
{
text: "Grandchild 1"
},
{
text: "Grandchild 2"
}
]
},
{
text: "Child 2"
}
]
},
{
text: "Parent 2"
},
{
text: "Parent 3"
},
{
text: "Parent 4"
},
{
text: "Parent 5"
}
];
arr = %w|ana_ola_una
ana_ola_ina
ana_asta
ana_ena_ola
ana_ena_cala
ana_ena_cina
ana_ena_cina_ula|
result = arr.each_with_object({}) do |s, memo|
s.split('_').inject(memo) do |deep, k|
deep[k.to_sym] ||= {}
end
end
mudasobwa's answer is good, but if you're using Ruby 2.3+ here's a slightly more concise alternative:
arr = [
"ana_ola_una",
"ana_ola_ina",
"ana_asta",
"ana_ena_ola",
"ana_ena_cala",
"ana_ena_cina",
"ana_ena_cina_ula"
]
tree = Hash.new {|h,k| h[k] = Hash.new(&h.default_proc) }
arr.each {|str| tree.dig(*str.split(?_).map(&:to_sym)) }
p tree
# => { ana:
# { ola:
# { una: {},
# ina: {}
# },
# asta: {},
# ena:
# { ola: {},
# cala: {},
# cina: { ula: {} }
# }
# }
# }

How find a nth node value using ruby

I have a JSON response tree like structure
{
"id":""
"node": [
{
"id":""
"node": [
{
"id":""
"node":[]
}
]
}
]
}
How could I get the last id value, it's just example it may contain n number of loops.
h = {
"id" => "1",
"node" => [
{
"id" => "2",
"node" => [
{
"id" => "3",
"node" => []
}
]
}
]
}
▶ λ = ->(h) { h['node'].empty? ? h['id'] : λ.(h['node'].last) }
#⇒ #<Proc:0x00000002f4b490#(pry):130 (lambda)>
▶ λ.(h)
#⇒ "3"
Maybe this method will helps you. You can call recursion method with sub hash.
h = {
"id" => "1",
"node" => [
{
"id" => "2",
"node" => [
{
"id" => "3",
"node" => []
}
]
}
]
}
def get_last_node(h)
if Array === h['node'] && !h['node'].empty?
h['node'].each do |node_h|
id = send(__callee__, node_h)
return id if id
end
nil
else
h['id']
end
end
get_last_node(h)
# => 3
Similar to #mudasobwa's answer:
def get_last_node(h)
h["node"].empty? ? h["id"] : get_last_node(h["node"].first)
end
get_last_node(h)
#=> 3

Ruby JSON node select, view & value change [duplicate]

This question already has an answer here:
How to convert JSON to a hash, search for and change a value
(1 answer)
Closed 7 years ago.
I am trying to select a node from a JSON parsed file and display the node/key hierarchy by using dots to separate the nodes.
json_hash =
{
"Samples": {
"Locations": {
"Presets": "c:\\Presets\\Inst",
"Samples": "c:\\Samples\\seperates\\round_robin"
},
"Format": {
"Type": "AIFF",
"Samplerate": 48,
"Bitrate": 24
},
"Groupings": {
"Type": "dynamic",
"Sets": {
"Keyswitch": "enabled",
"Mods": "Enabled"
}
},
"Ranges": {
"Keys": {
"LowKey": 30,
"HighKey": 80,
"LowVel": 0,
"MidVel": 53,
"HighVel": 127
}
},
"Dynamics": {
"DN": {
"MultiRange": "enabled",
"Range": [
"0-36",
"37-80"
]
}
}
},
"Modulation": {
"Pitch": true,
"Range": []
},
"Encoded": false,
"test": "test_one"
}
My idea is to output something like this;
Samples.Locations.Presets => c:\Presets\Inst
Samples.Locations.Samples => c:\Samples\seperates\round_robin
I managed to get this;
Presets. => c:\Presets\Inst
Presets.Samples. => c:\Samples\seperates\round_robin
Using this code:
def node_tree(hash)
kl = ''
hash.each do |k, v|
kl << "#{k}."
if v.kind_of?(Hash)
node_tree(v)
else
print "#{kl} => "
print "#{Array(v).join(', ')}\n"
end
end
end
node_tree(json_hash)
It would be great if I could get the full node hierarchy to display and then be able to select a node using dot separated node and change the value.
So I could change the Samples.Dynamics.DN.Range from 0-36, 37-80 to 0-30, 31-60, 61-90
key = 'Samples.Dynamics.DN.Range'
value = %w(0-30 31-60 61-90)
node_set('Samples.Dynamics.DN.Range', value)
I can't work on the later until I figure out how best to display and select the nodes and values.
Using this method you can flatten the hash:
def flat_hash(hash, k = [])
return { k => hash } unless hash.is_a?(Hash)
hash.inject({}) { |h, v| h.merge! flat_hash(v[-1], k + [v[0]]) }
end
p flat_hash(json_hash)
#=> {["Samples", "Locations", "Presets"]=>"c:\\Presets\\Inst", ["Samples", "Locations", "Samples"]=>"c:\\Samples\\seperates\\round_robin", ["Samples", "Format", "Type"]=>"AIFF", ["Samples", "Format", "Samplerate"]=>48, ["Samples", "Format", "Bitrate"]=>24, ["Samples", "Groupings", "Type"]=>"dynamic", ["Samples", "Groupings", "Sets", "Keyswitch"]=>"enabled", ["Samples", "Groupings", "Sets", "Mods"]=>"Enabled", ["Samples", "Ranges", "Keys", "LowKey"]=>30, ["Samples", "Ranges", "Keys", "HighKey"]=>80, ["Samples", "Ranges", "Keys", "LowVel"]=>0, ["Samples", "Ranges", "Keys", "MidVel"]=>53, ["Samples", "Ranges", "Keys", "HighVel"]=>127, ["Samples", "Dynamics", "DN", "MultiRange"]=>"enabled", ["Samples", "Dynamics", "DN", "Range"]=>["0-36", "37-80"], ["Modulation", "Pitch"]=>true, ["Modulation", "Range"]=>[], ["Encoded"]=>false, ["test"]=>"test_one"}
Using regular expression for valid path /([A-Z|a-z]:\\[^*|"<>?\n]*)|(\\\\.*?\\.*)/ you can filter flatten hash:
VALID_PATH_REGEXP = /([A-Z|a-z]:\\[^*|"<>?\n]*)|(\\\\.*?\\.*)/
flat_hash(json_hash).each_pair do |k, v|
puts "#{Array(k).join('.')} => #{v}" if v.to_s =~ VALID_PATH_REGEXP
end
#=> Samples.Locations.Presets => c:\Presets\Inst
#=> Samples.Locations.Samples => c:\Samples\seperates\round_robin
Final code:
require 'json'
VALID_PATH_REGEXP = /([A-Z|a-z]:\\[^*|"<>?\n]*)|(\\\\.*?\\.*)/
json_hash = JSON.parse(File.read('example.json'))
def flat_hash(hash, k = [])
return { k => hash } unless hash.is_a?(Hash)
hash.inject({}) { |h, v| h.merge! flat_hash(v[-1], k + [v[0]]) }
end
flat_hash(json_hash).each_pair do |k, v|
puts "#{Array(k).join('.')} => #{v}" if v.to_s =~ VALID_PATH_REGEXP
end
#=> Samples.Locations.Presets => c:\Presets\Inst
#=> Samples.Locations.Samples => c:\Samples\seperates\round_robin
Update:
That's probably better to return hash with the results, rather than just printing them:
require 'json'
VALID_PATH_REGEXP = /([A-Z|a-z]:\\[^*|"<>?\n]*)|(\\\\.*?\\.*)/
json_hash = JSON.parse(File.read('example.json'))
def flat_hash(hash, k = [])
return { k => hash } unless hash.is_a?(Hash)
hash.inject({}) { |h, v| h.merge! flat_hash(v[-1], k + [v[0]]) }
end
def node_tree(hash)
flat_hash(hash).map { |k, v| [Array(k).join('.'), v] if v.to_s =~ VALID_PATH_REGEXP }.compact.to_h
end
p node_tree(json_hash)
#=> {"Samples.Locations.Presets"=>"c:\\Presets\\Inst", "Samples.Locations.Samples"=>"c:\\Samples\\seperates\\round_robin"}

Resources