2

I have a hash in ruby with a complex structure, like this:

something = {}
something[1488343493] = { :type => 'tag', :name => 'v1.2', :sha => 'a66fd116e454378794d24c41c193d385be37436f'}
something[1488288253] = { :type => 'pull', :number => '469', :sha => '190ed76e30a5fa7d357e8bfb78adfa687a673635', :title => "Consistent file uploads "}
something[1468674242] = { :type => 'tag', :name => 'v1.1', :sha => '2cf4549d0181ad1d60fbd3bbe132b599a14a8965'}
something[1488457772] = { :type => 'pull', :number => '476', :sha => '5f51fa23ea79bd9b89703cb93a5e38a0f0a338bb', :title => "Extract i18n strings in modals/* "}
something[1488288044] = { :type => 'pull', :number => '470', :sha => 'ab98ec3bf7cbe04f11a17d30ed07e5323b45d5df', :title => "Stop copy & clickthrough from list summaries "}

This basically contains a list of Github tags and merged pull requests. I can easily sort this with .sort:

something.sort.each do | key, value | # sorts perfectly fine
  p "#{key} #{value[:type]} #{value[:sha]}"
end

But I don't want a sorted hash, but a reversed hash. And I'm totally puzzled I can not reverse it at all, trying to .reverse it gives NoMethodError for the hash:

something.reverse.each do | key, value | # undefined method `reverse' for #<Hash:0x0> (NoMethodError)
  p "#{key} #{value[:type]} #{value[:sha]}"
end

Trying to reverse_each does simply nothing:

something.reverse_each do | key, value | # does not reverse at all
  p "#{key} #{value[:type]} #{value[:sha]}"
end

Same applies to converting to array and reversing, does nothing at all:

gnihtemos = something.to_a.reverse.to_h # does not reverse at all
gnihtemos.each do | key, value |
  p "#{key} #{value[:type]} #{value[:sha]}"
end

gnihtemos = Hash[something.to_a.reverse] # does not reverse at all
gnihtemos.each do | key, value |
  p "#{key} #{value[:type]} #{value[:sha]}"
end

I'm running out of options. I'm using Ruby 2.4.0p0. What else can I do to reverse something?

Community
  • 1
  • 1
q9f
  • 11,293
  • 8
  • 57
  • 96
  • Perhaps you want `something.invert`, which produces a hash with keys and values reversed. See [Hash#invert](https://ruby-doc.org/core-2.4.0/Hash.html#method-i-invert). Here the keys would become hashes. I say "perhaps" because I don't understand the question. – Cary Swoveland Apr 21 '17 at 15:03
  • Why do you want to sort a hash? There's no advantage to that. Instead, get the keys, sort those, then extract values in that order. If you want to reverse the order, then use `reverse`. – the Tin Man Apr 21 '17 at 20:15

4 Answers4

4

reverse reverses the current order. That means you have to sort first and reverse in a second step:

something.sort.reverse.each { ... }

Or you need to explicitly tell Ruby how to sort:

something.sort_by { |commit_id, _| -commit_id }.each { ... }
spickermann
  • 100,941
  • 9
  • 101
  • 131
  • 2
    I dare say that `something.sort_by{ |k,_| -k }` or `something.sort_by{ |commit_id, _| -commit_id }` is more readable. With `sort` and a block, it might not be directly obvious that `a` and `b` are swapped. – Eric Duminil Apr 21 '17 at 16:08
  • Agree. I don't know why, but I always forget the advantages of `sort_by`... – spickermann Apr 21 '17 at 20:30
1

I'm not sure I understand what you ask. If what you want is your hash reversed in the sense that the keys' order is reversed, you can do it this way:

reversed = {}
something.keys.reverse.each { |k| reversed[k] = something[k] }
yoones
  • 2,394
  • 1
  • 16
  • 20
  • In ruby we use `each_with_object`: `something.keys.reverse.each_with_object { |k, acc| acc.merge!({k => something[k]}) }`. – Aleksei Matiushkin Apr 21 '17 at 15:17
  • thanks eric-duminil, code updated. @mudasobwa you can't use each_with_object on an array (#keys returns an array). – yoones Apr 21 '17 at 15:38
  • @yoones: Why would you think `each_with_object` isn't defined for arrays? try `[].method(:each_with_object)` – Eric Duminil Apr 21 '17 at 15:44
  • @EricDuminil In fact, `each_with_object` is granted for free when the class implements `each` and includes `Enumerable` mixin. – Aleksei Matiushkin Apr 21 '17 at 15:48
  • you're right, my bad. Still @mudasobwa I don't see the point of using it in this case? – yoones Apr 21 '17 at 16:00
  • The point is to save 2 lines out of 3. With `each_with_object`, you don't need to initialize `reversed`. You also return the desired object directly, which isn't the case in your example. – Eric Duminil Apr 21 '17 at 16:02
1

Instead of trying to sort a hash, which has very little value, instead sort the keys and then extract based on their order:

something = {
  1488343493 => { :type => 'tag', :name => 'v1.2', :sha => 'a66fd116e454378794d24c41c193d385be37436f'},
  1488288253 => { :type => 'pull', :number => '469', :sha => '190ed76e30a5fa7d357e8bfb78adfa687a673635', :title => "Consistent file uploads "},
  1468674242 => { :type => 'tag', :name => 'v1.1', :sha => '2cf4549d0181ad1d60fbd3bbe132b599a14a8965'},
  1488457772 => { :type => 'pull', :number => '476', :sha => '5f51fa23ea79bd9b89703cb93a5e38a0f0a338bb', :title => "Extract i18n strings in modals/* "},
  1488288044 => { :type => 'pull', :number => '470', :sha => 'ab98ec3bf7cbe04f11a17d30ed07e5323b45d5df', :title => "Stop copy & clickthrough from list summaries "},
}

rev_sorted_keys = something.keys.sort.reverse
# => [1488457772, 1488343493, 1488288253, 1488288044, 1468674242]

something.values_at(*rev_sorted_keys)
# => [{:type=>"pull",
#      :number=>"476",
#      :sha=>"5f51fa23ea79bd9b89703cb93a5e38a0f0a338bb",
#      :title=>"Extract i18n strings in modals/* "},
#     {:type=>"tag",
#      :name=>"v1.2",
#      :sha=>"a66fd116e454378794d24c41c193d385be37436f"},
#     {:type=>"pull",
#      :number=>"469",
#      :sha=>"190ed76e30a5fa7d357e8bfb78adfa687a673635",
#      :title=>"Consistent file uploads "},
#     {:type=>"pull",
#      :number=>"470",
#      :sha=>"ab98ec3bf7cbe04f11a17d30ed07e5323b45d5df",
#      :title=>"Stop copy & clickthrough from list summaries "},
#     {:type=>"tag",
#      :name=>"v1.1",
#      :sha=>"2cf4549d0181ad1d60fbd3bbe132b599a14a8965"}]

A hash is a random access structure, and sorting it isn't necessary. We can quickly sort the keys and then iterate over them, or use values_at to extract the values in the same order as the keys.

Sorting makes a lot more sense with an Array, which is often used as a queue or list where order can be important.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
0

Okay, sometimes it just requires to take the time to write down the whole issue on StackOverflow. I just learned .reverse_each actually does what it says: reversing the hash. I was expecting it reverse sorts the hash.

And this is the solution: .sort.reverse, as in:

something.sort.reverse.each do | key, value |
  p "#{key} #{value[:type]} #{value[:sha]}"
end
q9f
  • 11,293
  • 8
  • 57
  • 96
  • 2
    `reverse` isn't defined for hashes. So no, `.reverse` doesn't reverse the hash. It reverses the array returned by `Hash#sort`, though. – Eric Duminil Apr 21 '17 at 15:46
  • 1
    I think it's because your answer makes it clear that the question wasn't clear at all. ;) – Eric Duminil Apr 21 '17 at 16:15
  • 1
    Well, I downvoted for a) this answer is _incorrect_ (it states that `Hash#reverse` does it’s job, while this method is even not existing, as pointed out by @EricDuminil) and b) this answer brings no added value compared to the accepted one. – Aleksei Matiushkin Apr 21 '17 at 16:19