27

Let's say I have an unsorted array from 1 to 10, as shown below...

a = ["3", "5", "8", "4", "1", "2", "9", "10", "7", "6"]

If I use the sort method on this array, it returns this...

a.sort = ["1", "10", "2", "3", "4", "5", "6", "7", "8", "9"]

As you can see, the 10, appears before the 2, which is incorrect. How can I sort these numbers so that 10 appears correctly?

EDIT: Thank you all for your responses. I should explain my problem a little better. The array I need sorted is for an e-commerce price list. So the array appears as follows...

a = ["0-10", "11-20", "21-30", "31-40" etc.]

So the strings cannot be converted to integers. I should have put this when I wrote the question. I did not think that there would be much difference in the fix. My mistake, I apologise for making this assumption! How though can I sort this array? Thanks!

peterh
  • 11,875
  • 18
  • 85
  • 108
tob88
  • 2,151
  • 8
  • 30
  • 33

8 Answers8

55

I'll throw another method out there since it's the shortest way I can think of

a.sort_by(&:to_i)
Wizard of Ogz
  • 12,543
  • 2
  • 41
  • 43
  • 1
    This is equivalent to bricker's answer, so his comment about the number of times `to_i` is called compared to Matt's solution applies here as well. – Wizard of Ogz Oct 11 '11 at 14:24
  • 2
    What is `&:`? That's a new syntax to me. – nipponese Jul 18 '17 at 20:22
  • 2
    @nipponese That is a good question. Here is a good thread which explains it in detail https://stackoverflow.com/questions/1961030/ruby-ampersand-colon-shortcut. – Wizard of Ogz Jul 18 '17 at 21:06
10

As your updated question states:

array.sort_by {|elt| ary = elt.split("-").map(&:to_i); ary[0] + ary[1]}

even geekier:

array.sort_by {|elt| ary = elt.split("-").map(&:to_i).inject(&:+)}
Harish Shetty
  • 64,083
  • 21
  • 152
  • 198
apneadiving
  • 114,565
  • 26
  • 219
  • 213
7

If you convert all strings to integers beforehand, it should work as expected:

a.map(&:to_i).sort
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Matt
  • 17,290
  • 7
  • 57
  • 71
  • Doesn't really matter for such a small array, but your method would be faster than mine for large arrays, calling to_i only half as many times. +1 – bricker Oct 11 '11 at 13:56
  • 1
    @bricker Matt's method also uses more memory as it creates a new array with `map` that your method doesn't. (You could use the in-place map `map!`, but that might not always be a viable option in your code) – Wizard of Ogz Oct 11 '11 at 14:13
6
a.sort { |a,b| a.to_i <=> b.to_i }
bricker
  • 8,911
  • 2
  • 44
  • 54
  • 3
    use Enumerable#sort_by instead – tokland Oct 11 '11 at 14:23
  • Why? The sort method works perfectly, I'd like to know why you recommend sort_by. – bricker Oct 11 '11 at 15:08
  • 7
    By definition xs.sort { |a, b| a.method <=> b.method } is totally equivalent to xs.sort_by(&:method). Why use sort when you have a shorter built-in exactly designed for this task? If that's not enough, there are also performance reasons, c&p from the docs: "As of Ruby 1.8, the method Enumerable#sort_by implements a built-in Schwartzian Transform, useful when key computation or comparison is expensive." – tokland Oct 11 '11 at 15:11
  • @tokland Thanks for the point about Schwartzian transforms occurring behind the scenes. – Wizard of Ogz May 01 '12 at 14:31
2

Another option, that can be used in your example or others arrays like

a = ["teste", "test", "teste2", "tes3te", "10teste"]

is:

a.sort_by! {|s| s[/\d+/].to_i}
1

The reason for this behavior is that you have an array of strings and the sort that is being applied is string-based. To get the proper, numeric, sorting you have to convert the strings to numbers or just keep them as numbers in the first place. Is there a reason that your array is being populate with strings like this:

a = ["3", "5", "8", "4", "1", "2", "9", "10", "7", "6"]

Rather than numbers like this:

a = [3, 5, 8, 4, 1, 2, 9, 1, 7, 6]

?

Paul Sasik
  • 79,492
  • 20
  • 149
  • 189
0

For the special case (e-commerce price list) you mentioned

a = ["0-10", "11-20", "21-30", "31-40"]

let's add a few more values to this array (as it was mentioned as price list). so

a = ["0-10", "11-20", "120-150", "110-120", "21-30", "31-40"]

we can sort such an array using the following

a.sort.sort_by(&:length)
0

The cheap way would be to zero fill on the left and make all numbers 2 digit.

Thom
  • 14,013
  • 25
  • 105
  • 185