views:

1799

answers:

2

I have the following hash:

user = {
  'user' => {
    'title' => {'weight' => 1, .... }
    'body' => {'weight' => 4, ....}
     ....
     ....
  }
}

Is is possible to get the User sorted by the weight key of its child hashes?

I looked in the Hash.sort, but it looks like it returns array rather than my original hash sorted.

+7  A: 

Hashes are fundamentally unsorted data structures; Hash#sort is, indeed, what you want. Either that, or sort a list of keys and then use that to enumerate when it's time to output the hash, instead of enumerating directly over the hash using its own methods.

Jim Puls
Technically, Hashes are ordered in Ruby 1.9. But I think it's still generally better to treat them as if they weren't, since support for reordering and such does not exist.
Chuck
Jim, can you give me an example?
satynos
"Technically, Hashes are ordered in Ruby 1.9"...this makes me very sad. A hash is not supposed to have any implicit order! Sure, I understand wanting ordering sometimes...but create a different name for that data structure! Grr.
Beska
+3  A: 

In Ruby 1.9, Hashes are sorted, but Hash#sort still returns an Array of Arrays. Imagine that! It does imply that you can build your own sorting method on top of it.

class Hash
  def sorted_hash(&block)
    self.class[sort(&block)]   # Hash[ [[key1, value1], [key2, value2]] ]
  end
end

Hashes are unsorted in Ruby 1.8. If you want Ruby 1.8 compatibility, you can use ActiveSupport's OrderedHash. It behaves like a 1.9-Hash, so you can define the same sorted_hash method on it:

class ActiveSupport::OrderedHash
  def sorted_hash(&block)
    self.class[sort(&block)]
  end
end

hash = ActiveSupport::OrderedHash.new
hash["b"] = "b"
hash["a"] = "a"
hash               #=> {"b"=>"b", "a"=>"a"}  => unsorted
hash.sorted_hash   #=> {"a"=>"a", "b"=>"b"}  => sorted!

You have to copy the sorted_hash method to your code, because it does not exist by default!

Update for deep sorting: If you're looking to sort on something else than the hash key, pass a block to the sorted_hash method as follows (assuming the implementation from above):

hash = ActiveSupport::OrderedHash.new
hash["a"] = { "attr" => "2", "..." => "..." }
hash["b"] = { "attr" => "1", "..." => "..." }

# Unsorted.
hash 
  #=> {"a"=>{"attr"=>"2", "..."=>"..."}, "b"=>{"attr"=>"1", "..."=>"..."}}

# Sort on the "attr" key. (Assuming every value is a Hash itself!)
hash.sorted_hash { |a, b| a[1]["attr"] <=> b[1]["attr"] }
  #=> {"b"=>{"attr"=>"1", "..."=>"..."}, "a"=>{"attr"=>"2", "..."=>"..."}}
molf
I did not get you. I looked in the OrderedHash source code of the rails 2.3.2 and I don't see anything in regards to sorting methods.
satynos
@satynos: I clarified it a bit, hopefully. You need to define sorted_hash yourself, but it's really easy! Just copy my implementation if you want.
molf
@molf: excellent... thanks for the feedback and help on implementation. Also if I implement your method in Hash class, will it work? or should it have to be implemented in ActiveSupport::OrderedHash ?
satynos
@satynos: only use the Hash class if you are using Ruby 1.9. Ruby 1.8 Hashes are unordered by definition -- you will not be able to sort. Therefore, for Ruby 1.8+ compatibility you need to use ActiveSupport::OrderedHash.
molf