0

I have an array with the given strings

array = [
  "1mo-30-super",
  "1mo-40-classic",
  "1mo-30-classic",
  "1mo-110-super",
  "1mo-20-extra",
  "6mo-21-super",
  "6mo-11-super",
  "12mo-21-classic",
  "12mo-21-super"
]

How can I sort the array so that it goes in numerical order, then alphabetical order so the array displays like so:

array = [
  "1mo-20-extra",
  "1mo-30-classic",
  "1mo-30-super",
  "1mo-40-classic",
  "1mo-110-super",
  "6mo-11-super",
  "6mo-21-super",
  "12mo-21-classic",
  "12mo-21-super"
]
2
  • 2
    Please be more specific about sorting numerically, considering that each string begins with one or two digits and there are two or three digits between the hyphens. Similarly, what do you mean by the second sort criterion, considering that there are strings of letters in two places as well? Commented Nov 28, 2018 at 0:34
  • I wanted it sorted so the first section is sorted by number, then with all the ones with the same first number the second section is sorted by number, then the within those the last section is sorted alphabetically. Commented Nov 28, 2018 at 15:10

3 Answers 3

1

You're looking for a "natural" sort where the numeric substrings will be compared as numbers as the non-numeric parts will be compared like strings. Conveniently enough, arrays in Ruby compare element-by-element and your format is fairly regular so you can get away with a #sort_by call and a bit of mangling to convert "12mo-21-classic" to [12, 'mo-', 21, '-classic']. Something like this for example:

# This is a bit complicated so we'll give the logic a name.
natural_parts = ->(s) { s.match(/(\d+)(\D+)(\d+)(\D+)/).to_a.drop(1).map.with_index { |e, i| i.even?? e.to_i : e } }
array.sort_by(&natural_parts)
Sign up to request clarification or add additional context in comments.

Comments

1
array.sort_by { |s| [s.to_i, s[/(?<=-)\d+/].to_i, s.gsub(/\A.+-/,'')] }
  #=> ["1mo-20-extra", "1mo-30-classic", "1mo-30-super", "1mo-40-classic", "1mo-110-super",
  #    "6mo-11-super", "6mo-21-super", "12mo-21-classic", "12mo-21-super"]

When sorting arrays the method Arrays#<=> is used to order pairs of arrays. See the third paragraph of the doc for an explanation of how that is done.

The arrays used for the sort ordering are as follows.

array.each do |s|
  puts "%-15s -> [%2d, %3d, %s]" % [s, s.to_i, s[/(?<=-)\d+/].to_i, s.gsub(/\A.+-/,'')]
end

1mo-30-super    -> [ 1,  30, super]
1mo-40-classic  -> [ 1,  40, classic]
1mo-30-classic  -> [ 1,  30, classic]
1mo-110-super   -> [ 1, 110, super]
1mo-20-extra    -> [ 1,  20, extra]
6mo-21-super    -> [ 6,  21, super]
6mo-11-super    -> [ 6,  11, super]
12mo-21-classic -> [12,  21, classic]
12mo-21-super   -> [12,  21, super]

(?<=-) is a positive lookbehind. It requires that the match be immediately preceded by a hyphen. /\A.+-/ matches the beginning of the string followed by one or more characters followed by a hyphen. Because regular expressions are by default greedy, it concludes the match on the second hyphen.

Note that it is not necessary to use regular expressions:

array.sort_by { |s| [s.to_i, s[s.index('-')+1..-1].to_i, s[s.rindex('-')+1..-1]] }

Comments

0

You can chain several #sort method calls, each sorting by a different part of a string (starting with one with smallest priority):

array.sort { |a,b| a.match(/-(.*)$/)[1] <=> b.match(/-(.*)-/)[1] } # sort by last element ('classic', 'super')
     .sort { |a,b| a.match(/-(\d+)-/)[1].to_i <=> b.match(/-(\d+)-/)[1].to_i } # sort by the number between dashes 
     .sort { |a,b| a.to_i <=> b.to_i  } # sort by the initial number

=> ["1mo-20-extra",
 "1mo-30-classic",
 "1mo-30-super",
 "1mo-40-classic",
 "1mo-110-super",
 "6mo-11-super",
 "6mo-21-super",
 "12mo-21-super",
 "12mo-21-classic"]

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.