1

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

2 Answers 2

3

I assume that:

  • every element of list is in sort_order;
  • sort_order may contain elements that are not in list;
  • list may contain duplicates; and
  • sort_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"] 
Sign up to request clarification or add additional context in comments.

4 Comments

sort_order.each_with_index.to_h?
To generate the count table in one shot.
But that generates #=> {"bronze"=>0, "silver"=>1, "gold"=>2, "copper"=>3, "steel"=>4}. I need {"gold"=>2, "copper"=>2, "silver"=>1, "steel"=>1}.
I think you understand the problem better than I do because the question itself doesn't make sense to me.
1

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}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.