DEV Community

loading...

Pigeon Organizer Lab

2spacemilk profile image Mark Harless Updated on ・2 min read

Problem

I'm given a hash of information and I need to rearrange them into a new hash sorted in a different way.

To go from this:

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"]
  }
}
Enter fullscreen mode Exit fullscreen mode

To this:

pigeon_list = {
  "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"]
  }
}
Enter fullscreen mode Exit fullscreen mode

What I Learned

While this one was challenging, I learned the most from this lab so far.

  1. How to iterate through a hash using .each and assigning the keys and values different names.

  2. I found that creating a new hash required to check to see if it exists which I found interesting. What I though I could do in one line, I had to do in three. I later found out I could do it in a single line using pigeon_list[:name] || = {} but I'm not sure how to use it yet.

  3. I re-learned how to change something to a string using .to_s

Final Iteration

def nyc_pigeon_organizer(data)
  pigeon_list = {}
  data.each do |color_gender_lives, value|
    value.each do |stats, all_names|
      all_names.each do |name|
        if pigeon_list[name] == nil
          pigeon_list[name] = {}
        end
        if pigeon_list[name][color_gender_lives] == nil
          pigeon_list[name][color_gender_lives] = []
        end
        pigeon_list[name][color_gender_lives].push(stats.to_s)
      end
    end 
  end
  pigeon_list
end
Enter fullscreen mode Exit fullscreen mode

Discussion

pic
Editor guide
Collapse
baweaver profile image
Brandon Weaver

You may enjoy Hash constructors and what you can do with them.

For primitive values you can use Hash.new(0) to create something that works like a counter.

For more complex types, you need the block format, Hash.new { |h, k| h[k] = {} }. This ensures a brand new object for each key that the hash doesn't know about as a default value, rather than reusing the same one repeatedly. This also works with hashes containing arrays. You could even go as far as to mix the two and get something like this:

pigeon_list = Hash.new { |h, k| h[k] = Hash.new { |h2, k2| h2[k2] = [] } }

...and the most fun trick? You could make an infinitely deep hash if you really wanted to:

infinite_hash = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }

This works because h.default_proc is referring to the function that is called when a default value is needed, or in other words, magic recursive hashes:

[20] pry(main)> infinite_hash = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
=> {}
[21] pry(main)> infinite_hash[1][2][3][4][5][6][7] = 8
=> 8
[22] pry(main)> infinite_hash
=> {1=>{2=>{3=>{4=>{5=>{6=>{7=>8}}}}}}}

Using those ideas you can avoid the nil checks. If you were to use ||= instead it would have looked like this:

pigeon_list[name] ||= {}
pigeon_list[name][color_gender_lives] ||= []

...which would take out four extra lines.

Now names. Names names names, names are very important. color_gender_lives relies on knowledge of the data structure. What if it changed? Color, gender, and lives are all properties, or perhaps attributes of a pigeon. Perhaps attribute_name or property?

That would bring us to value and stats and all_names. They're not as much values as they are the actual attributes or properties of a pigeon.

Combining all these ideas, we might get code that looks something close to this:

def nyc_pigeon_organizer(data)
  pigeon_list = {}

  data.each do |attribute_name, attributes|
    attributes.each do |attribute_value, pigeon_names|
      pigeon_names.each do |name|
        pigeon_list[name] ||= {}
        pigeon_list[name][attribute_name] ||= []
        pigeon_list[name][attribute_name].push(attribute_value.to_s)
      end
    end 
  end

  pigeon_list
end
Collapse
lagofelipe profile image
Felipe Dolago

Thanks Brandon, that was a super useful explanation and insight,....just awesome stuff