Reputation: 714
I have an array that is currently sorted alphabetically, and I'm trying to sort it by a manual order of strings.
Current code:
list = ["gold","silver","bronze","steel","copper"]
list = list.sort { |a, b| a <=> b }
What I'm trying to achieve: (With a blank entry as a separator)
list = ["gold","silver","bronze","steel","copper"]
sort_order = ["bronze","silver","gold","","copper","steel"]
list = list.sort_by sort_order
Output: bronze | silver | gold | - | copper | steel
Is this possible? Currently stuck with these error messages:
comparison of Integer with nil failed
comparison of String with String failed
Upvotes: 1
Views: 1364
Reputation: 110675
I assume that:
list
is in sort_order
;sort_order
may contain elements that are not in list
;list
may contain duplicates; andsort_order
contains no duplicates.If sort_order
initially contains duplicates the temporary array sort_order.uniq
can be used in the calculations.
Observe that if, as in the example, list
contains no duplicates and sort_order
contains no elements other than those in list
, sorting list
by the order of its elements in sort_order
is trivial, as it merely returns sort_order
.
The following is more efficient than methods that use sort
or sort_by
(O(n)
versus O(n*log(n)
) computational complexity.)
list = ["gold", "copper", "silver", "copper", "steel", "gold"]
sort_order = ["bronze", "silver", "tin", "gold", "copper", "steel"]
count = list.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
#=> {"gold"=>2, "copper"=>2, "silver"=>1, "steel"=>1}
sort_order.flat_map { |e| [e]*count[e] }.reject(&:empty?)
#=> ["silver", "gold", "gold", "copper", "copper", "steel"]
Upvotes: 3
Reputation: 683
you may want to just make a medal class or something, with a numerical rank and a string name, and then just sort by the rank. like so.
class Medal
attr_accessor :name, :rank
def initialize(name, rank)
@name = name
@rank = rank
end
end
list = [Medal.new("gold", 0), Medal.new("silver", 1), Medal.new("bronze", 2), Medal.new("steel", 4), Medal.new("copper", 3)]
list = list.sort_by &:rank
you could take this a big farther if you want and define a map of medal names to ranks to drop the rank from the initialize. You could also just define each medal as a constant if you want, since you'll probably only have a fixed list.
But if you don't want to do that then you could probably also just have a list of the order and sort by the index, like this
list = ["gold","silver","bronze","steel","copper"]
sort_order = ["bronze","silver","gold","","copper","steel"]
list = list.sort_by {|m| sort_order.index m}
Upvotes: 1