Reputation: 257
I have a hash that looks something like:
{ "3g3dsd3" => {"price"=>0.12, "avg"=>81, "top"=>true}, "1sf3af" => {"price"=>0.14, "avg"=>121, "top"=>false}...}
I wanna reorder it so that items with "top"=>true
would be on top but besides that the items would keep the previous order, meaning items with the same top
value wont change the order between when.
I could not find evidence in the original doc that sort_by
keeps the order of the attributes that are not sorted.
How can I do that?
Upvotes: 1
Views: 729
Reputation: 21130
sort_by
specifically states:
The result is not guaranteed to be stable. When two keys are equal, the order of the corresponding elements is unpredictable.
Meaning your question is well founded.
In this scenario you could use partition
to split the collection up into two. Then paste them back together in the way you desire.
top, non_top = hash.partition { |key, hash| hash['top'] }
result = (top + non_top).to_h
partition
keeps the order of the of the original array.
If you are a fan of one liners, the following does the same in this scenario.
result = hash.partition { |key, hash| hash['top'] }.flatten(1).to_h
Upvotes: 1
Reputation: 114188
You can make sort_by
stable by incorporating with_index
.
Instead of:
collection.sort_by { |...| ... }
You write:
collection.sort_by.with_index { |(...), i| [..., i] }
Applied to your problem:
hash.sort_by { |_k, v| v['top'] ? 0 : 1 } # unstable
hash.sort_by.with_index { |(_k, v), i| [v['top'] ? 0 : 1, i] } # stable
Upvotes: 3
Reputation: 110685
Here is a way to reorder the hash's key-value pairs without converting the hash to one or more arrays and then converting those arrays, after modification, back to a hash.
Suppose
h = { "2rh4abc" => {"price"=>0.18, "avg"=>130, "top"=>false },
"3g3dsd3" => {"price"=>0.12, "avg"=>81, "top"=>true },
"1sf3af" => {"price"=>0.14, "avg"=>121, "top"=>false } }
then
top_key = h.find { |_,v| v["top"] == true }.first
#=> "3g3dsd3"
{ top_key=>h[top_key] }.merge(h.reject { |k,_| k == top_key })
#=> {"3g3dsd3"=>{"price"=>0.12, "avg"=>81, "top"=>true},
# "2rh4abc"=>{"price"=>0.18, "avg"=>130, "top"=>false},
# "1sf3af"=>{"price"=>0.14, "avg"=>121, "top"=>false}}
If h
can be mutated (modified in place) this can be simplified to:
{ top_key=>h.delete(top_key) }.merge(h)
#=> {"3g3dsd3"=>{"price"=>0.12, "avg"=>81, "top"=>true},
# "2rh4abc"=>{"price"=>0.18, "avg"=>130, "top"=>false},
# "1sf3af"=>{"price"=>0.14, "avg"=>121, "top"=>false}}
Upvotes: 0
Reputation: 124
Ruby sort / sort_by
are not stable. You can however use the index in the original input as a tiebreaker: Is sort in Ruby stable?
Upvotes: 0