Reputation: 135
I saw several posts on sorting an array that is a value of a hash, but I'm trying to sort an array of arrays that is a value of a hash. I have a hash that looks like this:
h = {
"pets"=>[["dog", 1], ["cat", 2]],
"fruits"=>[["orange", 1], ["apple", 2]]
}
I would like to sort array of arrays according to the first elements (string) of the arrays inside. So I want the result to be
{
"pets"=>[["cat", 2], ["dog", 1]],
"fruits"=>[["apple", 2], ["orange", 1]]
}
This is what I have right now:
h.map do |key, value|
value.sort_by! { |x, y| x[0] <=> y[0] }
end
But this just returns the original array. What do I need to change? Thanks!
Upvotes: 1
Views: 379
Reputation: 110675
h.merge(h) { |*, n| n.sort }
#=> {"pets"=>[["cat", 2], ["dog", 1]], "fruits"=>[["apple", 2], ["orange", 1]]}
This does not modify h
.
This uses the form of Hash#merge that employs a block to determine the values of keys that are present in both hashes being merged, which here is all keys. The block has three block variables, the common key k
, the value of k
for the "old hash, o
and the value of k
for the "new" hash, n
. (Here o
and n
are of course equal.) I'm only using the last of these variables, so I've expressed the block variables as |*, n|
, rather than |k, o, n|
. See the doc for details.
Another way that does not mutate h
:
h.each_key.zip(h.each_value.map(&:sort)).to_h
#=> {"pets"=>[["cat", 2], ["dog", 1]], "fruits"=>[["apple", 2], ["orange", 1]]}
This could instead be written
h.keys.zip(h.values.map(&:sort)).to_h
Upvotes: 3
Reputation: 121000
Immutable version, having no side effects:
h.map { |k, v| [k, v.sort_by(&:first)] }.to_h
Upvotes: 3
Reputation: 1648
The one issue I see is that you are attempting to map
your hash. That will always return an array.
In this case, you’ll get back something like this:
[[["cat", 2], ["dog", 1]], [["apple", 2], ["orange", 1]]]
If you want to modify your hash in place, you can do:
h.each { |key, value| h[key] = value.sort_by { ... } }
Then h
will be the new hash, sorted as you expect.
Or you can set it to another variable:
new_hash = h.each { |key, value| value.sort_by! { ... } }
Next, it looks like you’ve confused sort
and sort_by
. (Easy to do)
When you use sort
, the two variables you pass in your block are assigned to the values being compared. (In your case, the arrays ["dog", 1]
is one argument, and the other would be another array to compare it to, like ["cat", 2]
)
However, sort_by
expects you to do the computing to determine where it’s place is in the new order. Normally sort_by
only accepts a single argument, but because you’re working with arrays (and because of the way Ruby handles tuples) when you tell sort_by
to accept 2 arguments (x
and y
) those are set to the first and second values of your array. (x == "dog"
, y == 1
)
So you’ve got 2 options here: Keep your block the same and switch your enumerable method to sort
, or switch to sort_by
and change your block:
sort
:
h.each { |key, value| h[key] = value.sort! { |x, y| x[0] <=> y[0] } }
sort_by
:
h.each { |key, value| h[key] = value.sort_by! { |x, y| y } } # In this case, `y` is the second value in your arrays, which is the integer
Upvotes: 1