Iterate through deep nested hash by using Ruby on Rails - ruby-on-rails

I'm trying to iterate through a nested object to retrieve a specific object identified by a string. In the sample object below, the identifier string is the "label" property. I don't know how to iterate down through the tree to return the appropriate object.
My Ruby and Rails versions are pretty old.
Ruby - 1.9.3
Rails - 3.0.9
`
company_tree = {
label: 'Autos',
subs: [
{
label: 'SUVs',
subs: []
},
{
label: 'Trucks',
subs: [
{
label: '2 Wheel Drive',
subs: []
},
{
label: '4 Wheel Drive',
subs: [
{
label: 'Ford',
subs: []
},
{
label: 'Chevrolet',
subs: []
}
]
}
]
},
{
label: 'Sedan',
subs: []
}
]
}
`
I tried using below code. But I didn't worked. It only return the second array only. It won't go beyond that.
`
data = JSON.parse(requestData)
data['company_tree']['subs'].each do |element|
puts "element=> #{element['subs']}"
end
`

Assuming you have the company_tree variable like described in your question, you can use recursion to iterate over each element and all of its subs. This is the code I made and I will explain it a bit further below
def destruct_tree(element)
puts "#{element[:label]} with subs #{element[:subs]}"
element[:subs].each do |sub|
destruct_tree sub
end
end
destruct_tree company_tree
You give the function the company_tree element. Then it prints its label and subs. Then it iterates over all the subs and passes them to the same function again. Which will intern print their label and subs and run through all their subs again.
I hope this made sense

You can iterate through the hash like below:
def iterate_through(company_tree)
puts "#{company_tree[:label]} => #{company_tree[:subs]}"
company_tree[:subs].each do |sub|
iterate_through(sub)
end
end
iterate_through(company_tree)
Output:
Autos => [{:label=>"SUVs", :subs=>[]}, {:label=>"Trucks", :subs=>[{:label=>"2 Wheel Drive", :subs=>[]}, {:label=>"4 Wheel Drive", :subs=>[{:label=>"Ford", :subs=>[]}, {:label=>"Chevrolet", :subs=>[]}]}]}, {:label=>"Sedan", :subs=>[]}]
SUVs => []
Trucks => [{:label=>"2 Wheel Drive", :subs=>[]}, {:label=>"4 Wheel Drive", :subs=>[{:label=>"Ford", :subs=>[]}, {:label=>"Chevrolet", :subs=>[]}]}]
2 Wheel Drive => []
4 Wheel Drive => [{:label=>"Ford", :subs=>[]}, {:label=>"Chevrolet", :subs=>[]}]
Ford => []
Chevrolet => []
Sedan => []

Thanks for the support, I found out the solution
def iterate(i)
if i.is_a?(Hash)
i.each do |k, v|
if v.is_a?(Hash) || v.is_a?(Array)
iterate(v)
else
puts("k is #{k}, value is #{v}")
end
end
end
if i.is_a?(Array)
i.each do |v|
iterate(v)
end
end
end

Related

how to create an array of hashes by looping over array of objects

I have following array of hash. I am trying to loop over it and build an array of hash of values of id and product_order_id.
objects =
[
#<Product: 0x00007ffd4a561108
#id="1",
#product_id="2",
#product_order_id="23",
#description="abc",
#status="abcde",
#start_date=nil,
#end_date=nil>,
#<Product: 0x00007ffd4a560c80
#id="45",
#product_id="22",
#product_order_id="87",
#description="ahef",
#status="gesff",
#start_date=nil,
#end_date=nil>
......more objects.....
]
This is what it should look like
[{ "1": "23" }, { "45": "87" }] -->its going to be uuid
I tried doing this but no luck
def mapped_product(objects)
mapping = []
objects.each do |object|
mapping << {
object.product_order_id: object.id
}
end
end
Any idea?
inline solution:
> Hash[objects.map{|p| [p.id, p.product_order_id] }]
# Output : [{ 1=>23 }, { 45=>87 }]
I'd usually implement it using an each_with_object
objects.each_with_object({}) { |obj, acc| acc[obj.id] = obj.product_order_id }
Unless I reaaaly want to squeeze some performance, than I'd go with Gagan's answer
Have you tried this?
def mapped_product(objects)
mapping = []
objects.each do |object|
mapping << {
object.id => object.product_order_id # I'm using an `=>` here
}
end
mapping # return the new mapping
end
I've just changed the : on the hash for a => to "make it dynamic" and swapped the values of id and product_order_id
You can also use a map here:
def mapped_product(objects)
objects.map do |object|
{ object.id => object.product_order_id }
end
end

tree structure in ruby with parent child in array format without gems?

I have a array which have list of item like this
arr = [
{:id=>1, :title=>"A", :parent_id=>nil},
{:id=>2, :title=>"B", :parent_id=>nil},
{:id=>3, :title=>"A1", :parent_id=>1},
{:id=>4, :title=>"A2", :parent_id=>1},
{:id=>5, :title=>"A11", :parent_id=>3},
{:id=>6, :title=>"12", :parent_id=>3},
{:id=>7, :title=>"A2=121", :parent_id=>6},
{:id=>8, :title=>"A21", :parent_id=>4},
{:id=>9, :title=>"B11", :parent_id=>2},
{:id=>10, :title=>"B12", :parent_id=>2},
...
]
If parent_id is nil then its should be the parent node, if parent_id is not nil then it should comes under the particular parent.
Based on id and parent_id, I want to provide a response like this:
-A
-A1
-A11
-A12
-A123
-A2
-A21
-B
-B1
-B11
-B12
How could I generate a responds mentioned above?
This is easier than you think, you just need to realize a couple simple things:
nil is a perfectly valid Hash key.
You can use nil as a virtual root for your tree so that all the :parent_ids point at things in your tree.
You can iterate through the array and track entries in two ways at once: by :id and by :parent_id.
First a tree represented by a Hash:
tree = Hash.new { |h,k| h[k] = { :title => nil, :children => [ ] } }
We're going to be going from the root to the leaves so we're only interested in the children side of the parent/child relationship, hence the :children array in the default values.
Then a simple iteration that fills in the :titles and :children as it goes:
arr.each do |n|
id, parent_id = n.values_at(:id, :parent_id)
tree[id][:title] = n[:title]
tree[parent_id][:children].push(tree[id])
end
Note that the nodes (including the parent nodes) are automatically created by tree's default_proc the first time they're seen so the node order in arr is irrelevant.
That leaves us with the tree in tree where the keys are :ids (including the virtual root at the nil key) and the values are subtrees from that point.
Then if you look at tree[nil][:children] to peel off the virtual root, you'll see this:
[
{ :title => "A", :children => [
{ :title => "A1", :children => [
{ :title => "A11", :children => [] },
{ :title => "12", :children => [
{ :title => "A2=121", :children => [] }
] }
] },
{ :title => "A2", :children => [
{ :title => "A21", :children => [] }
] }
] },
{ :title => "B", :children => [
{ :title => "B11", :children => [] },
{ :title => "B12", :children => [] }
] }
]
and that has exactly the structure you're looking for and you should be able to take it from there. That doesn't match your sample response but that's because your sample arr doesn't either.
You could also say:
tree = arr.each_with_object(Hash.new { |h,k| h[k] = { :title => nil, :children => [ ] } }) do |n, tree|
#...
end
if you preferred that rather noisy first line to a separate tree declaration.
Some thing like this will work:
parents = arr.select{|hash| hash[:parent_id] == nil }
parents.each {|hash| print_children hash, arr, "-"}
def print_children(hash, arr, spaces)
puts spaces + hash[:title]
spaces = ' ' + spaces
children = arr.select{|all_hash| all_hash[:parent_id] == hash[:id] }
children.each { |child_hash| print_children child_hash, arr, spaces }
end

JBuilder loop that produces hash

I need loop that produces hash, not an array of objects. I have this:
json.service_issues #service.issues do |issue|
json.set! issue.id, issue.name
end
that results:
service_issues: [
{
3: "Not delivered"
},
{
6: "Broken item"
},
{
1: "Bad color"
},
{
41: "Delivery problem"
}
]
I need this:
service_issues: {
3: "Not delivered",
6: "Broken item",
1: "Bad color",
41: "Delivery problem"
}
Is it possible to do this without converting AR result to hash manually?
Jbuilder dev here.
Short answer: Yes. It's possible without converting array of models into hash.
json.service_issues do
#service.issues.each{ |issue| json.set! issue.id, issue.name }
end
but it'd probably be easier to prepare hash before-hand.
json.service_issues Hash[#service.issues.map{ |issue| [ issue.id, issue.name ] }]
For anyone who is interested in having an hash of arrays (objects), you can use the following code:
#bacon_types.each do |bacon_type|
json.set! bacon_type.name, bacon_type.bacons do |bacon|
bacon.title bacon.title
...
end
You can do it like this way
Jbuilder.encode do |json|
json.service_issues #service.issues.inject({}) { |hash, issue| hash[issue.id] = issue.name; hash }
end
The code generating hash technique may be understood by following example.
[1] pry(main)> array = [{id: 1, content: 'a'}, {id: 2, content: 'b'}]
=> [{:id=>1, :content=>"a"}, {:id=>2, :content=>"b"}]
[2] pry(main)> array.inject({}) { |hash, element| hash[element[:id]] = element[:content]; hash }
=> {1=>"a", 2=>"b"}
The key point of inject to generate hash, return created hash every after inserting new element. Above example, it is realized by ; hash.

recursively search hash in ruby and return an array of items

I have a hash that I'm getting from using JSON.parse. I am inserting into db at that point. The data structure uses acts as tree so I can have a hash like this:
category_attributes
name: "Gardening"
items_attributes:
item 1: "backhoe"
item 2: "whellbarrel"
children_attributes
name: "seeds"
items_attributes
item 3: "various flower seeds"
item 4: "various tree seeds"
children_attributes
name: "agricultural seeds"
items_attributes
item 5: "corn"
item 6: "wheat"
For this hash, I'd like to return an array of all the items_attributes. I have seen this question Traversing a Hash Recursively in Ruby but it seems different. Is there a way to recursively search a hash and return all those elements? Ideally, empty items_attributes should come back with nothing rather than nil.
thx
Try this:
def extract_list(hash, collect = false)
hash.map do |k, v|
v.is_a?(Hash) ? extract_list(v, (k == "items_attributes")) :
(collect ? v : nil)
end.compact.flatten
end
Now let us test the function:
>> input = {
'category_attributes' => {
'name' => "Gardening",
'items_attributes' => {
'item 1' => "backhoe",
'item 2' => "whellbarrel",
'children_attributes' => {
'name' => "seeds",
'items_attributes' => {
'item 3' => "various flower seeds",
'item 4' => "various tree seeds"
},
'children_attributes' => {
'name' => "agricultural seeds",
'items_attributes' => {
'item 5' => "corn",
'item 6' => "wheat"
}
}
}
}
}
}
>> extract_list(input)
=> ["various flower seeds", "various tree seeds", "wheat", "corn",
"backhoe", "whellbarrel"]
You can do something like this:
def collect_item_attributes h
result = {}
h.each do |k, v|
if k == 'items_attributes'
h[k].each {|k, v| result[k] = v } # <= tweak here
elsif v.is_a? Hash
collect_item_attributes(h[k]).each do |k, v|
result[k] = v
end
end
end
result
end
puts collect_item_attributes(h)
# => {"item 1"=>"backhoe",
# "item 2"=>"whellbarrel",
# "item 3"=>"various flower seeds",
# "item 4"=>"various tree seeds",
# "item 5"=>"corn",
# "item 6"=>"wheat"}

Rails/Ruby ordering / splitting it

<%
old_city = ""
#objects.order("city").each do |obj|
if old_city != obj.city && old_city != ""
old_city = obj.city
%>
--Different city--
<%
end
%>
City: <%= obj.city %>
<%
end
%>
So that output expected is:
Chicago
Chicago
--Different city--
New York
New York
New York
--Different city--
Paris
--Different city--
Rio de Janeiro
Maybe there's some cleaver/different way to do that in rails?
I don't think this is the best code for it...
Thanks!
There are several options, but Enumerable offers a group_by method.
group_by takes a block to define groupings. After grouping it's a matter of iterating over the resulting map's keys.
objs = [
{ :foo => 'baz', :wat => 'kthxbai' },
{ :foo => 'bar', :wat => 'narnar' },
{ :foo => 'plugh', :wat => 'xyzzy' },
{ :foo => 'bar', :wat => 'ohai' },
{ :foo => 'baz', :wat => 'fuuuu' },
{ :foo => 'plugh', :wat => 'jimmies' }
]
grouped = objs.group_by { |o| o[:foo] }
grouped.each do |k, v|
puts "GROUP: #{k}"
v.each { |v| puts v }
end
If you want to order by keys, you can do that too by sorting the keys and retrieving the resulting map's values while iterating over the sorted keys.
If they're ActiveRecords you might want to do the work in SQL/ActiveRecord proper.
Try something like this in the console:
Event.order(:city).group_by(&:city)
This will return a hash where the keys will be the individual cities and the values will be arrays of the corresponding event objects. You can then easily iterate over the hash's keys, and in an inner loop, iterate over the corresponding event objects.

Resources