简体   繁体   中英

Python: How to perform a secondary descending alphabetic sort within a numeric primary sort

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

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)]
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.

您可以对原始答案进行修改以使其正常工作:

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

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)))

I just ran into this, and I was surprised to find that it's a missing piece in Python's otherwise robust sorting. You can reverse the overall sort, but you can't reverse subsorts. The other answers are usable workarounds, but they aren't general solutions if you have a list of fields you want to sort with varying subsort order, especially if multiple-pass sorting isn't an option.

This is what I ended up with:

class inverse_str(str):
    """
    A string that sorts in inverse order.
    """
    def __lt__(self, rhs):
        return not super().__lt__(rhs)

data = [('Ayoz', 1, 18, 7), ('Aidan', 2, 4, 9), ('Alan', 2, 4, 9), ('Arlan', 5, 6, 7), ('Luke', 15, 16, 2), ('Tariq', 5, 4, 2)]
data.sort(key=lambda item: (item[1], inverse_str(item[0])))
print(data)

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

That has the same effect as returning eg. 1-item[1] from the key function for a float field.

RootTwo's multi-stage sort answer is valid, but multiple-step sorting isn't always an option. I'm merging incremental previously-sorted streams of data using heapq.merge, so that won't work. Doing it within the comparison does this within the actual ordering, so you don't need multiple passes and it works in the general case.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM