5

In Sorting a Python list by two criteria Fouad gave the following answer:

sorted(list, key=lambda x: (x[0], -x[1]))

I'd like to sort the following list primarily on the list of tuples primarily on the second item in each element in ascending order, followed by the first (alphabetic) item in descending order:

[('Ayoz', 1, 18, 7), ('Aidan', 2, 4, 9), ('Alan', 2, 4, 9), ('Arlan', 5, 6, 7), ('Luke', 15, 16, 2), ('Tariq', 5, 4, 2)] 

to give the answer:

[('Ayoz', 1, 18, 7), ('Alan', 2, 4, 9), ('Aidan', 2, 4, 9), ('Tariq', 5, 4, 2), ('Arlan', 5, 6, 7), ('Luke', 15, 16, 2)]

using the above approach if possible . I tried

tlist = [('Ayoz', 1, 18, 7), ('Aidan', 2, 4, 9), ('Alan', 2, 4, 9), ('Arlan', 5, 6, 7), ('Luke', 15, 16, 2), ('Tariq', 5, 4, 2)]
sorted(tlist, key=lambda elem: (elem[1], -elem[0]))

but that only works when elem[0] is numeric (in this case it gives a TypeError: bad operand type for unary -: 'str')

I'll be grateful for any help. Python version is 3.4

Community
  • 1
  • 1
Disnami
  • 2,785
  • 2
  • 15
  • 15

4 Answers4

2
tlist = [('Ayoz', 1, 18, 7), ('Alan', 2, 4, 9), ('Aidan', 2, 4, 9), ('Arlan', 5, 6, 7), ('Luke', 15, 16, 2), ('Tariq', 5, 4, 2)] 

sorted(tlist, key=lambda elem: (elem[1],sorted(elem[0],reverse=True)))

Worked it out but it took me half an hour to type so I'm posting no matter what. I still welcome a better way of doing it.

Disnami
  • 2,785
  • 2
  • 15
  • 15
2

You can give the original answer a twist to get it work:

sorted(tlist, key=lambda elem: (-elem[1], elem[0]), reverse=True)
Berci
  • 474
  • 1
  • 5
  • 10
  • I know it says don't use comments to say thanks but it seems rude not to. Thanks. I think I prefer your approach for simpler sorting (It wouldn't work if you were sorting on two or more string elements with some ascending and the others descending). – Disnami Feb 14 '16 at 01:09
2

The built in sorting routines in Python are stable. That is, if two items have the same key value, then they keep the order they had relative to each other (the one closer to the front of the list stays closer to the front). So you can sort on multiple keys using multiple sorting passes.

from operator import itemgetter

tlist = [('Ayoz', 1, 18, 7), ('Aidan', 2, 4, 9), ('Alan', 2, 4, 9),
         ('Arlan', 5, 6, 7), ('Luke', 15, 16, 2), ('Tariq', 5, 4, 2)]

# sort by name in descending order
tlist.sort(key=itemgetter(0), reverse=True) 
print('pass 1:', tlist)

# sort by element 1 in ascending order.  If two items have the same value
# the names stay in the same order they had (descending order)
tlist.sort(key=itemgetter(1))
print(npass 2:', tlist)

Prints:

pass 1: [('Tariq', 5, 4, 2), ('Luke', 15, 16, 2), ('Ayoz', 1, 18, 7), ('Arlan', 5, 6, 7), ('Alan', 2, 4, 9), ('Aidan', 2, 4, 9)]

pass 2: [('Ayoz', 1, 18, 7), ('Alan', 2, 4, 9), ('Aidan', 2, 4, 9), ('Tariq', 5, 4, 2), ('Arlan', 5, 6, 7), ('Luke', 15, 16, 2)]
RootTwo
  • 4,108
  • 1
  • 10
  • 14
0

Here's an alternative. It works like the trick to use -number to reverse the order of a sort, but applies to strings of letters.

The table below maps 'a' to 'z', 'b' to 'y', etc. t[0].translate(table) translates 'Ayoz' to 'Zbla', so the key for ('Ayoz', 1, 18, 7) is (1, Zbla')

table = str.maketrans('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
                      'zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA')

tlist.sort(key=lambda t: (t[1], t[0].translate(table)))
RootTwo
  • 4,108
  • 1
  • 10
  • 14