Generate a tree from string split - ruby

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: {} }
# }
# }
# }

Related

Dry validation i18n message for array validation

Let say I have define a dry-validation like this:
class ApplicationContract < Dry::Validation::Contract
config.messages.backend = :i18n
config.messages.load_paths << 'config/errors.yml'
params do
required(:todo).schema do
required(:title).filled(:string)
required(:items).array(:hash) do
required(:name).filled(:string)
end
end
end
end
Here is my config/errors.yml:
vi:
dry_validation:
errors:
rules:
title:
filled?: 'phai duoc dien'
key?: 'ko dc trong'
items:
name:
key?: 'thieu name'
filled?: 'name rong'
In my code, I use it to validate my data:
my_json = create_my_json
v = ApplicationContract.new
result = v.call(my_json)
render json: result.errors(locale: :vi).to_h
If my_json like:
{
"title": "",
"items": [
{
"name": "bbb"
}
]
}
then I got response:
{
"todo": {
"title": [
"phai duoc dien"
]
}
}
You guys can see my validation for field title works fine with locale vi
Now if my json like:
{
"title": "aa",
"items": [
{
"name": ""
}
]
}
then the response is:
{
"todo": {
"items": {
"0": {
"name": [
"translation missing: vi.dry_validation.errors.filled?"
]
}
}
}
}
The validation still works but it can not get my locale message. It show the warning "translation missing: vi.dry_validation.errors.filled?" instead. How can I fix this problem?
Finally I got it. Just remove the node items from config/errors.yml:
vi:
dry_validation:
errors:
rules:
title:
filled?: 'phai duoc dien'
key?: 'ko dc trong'
name:
key?: 'thieu name'
filled?: 'name rong'

Ruby - Convert Hash with array values to array of hashes

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

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

Transmute hash structure [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 7 years ago.
Improve this question
I have a large JSON object as such (but x100+):
[
{
"category": "category1",
"text": "some text"
},
{
"category": "category2",
"text": "some more text"
},
{
"category": "category1",
"text": "even more text"
}
]
How would I transmute this into:
{
"category1": [
{
"text": "some text"
},
{
"text": "even more text"
}
],
"category2": {
"text": "even more text"
}
}
Any help in the right direction would be appreciated.
First you need to convert your JSON string into Ruby Object.
require "json"
json = %{
[
{
"category": "category1",
"text": "some text"
},
{
"category": "category2",
"text": "some more text"
},
{
"category": "category1",
"text": "even more text"
}
]
}
ary = JSON.parse(json)
Now that we have an array of hashes in Ruby form, we can manipulate it
h = ary.group_by {|i| i["category"]}
#=> {"category1"=>[{"category"=>"category1", "text"=>"some text"}, {"category"=>"category1", "text"=>"even more text"}], "category2"=>[{"category"=>"category2", "text"=>"some more text"}]}
h = h.map {|k,v| {k => v.map {|t| {"text" => t["text"]}}}}
#=> [{"category1"=>[{"text"=>"some text"}, {"text"=>"even more text"}]}, {"category2"=>[{"text"=>"some more text"}]}]
h = h.reduce(&:merge)
#=> {"category1"=>[{"text"=>"some text"}, {"text"=>"even more text"}], "category2"=>[{"text"=>"some more text"}]}
Print the JSON in pretty form to check the result
puts JSON.pretty_generate(h)
Output:
{
"category1": [
{
"text": "some text"
},
{
"text": "even more text"
}
],
"category2": [
{
"text": "some more text"
}
]
}
def transmute(arr)
out = Hash.new { |hash, key| hash[key] = [] }
arr.inject(out) do |h, e|
key = e[:category].to_sym
entry = {text: e[:text]}
h[key] << entry
h
end
end
Working code/spec snippet:
http://rubysandbox.com/#/snippet/56784c32793916000c000000
Assuming to get "category2": [{"text": "some more text"}] in the result:
array.map(&:dup).group_by{|h| h.delete(:category)}
Enumerable#each_with_object might help. Something like
json.each_with_object({}) do |h, acc|
acc[h[:category]] ||= []
acc[h[:category]] << {text: h[:text]}
end # {"category1"=>[{:text=>"some text"}, {:text=>"even more text"}], "category2"=>[{:text=>"some more text"}]}
where json is your original array.

Convert csv to json in ruby

CSV
id,modifier1_name,modifier2_price,modifier2_name,modifier2_price,modifier2_status
1,'Small',10,'Large',20,'YYY'
2,'Small',20,'Large',30,'YYY'
JSON
[
{
id: 1,
modifier: [
{name: 'Small', price: 10},
{name: 'Large', price: 20, status: 'YYY'}]
},
{
id: 2,
modifier: [
{name: 'Small', price: 20},
{name: 'Large', price: 30, status: 'YYY'}],
}
]
How to convert CSV to Json in this case when modifiers can be different ?
You will need to map the modifiers yourself, as there is no built-in method to map hash values into an array from your logic:
JSON.pretty_generate(CSV.open('filename.csv', headers: true).map do |row|
modifier = {}
row.each do |k, v|
if k =~ /modifier(.)_(.*)$/
(modifier[$1] ||= {})[$2] = v
end
end
{ id: row['id'],
modifier: modifier.sort_by { |k, v| k }.map {|k, v| v }
}
end)
For the file*
id,modifier1_name,modifier1_price,modifier2_name,modifier2_price,modifier2_status
1,Small,10,Large,20,YYY
2,Small,20,Large,30,YYY
*I made some changes to the file you show, since it will not give you the required result - you state modifier2_price twice, for example
You will get:
[
{
"id": "1",
"modifier": [
{
"name": "Small",
"price": "10"
},
{
"name": "Large",
"price": "20",
"status": "YYY"
}
]
},
{
"id": "2",
"modifier": [
{
"name": "Small",
"price": "20"
},
{
"name": "Large",
"price": "30",
"status": "YYY"
}
]
}
]
require 'csv'
require 'json'
CSV.open('filename.csv', :headers => true).map { |x| x.to_h }.to_json

Resources