Reputation: 23
I have a nested hash I'm trying to sort by name .
pigeon_data = {
:color => {
:purple => ["Theo", "Peter Jr.", "Lucky"],
:grey => ["Theo", "Peter Jr.", "Ms. K"],
:white => ["Queenie", "Andrew", "Ms. K", "Alex"],
:brown => ["Queenie", "Alex"]
so I'm looking for something like
"Theo" => {
:color => ["purple", "grey"],
:gender => ["male"],
:lives => ["Subway"]
},
"Peter Jr." => {
:color => ["purple", "grey"],
:gender => ["male"],
:lives => ["Library"]
},
But every time i try to change one of the values it ends up changes all the values with the same key.
{"Theo"=>
{:color=>["purple", "grey"],
"Peter Jr."=>
{:color=>["purple", "grey"],
...
My codes a mess but here it is earlier code already puts the seven birds into nested hash as the top level keys i think the problem is somewhere around here
def sort_birds(new_sort_1)
new_sort_2 = Marshal.load(Marshal.dump(new_sort_1))
new_sort_1.each do |ka,va|
va.each do |kb,vb|
vb.each do |kc,vc|
#binding.pry
if vc.include?("#{ka}") && new_sort_2[ka][kb].is_a?(Array)
new_sort_2["#{ka}"][kb] << "#{kc}"
elsif vc.include?("#{ka}")
new_sort_2["#{ka}"][kb] = Array.new
new_sort_2["#{ka}"][kb] << "#{kc}"
else
Upvotes: 2
Views: 142
Reputation: 110675
{ color: pigeon_data[:color].flat_map { |k,v| [k].product(v) }.
each_with_object({}) { |(color,bird),h| (h[bird] ||= []) << color.to_s } }
#=> {:color=>{"Theo"=>["purple", "grey"],
# "Peter Jr."=>["purple", "grey"],
# "Lucky"=>["purple"],
# "Ms. K"=>["grey, "white"],
# "Queenie"=>["white, "brown"],
# "Andrew"=>["white"],
# "Alex"=>["white", "brown"]}}
The steps are as follows:
a = pigeon_data[:color].flat_map { |k,v| [k].product(v) }
#=> [[:purple, "Theo"], [:purple, "Peter Jr."], [:purple, "Lucky"],
# [:grey, "Theo"], [:grey, "Peter Jr."], [:grey, "Ms. K"],
# [:white, "Queenie"], [:white, "Andrew"], [:white, "Ms. K"],
# [:white, "Alex"], [:brown, "Queenie"], [:brown, "Alex"]]
b = a.each_with_object({}) { |(color,bird),h| (h[bird] ||= []) << color.to_s }
#=> {"Theo"=>["purple", "grey"], "Peter Jr."=>["purple", "grey"],
# "Lucky"=>["purple"], "Ms. K"=>["grey", "white"], "Queenie"=>["white", "brown"],
# "Andrew"=>["white"], "Alex"=>["white", "brown"]}
{ color: b }
#=> <as above>
One could alternatively replace each_with_object({})
with each_with_object(Hash.new { |h,k| h[k] = [] })
and (h[bird] ||= [])
with h[bird]
.
See Enumerable#flat_map and Array#product.
Upvotes: 0
Reputation: 29318
Given the structure provided:
pigeon_data = { :color => {
:purple => ["Theo", "Peter Jr.", "Lucky"],
:grey => ["Theo", "Peter Jr.", "Ms. K"],
:white => ["Queenie", "Andrew", "Ms. K", "Alex"],
:brown => ["Queenie", "Alex"] },
:gender => {
:male => ["Alex", "Theo", "Peter Jr.", "Andrew", "Lucky"],
:female => ["Queenie", "Ms. K"] },
:lives => {
"Subway" => ["Theo", "Queenie"],
"Central Park" => ["Alex", "Ms. K", "Lucky"],
"Library" => ["Peter Jr."],
"City Hall" => ["Andrew"] } }
We can loop through the sets using a Hash that builds based on a default process.
builder = Hash.new {|h,k| h[k] = Hash.new {|h2,k2| h2[k2] = []}}
pigeon_data.each_with_object(builder) do |(category,values),cage|
values.each do |cat_value,birds|
birds.each do |bird|
cage[bird][category] << cat_value
end
end
end
When the builder receives a new key it assigns a new Hash as the value. When this nested Hash receives a new key it assigns an empty Array as the value. So then we just place the items in the order we wish to have them appear [bird][category] << value
Resulting in:
{"Theo"=>{
:color=>[:purple, :grey],
:gender=>[:male],
:lives=>["Subway"]},
"Peter Jr."=>{
:color=>[:purple, :grey],
:gender=>[:male],
:lives=>["Library"]},
"Lucky"=>{
:color=>[:purple],
:gender=>[:male],
:lives=>["Central Park"]},
"Ms. K"=>{
:color=>[:grey, :white],
:gender=>[:female],
:lives=>["Central Park"]},
"Queenie"=>{
:color=>[:white, :brown],
:gender=>[:female],
:lives=>["Subway"]},
"Andrew"=>{
:color=>[:white],
:gender=>[:male],
:lives=>["City Hall"]},
"Alex"=>{
:color=>[:white, :brown],
:gender=>[:male],
:lives=>["Central Park"]}}
Upvotes: 2
Reputation: 211560
What you'll want to do is create an entirely new structure instead of trying to wrestle a copy of the existing structure into a whole new form. Ruby's all about transformations defined as a series of new objects as opposed to in-place modifications of the same object.
This can be illustrated like:
def repigeonize(data)
# Create a target structure for this data that's a Hash with a default...
result = Hash.new do |h,k|
# ...inner hash that has...
h[k] = Hash.new do |ih, ik|
# ... arrays assigned by default to its keys.
ih[ik] = [ ]
end
end
# Iterate over the data starting at the top level where attributes...
data.each do |attr, set|
# ...have keys that represent values...
set.each do |value, names|
# ...and list the names of those with those properties.
names.each do |name|
result[name][attr] << value.to_s # Converted to a string.
end
end
end
# Pass the result back
result
end
Where it works like this:
pigeon_data = {
color: {
purple: ["Theo", "Peter Jr.", "Lucky"],
grey: ["Theo", "Peter Jr.", "Ms. K"],
white: ["Queenie", "Andrew", "Ms. K", "Alex"],
brown: ["Queenie", "Alex"]
},
lives: {
library: ["Peter Jr."],
cellar: ["Queenie","Alex"],
attic: ["Lucky","Ms. K"]
}
}
p repigeonize(pigeon_data)
# => {"Theo"=>{:color=>["purple", "grey"]}, "Peter Jr."=>{:color=>["purple", "grey"], :lives=>["library"]}, "Lucky"=>{:color=>["purple"], :lives=>["attic"]}, "Ms. K"=>{:color=>["grey", "white"], :lives=>["attic"]}, "Queenie"=>{:color=>["white", "brown"], :lives=>["cellar"]}, "Andrew"=>{:color=>["white"]}, "Alex"=>{:color=>["white", "brown"], :lives=>["cellar"]}}
Upvotes: 1