25

I have a mulitdimensional array like so:

[
  [name, age, date, gender]
  [name, age, date, gender]
  [..]
]

I'm wondering the best way to sort this array based on multiple conditions...For instance, how would I sort based on age first then by name?

I was messing around with the sort method like so:

array.sort { |a,b| [ a[1], a[0] ] <=> [ b[1], b[0] ] }

Besides that I don't really understand this syntax, I'm not getting the results I would expect. Should I be using the sort method? Should I be individually comparing results by mapping the array?

pruett
  • 2,101
  • 3
  • 22
  • 36
  • 1
    possible duplicate of [Sort a collection of objects by number (highest first) then by letter (alphabetical)](http://stackoverflow.com/questions/2232470/sort-a-collection-of-objects-by-number-highest-first-then-by-letter-alphabeti) – Andrew Grimm Jan 17 '12 at 02:15
  • 1
    @pruett: no disrespect for robbrit's answer, but you should consider the selected answer, there is nothing wrong with using Enumerable#sort *except* when Enumerable#sort_by does the job. This can be misleading for people landing here. – tokland Jan 17 '12 at 08:11
  • Possible duplicate of [Ruby sort by multiple values?](http://stackoverflow.com/questions/4309723/ruby-sort-by-multiple-values) – jtbandes Nov 08 '16 at 05:21

3 Answers3

50

You should always use sort_by for a keyed sort. Not only is it much more readable, it is also much more efficient. In addition, I would also prefer to use destructuring bind, again, for readability:

array.sort_by {|name, age| [age, name] }
Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
16

This should do the trick:

array.sort { |a,b| [ a[1], a[0] ] <=> [ b[1], b[0] ] }

So what does this do? It uses a lot of Ruby idioms.

  • First is blocks, which are sort of like callbacks or anonymous functions/classes in other languages. The sort method of Array uses them to compare two elements based on the return value of the block. You can read all about them here.
  • Next is the <=> operator. It returns -1 if the first argument is less than the second, 0 if they are equal, and 1 if the first is greater than the second. When you use it with arrays, it will compare the arrays element-wise until one of them returns -1 or 1. If the arrays are equal, you will get 0.
robbrit
  • 17,560
  • 4
  • 48
  • 68
7

As I understand it you want to order by age first, and then if more than one record has the same age, arrange that subset by name.


This works for me

people = [
      ["bob", 15, "male"], 
      ["alice", 25, "female"], 
      ["bob", 56, "male"], 
      ["dave", 45, "male"], 
      ["alice", 56, "female"], 
      ["adam", 15, "male"]
    ]

people.sort{|a,b| (a[1] <=> b[1]) == 0 ? (a[0] <=> b[0]) : (a[1] <=> b[1]) }

# The sorted array is

[["adam", 15, "male"], 
 ["bob", 15, "male"], 
 ["alice", 25, "female"], 
 ["dave", 45, "male"], 
 ["alice", 56, "female"], 
 ["bob", 56, "male"]]

What this is doing is comparing by age first, and if the age is the same (<=> returs 0) it comparing the name.

Fab
  • 665
  • 3
  • 9