Props to Jeremy Voorhis for this cool and simple Hash maneuver.

class Hash
  def rewrite mapping
    inject({}) do |rewritten_hash, (original_key, value)|
      rewritten_hash[mapping.fetch(original_key, original_key)] = value
      rewritten_hash
    end
  end
end

Example usage:

h = { :human => 'squishy', :robot => 'tinny' }
h.rewrite(:human => :mammal)
# => { :mammal => 'squishy', :robot => 'tinny' }

Interestingly, this is the first time I’ve seen argument decomposition used in a truly clear and helpful way.

4 Comments

  1. Bill Burcham

    That’s a beautiful method. Which part is the “argument decomposition” part? I Googled for “ruby argument decomposition” about all I get is this very post!

  2. duncanbeevers

    A block passed to inject takes two arguments. The first is the accumulator, and the second is the element of the enumerable being injected (or folded.)

    The decomposition in this case occurs in the second argument of the inject block, which instead of assigning the enumerable element to a single block-local variable, instead decomposes the Hash tuple (key, value) to two discrete locals. Neat!

  3. Bill Burcham

    Oh yeah. For folks who want to find more of these in the wild, here’s a TextMate regexp that will find all injects that use argument decomposition:

    inject.*[|][^|(\n]*[(][^)|\n]+[)][^|\n]*[|]

    That makes sense when injecting on anything whose each method yields an array and Hash is a real common one (yielding [key,value]).

  4. Rodent of Unusual Size

    Unfortunately, I think this technique has a basic drawback, which leads to others. It returns a *new* hash rather than updating the existing one. Which means the return value lacks any special characteristics of the original hash (such as default value, singleton methods, etc.).

    Obviously the modify-origin-hash would be addressed by using a #rewrite! method, but the other aspect remains.

    To rewrite the hash in place without losing any specifics, try replacing the #inject block with this:

    mapping.inject(self) do |memo,(oldkey,newkey)|
      memo[newkey] = memo[oldkey]
      memo.delete(oldkey) 
      memo
    end
    self

    This will retain instance-local attributes, modify the hash in place, and only iterates over the changed pairs rather than the entire hash (which might be humongous).

Leave a Comment

Enclose code in <code lang="ruby"></code> if you care.
Preview your comment using the button below.