11

If I have a list of tuples:

results = [('10', 'Mary'), ('9', 'John'), ('10', 'George'), ('9', 'Frank'), ('9', 'Adam')]

How can I sort the list as you might see in a scoreboard - such that it will sort the score from biggest to smallest, but break ties alphabetically by name?

So after the sort, the list should look like:

results = [('10', 'George'), ('10', 'Mary'), ('9', 'Adam'), ('9', 'Frank'), ('9', 'John')]

At the moment all I can do is results.sort(reverse=True), but breaks ties reverse alphabetically too...

Any help would be much appreciated. Thanks!

chillman
  • 123
  • 1
  • 1
  • 5
  • 1
    `'10' < '9' is True` and `'Adam' < 'Frank' is True`. I guess you want make `'10' > '9'`? – Kabie Aug 24 '13 at 04:54
  • Why do you have strings instead of ints for your numbers? – user2357112 Aug 24 '13 at 04:55
  • That's a good point!! I didn't think about that. All my numbers are ints now, but I'm still unsure as to how to sort reverse and break ties non-reverse... – chillman Aug 24 '13 at 05:45

4 Answers4

24

The simplest way to achieve what you want is to use the fact that python sort is stable. This allows to first sort alphabetically and then by score:

In [11]: results = [(10, 'Mary'), (9, 'John'), (10, 'George'), (9, 'Frank'), (9, 'Adam')]

In [12]: results.sort(key=lambda x: x[1])

In [13]: results.sort(key=lambda x: x[0], reverse=True)

In [14]: results
Out[14]: [(10, 'George'), (10, 'Mary'), (9, 'Adam'), (9, 'Frank'), (9, 'John')]

The first sort sorts alphabetically, in ascending order. The second sort sorts by score, in descending order, maintaining the relative order of elements with equal score.

You can do this to do even more complex sorts. Just remember that you must first sort by the secondary key, and then by the first key. (If you have three keys, first sort by the third, then by the second, and lastly by the main key).

If you don't want to call sort twice you'll have to write a more complex key function. Something like:

In [50]: def key(elem):
    ...:     return elem[0], [-ord(c) for c in elem[1]]

In [51]: sorted(results, key=key, reverse=True)
Out[51]: [(10, 'George'), (10, 'Mary'), (9, 'Adam'), (9, 'Frank'), (9, 'John')]

In particular, every time you have something sorted in lexicographic order(such as strings, tuples, lists etc.), you can invert the order by changing the sign to all the elements.

Bakuriu
  • 98,325
  • 22
  • 197
  • 231
  • 2
    It may be worth noting that you can avoid some of the complexity in your "one sort call" example by using the default ascending sort and reversing the number instead of the string `sorted(results, key=lambda x: (-x[0], x[1]))` – Daniel Dec 05 '16 at 00:46
7

sort method accept optional key parameter.

key specifies a function of one argument that is used to extract a comparison key from each list element

You need to convert string to number:

>>> results = [('10', 'Mary'), ('9', 'John'), ('10', 'George'), ('9', 'Frank'), ('9', 'Adam')]
>>> results.sort(key=lambda x: (int(x[0]), x[1]), reverse=True)
>>> results
[('10', 'Mary'), ('10', 'George'), ('9', 'John'), ('9', 'Frank'), ('9', 'Adam')]
falsetru
  • 357,413
  • 63
  • 732
  • 636
1

You can have very granular control on how to sort each of the values in a tuple with sort method's key parameter. For example:

In [41]: results = [('10', 'Mary'), ('9', 'John'), ('10', 'George'), ('9', 'Frank'), ('9', 'Adam')]

In [42]: results.sort(key=lambda (score, name): (-int(score), name))

In [43]: results
Out[43]: 
[('10', 'George'),
 ('10', 'Mary'),
 ('9', 'Adam'),
 ('9', 'Frank'),
 ('9', 'John')]
AnukuL
  • 595
  • 1
  • 7
  • 21
-1

Just using sorted should suffice

>>> sorted(results)
[('10', 'George'), ('10', 'Mary'), ('9', 'Adam'), ('9', 'Frank'), ('9', 'John')]

default cmp function checks precedence of a tuple by comparing each member of tuple, from 0 to n, in that order

Anycorn
  • 50,217
  • 42
  • 167
  • 261
  • Ah, I see - problem is that if the numbers are anything else, this wont work... bad example I suppose :) sorry – chillman Aug 24 '13 at 05:43