rowyourboat rowyourboat - 3 months ago 9
Ruby Question

Make hash keys match the order of a hash constant

I have many hashes that contain the same keys but are all out of order from each other. I need to reorder each one to make them fit the proper order:

correct_order = {
:cat => "cat",
:dog => "dog",
:bear => "bear",
:zebra => "zebra",
:monkey => "monkey"
}

hash1 = {
:bear => "bear",
:cat => "cat"
}

hash2 = {
:cat => "cat",
:monkey => "monkey",
:zebra => "zebra",
:bear => "bear"
}

hash3 = {
:dog => "dog",
:monkey => "monkey",
:cat => "cat"
}


How would I go about comparing the key order of hash1, hash2, and hash3 to make each key match its place in the correct_order hash? hash2 would become:

hash2 {
:cat => "cat",
:bear => "bear",
:zebra => "zebra",
:monkey => "monkey"
}


Note that each new hash will not necessarily have all the keys. Each hash will be of varing size AND varying order.

Thank you

Answer

There is something that needs be said about the order of hash elements, but I'll address that after showing you how you can do what you want to do. This assumes you are using Ruby 1.9+, where the order of key insertion is maintained.

Code

def reorder(hash, order)
  keys = order & hash.keys
  Hash[keys.zip(hash.values_at(*keys))]
end

Examples

order = correct_order.keys
  #=> [:cat, :dog, :bear, :zebra, :monkey]

reorder(hash1, order)
  #=> {:cat=>"cat", :bear=>"bear"}
reorder(hash2, order)
  #=> {:cat=>"cat", :bear=>"bear", :zebra=>"zebra", :monkey=>"monkey"}
reorder(hash3, order)
  #=> {:cat=>"cat", :dog=>"dog", :monkey=>"monkey"}

Explanation

Let's see how the code deals with hash1:

hash = hash1
  #=> {:bear=>"bear", :cat=>"cat"}
order 
  #=> [:cat, :dog, :bear, :zebra, :monkey]
keys = order & hash.keys
  #=> [:cat, :dog, :bear, :zebra, :monkey] & [:bear, :cat]
  #=> [:cat, :bear] 
arr = keys.zip(hash.values_at(*keys))
  #=> keys.zip({:bear=>"bear", :cat=>"cat"}.values_at(*[:cat, :bear]))
  #=> keys.zip({:bear=>"bear", :cat=>"cat"}.values_at(:cat, :bear))
  #=> keys.zip(["cat", "bear"])
  #=> [[:cat, "cat"], [:bear, "bear"]]
Hash[arr]
  #=> {:cat=>"cat", :bear=>"bear"}

In Ruby 1.9+ you can instead write the last step as:

arr.to_h

Note that the method Array#& preserves the order of the first array.

Hash order

Prior to Ruby 1.9 you could not depend on the order of key-value pairs (let's just say keys) in a hash. That changed in 1.9. Now you can be confident that when you enumerate the keys of a hash, the order of the enumeration will follow the order in which the keys were added to the hash. Be warned that that change was not embraced by all Rubyists. If you've been thinking of hashes as unordered collections for twenty years, it's a big change. Personally, I like the change and have found good uses for it.

Comments