Mike-C
Mike-C

Reputation: 23

How do I update single hash value where key appears multiple times in ruby?

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

Answers (3)

Cary Swoveland
Cary Swoveland

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

engineersmnky
engineersmnky

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

tadman
tadman

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

Related Questions