1

I have got an array with those strings

["Cat 2", "Goal A", "Goal B", "Goal C", "Target 1", "Target 11", "Target 4"]

Those values are already sorted alphabetically but as you can see, they are not sorted numerically. I would like to keep the alphabetic sorting but now I would like to sort them numerically, each group of string (strings which start with the same word, i.e. all the "Target" together) need to be sorted numerically. The result needs to be

["Cat 2", "Goal A", "Goal B", "Goal C", "Target 1", "Target 4", "Target 11"]

and not

["Goal A", "Goal B", "Goal C", "Target 1", "Cat 2", "Target 4", "Target 11"]

I don't really know how to do that, any help welcome !!

4
  • 3
    This is underspecified, I feel. In case of "Goal 15", should that be placed between "Target 13" and "Target 20"? Before "Goal A"? After "Goal C"? Commented Aug 24, 2022 at 17:19
  • 1
    Could there be elements "Dog woof" and "Cat 5"? If so, what would be the desired sorted array? In future I suggest you reduce the size of your example to the minimum required to make the point. There is no need, for example, for so many "Target X"'s.' Commented Aug 24, 2022 at 18:04
  • sort accepts a block which, for two arguments, decides which argument comes first in the sorting order. Basically this is what defines the sorting logic. I would first split the words into two parts, and based on which define what comes first. Only you know the algorithm, because from your description, I don't understand how i.e. ['Goal A', 'Goal 06', 'Goal B', 'Goal 4', 'Goal 4x'] is to be sorted. Commented Aug 25, 2022 at 6:47
  • Indeed I went a bit fast on writing my question. I have edited it. Commented Aug 25, 2022 at 9:34

3 Answers 3

1
array.sort_by {|e| e.split(/(\d+)/).map {|a| a =~ /\d+/ ? a.to_i : a }}
Sign up to request clarification or add additional context in comments.

Comments

1

As Cary Swoveland, but just use \d+ for numbers

arr = [
  "Target 4", "Goal C", "Target 11", "Target 13", "Goal B", "Target 3",
  "Target 1", "Target 2", "Target 12", "Target 20", "Target 21",
  "Target 10", "Goal A"
]
arr.sort_by { |s| [s[/\d+/].to_i, s] }

# => ["Goal A", "Goal B", "Goal C", "Target 1", "Target 2", "Target 3",
#    "Target 4", "Target 10", "Target 11", "Target 12", "Target 13",
#    "Target 20", "Target 21"]

1 Comment

Watch out for "Target 0" when "Goal" is replaced with "Zebra", and with "Goal A1".
1
arr = [
  "Target 4", "Goal C", "Target 11", "Target 13", "Goal B", "Target 3",
  "Target 1", "Target 2", "Target 12", "Target 20", "Target 21",
  "Target 10", "Goal A", "Dog D", "Cat 5"
]
arr.sort_by do |s|
  t = s[/\S+\z/]
  Integer(t, exception: false) ? [1, t.to_i] : [0, t]
end
  #=> ["Goal A", "Goal B", "Goal C", "Dog D", "Target 1", "Target 2",
  #    "Target 3", "Target 4", "Cat 5", "Target 10", "Target 11",
  #    "Target 12", "Target 13", "Target 20", "Target 21"]

My additions of "Dog D" and "Cat 5" were to illustrate my assumption of how the sort was to be performed; that is, on the end of the string only.

See Kernel#Integer.

Items are compared using Array#<=>. See especially the third paragraph of that doc. Thus, the first element of the array returned by Enumerable#sort_by ensures that in the sorted array strings ending in a string of digits appear after those that do not. The second element of the array returned by sort_by breaks the ties.

The regular expression /\S+\z/ is read, "Match one or more (+) non-whitespace characters (\S) followed by the end of the string (\z)". When s = "Target 12", for example, t = s[/\S+\z/] #=> "12". See the fourth form of String#[] at that link.


For a different sorting assumption (which should be obvious for the example array, before and after it is sorted) we could write:

arr = [
  "Target 4", "XGoal C", "ATarget 11", "Target 13", "Goal B", "Target 3",
  "Target 1", "Target 2", "Target 12", "ATarget 20", "Target 21",
  "ATarget 10", "XGoal A"
]

arr.sort_by do |s|
  b, e = s.split
  Integer(e, exception: false) ? [1, b, e.to_i] : [0, b, e]
end
  #=> ["Goal B", "XGoal A", "XGoal C", "ATarget 10", "ATarget 11",
  #    "ATarget 20", "Target 1", "Target 2", "Target 3", "Target 4",
  #    "Target 12", "Target 13", "Target 21"]

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.