3

Assume we have a list of 5 tuple:

(a, b , c, d, e)

Let the list be student_tuples.

I wish to sort the list different orders for different fields.

The below mentioned command

sorted(student_tuples, key=itemgetter(2,4,0,1))

Will sort the list on ascending order for all the fields.

The below mentioned command

sorted(student_tuples, key=itemgetter(2,4,0,1), reverse=true)

Will sort the list on descending order for all the fields. What I am looking for is sorting a list on different orders for different fields. Is there a easy way to do so.

Based on the answers the technique could be used in any language

Thanks, Gudge

gudge
  • 1,053
  • 4
  • 18
  • 33

2 Answers2

9

If the values are numeric, you can do this easily using lambda:

sorted(student_tuples, key=lambda x: (x[2],x[4],-x[0],x[1]))
                                                #^ This field will be 
                                                #  in descending order 

If you can't easily negate the order inside a lambda function, you need to rely on the stableness of python sorting and sort a few times:

s = sorted(student_tuples, key=itemgetter(1))
s.sort(key=itemgetter(0),reversed=True)
s.sort(key=itemgetter(2,4))

I explain it in more depth in this answer.

Proof that my answers above accomplish the same thing (with numeric input):

import random
def rand_tuple():
    """ Return a random 5-tuple """
    return tuple( random.random() for _ in range(5) )

#100 random 5-tuples
lst = [ rand_tuple() for _ in range(100) ] 

#sort the list using method 1
sorted_lst = sorted(lst, key = lambda x: (x[2],x[4],-x[0],x[1])) 

#sort the list in place using method 2
lst.sort(key = itemgetter(1))  #<- Rightmost tuple element first!!!
lst.sort(key = itemgetter(0), reversed = True)
lst.sort(key = itemgetter(2,4))

print (lst == sorted_lst) #True -- Results are the same :-)
Community
  • 1
  • 1
mgilson
  • 300,191
  • 65
  • 633
  • 696
  • sorted(student_tuples, key=lambda x: x[2],x[4],-x[0],x[1]) I get the following error message: SyntaxError: non-keyword arg after keyword arg – gudge Aug 16 '12 at 18:26
  • @gudge -- That's because I forgot the parenthesis. See the updated answer. – mgilson Aug 16 '12 at 18:27
  • Thanks, It works. The second solution is more elegant and your explanation in the link is very good. – gudge Aug 16 '12 at 18:39
  • Hi, I don't think the solution works: x = [(100, 103), (100, 105), (98, 104)] Sort in ascending order for the first field: [(98, 104), (100, 103), (100, 105)] Sort in descending order on second field [(98, 104), (100, 105), (100, 103)] – gudge Aug 16 '12 at 19:11
  • @gudge -- I'm confused. What's not working? One thing that is counter-intuitive in the multiple sort example is that you need to sort on the least significant field first. – mgilson Aug 16 '12 at 19:15
  • Sorry for the confusion. In my above comment I have specified the output which I wish to have which I get through your first solution (-negation example). s = sorted(x, key=itemgetter(0)) s.sort(key=itemgetter(1), reverse=True) This leads to: print s [(100, 105), (98, 104), (100, 103)] This does not preserve the order of the first sort. Thanks – gudge Aug 16 '12 at 19:21
  • 1
    @gudge -- It will only preserve the order of the first sort if the `key` function returns the same value in the subsequent sort. My two examples should be equivalent (notice how in the multi-sort context, I use the rightmost index (1) as the sort key *first*). – mgilson Aug 16 '12 at 19:24
  • Got you!. The solution is correct. – gudge Aug 16 '12 at 19:35
  • @gudge -- Glad to help. I even added some proof using random tuples for posterity :-). I like sorting in python. It's pretty neat. One of these days I'll get around to timing the two methods here. I'm guessing the first will be faster, but sometimes python surprises me. – mgilson Aug 16 '12 at 19:40
  • I was getting two involved in the method. The idea is to reverse the steps so that the fields get there due importance at each stage. – gudge Aug 16 '12 at 19:48
  • @gudge -- That's a great way to remember it. If I can manage to keep that explanation in my head I may use it to explain sort stability to people in the future. – mgilson Aug 16 '12 at 19:51
  • The technique you mentioned can be used for sorting in any language not just python. – gudge Aug 16 '12 at 20:38
1

You could create a class with meaningful attribute names instead of just numeric indices; that would sort easily if you give it __cmp__ (python 2.x) or __eq__ and __lt__ plus @total_ordering (python 3.x).

Another option would be to keep the tuples, convert them to lists, and negate any numeric fields that you need to sort in reverse. You can kind of do this for strings, but it's not as neat as for numbers.

Part of the reason tuples sort fast, is that they aren't super flexible.

user1277476
  • 2,871
  • 12
  • 10
  • You might want to mention where `total_ordering` can be found ( i.e. `functools.total_ordering`) – mgilson Aug 16 '12 at 19:44