Ruby, iOS, and Other Development

A place to share useful code snippets, ideas, and techniques

All code in posted articles shall be considered public domain unless otherwise noted.
Comments remain the property of their authors.

2007-02-22

ArrayOfHashes

More often than I might have expected I wind up dealing with arrays of hashes. A lot of this comes from making JSON-RPC calls, incidentally, but that doesn't matter. The important thing is that I kept writing code like this:

values = foo.map{|x|x["bar"]}
...or worse...
some_values = foo.map{|x|x["bar"]}
other_values = foo.map{|x|x["baz"]}

I came to the conclusion that what I really wanted was a way to treat arrays of hashes in a special way, and that means mixing in a module! Here's the ArrayOfHashes module:

module ArrayOfHashes
  def transpose
    hash = {}
    each_with_index { |h,i| h.each { |k,v| (hash[k] ||= [])[i] = v } }
    hash
  end

  def [](key)
    map { |h| h[key] }
  end
end

It's pretty simple, actually. Array#transpose assumes it's an array of arrays, so this just overrides it with the assumption that it's an array of hashes. The result is a hash of arrays, generated in the spirit of the original Array#transpose. Overriding the [] operator is a little bit weirder, but it is (nearly) equivalent to using transpose[] without doing the full transpose.

So now those two examples become...

values = foo.extend(ArrayOfHashes)["bar"]
...and...
hash = foo.extend(ArrayOfHashes).transpose
some_values,other_values = hash["bar"],hash["baz"]
...respectively. Enjoy!

Labels: ,

2007-02-14

Have you seen that key?

This is tiny, but oh, so cool. From time to time I find myself needing a way of checking whether I've seen something. Consider, for example, uniq_by. Here's the implementation of Enumerable#uniq_by I gave previously:

module Enumerable
  def uniq_by
    seen = {}
    select { |v|
      key = yield(v)
      (seen[key]) ? nil : (seen[key] = true)
    }
  end
end
Now we make the "seen" hash a bit smarter:
module Enumerable
  def uniq_by
    seen = Hash.new { |h,k| h[k] = true; false }
    select { |v| !seen[yield(v)] }
  end
end

...and isn't that more concise? It just feels better. Yes, I could swap the true and false in the hash block so I don't need the negation in the #select block, but that has weird semantics. It probably would make more sense to use #reject to avoid the negation, especially since that doesn't force it to return an array:

module Enumerable
  def uniq_by
    seen = Hash.new { |h,k| h[k] = true; false }
    reject { |v| seen[yield(v)] }
  end
end

Enjoy!

Labels: ,