如何在不使用reverse=True参数的情况下将一个字符串列表按照相反顺序排序?

9

我想要按照相反的顺序排序一个字符串列表,例如:

my_list = ['aaa', 'bbb', 'ccc']

预期结果:

['ccc', 'bbb', 'aaa']

我不想使用 sorted(my_list, reverse=True),因为在更复杂的情况下,当两个值同时进行筛选时,它将无法正常工作。例如:

my_list2 = [('aaa', 'bbb'), ('aaa', 'ccc'), ('bbb', 'aaa'), ('bbb', 'ccc')]

预期的结果应该是:

[('bbb', 'aaa'), ('bbb', 'ccc'), ('aaa', 'bbb'), ('aaa', 'ccc')]

sorted(my_list2,reverse=True) 返回:

[('bbb', 'ccc'), ('bbb', 'aaa'), ('aaa', 'ccc'), ('aaa', 'bbb')]

对于数字来说很简单,你可以取反数值:

>>> my_list3 = [(1, 2), (1, 3), (2, 1), (2, 3)]
>>> sorted(my_list3, key=lambda x: (-x[0], x[1]))
... [(2, 1), (2, 3), (1, 2), (1, 3)]

但是如何在字符串中实现呢?

你能解释一下 [('aaa', 'bbb'), ('aaa', 'ccc'), ('bbb', 'aaa'), ('bbb', 'ccc')] 怎么会变成 [('bbb', 'aaa'), ('bbb', 'ccc'), ('aaa', 'bbb'), ('aaa', 'ccc')] 吗? - Devesh Kumar Singh
1
@DeveshKumarSingh 外部或第一项为降序,内部项为升序。 - Paritosh Singh
@DeveshKumarSingh:按第一个元素降序排序('bbb'在'aaa'之前),然后在第一个元素相等的元素之间,按第二个元素升序排序(对于第一个元素为'bbb'的元组,'aaa'排在'ccc'之前)。 - Martijn Pieters
4个回答

18

您需要进行两次排序。Python的排序算法是稳定的,这意味着相等的元素会保持它们的相对顺序。首先按第二个元素排序(升序),然后再次排序,只按第一个元素且降序排列:

sorted(sorted(my_list2, key=lambda t: t[1]), key=lambda t: t[0], reverse=True)

使用 operator.itemgetter() 代替 lambda 可以使这个小程序更快(避免每个元素都要返回 Python 解释器):

from operator import itemgetter

sorted(sorted(my_list2, key=itemgetter(1)), key=itemgetter(0), reverse=True)

演示:

>>> from operator import itemgetter
>>> my_list2 = [('aaa', 'bbb'), ('aaa', 'ccc'), ('bbb', 'aaa'), ('bbb', 'ccc')]
>>> sorted(sorted(my_list2, key=lambda t: t[1]), key=lambda t: t[0], reverse=True)
[('bbb', 'aaa'), ('bbb', 'ccc'), ('aaa', 'bbb'), ('aaa', 'ccc')]
>>> sorted(sorted(my_list2, key=itemgetter(1)), key=itemgetter(0), reverse=True)
[('bbb', 'aaa'), ('bbb', 'ccc'), ('aaa', 'bbb'), ('aaa', 'ccc')]

一般规则是从内部元素到外部元素进行排序。因此,对于任意数量的元素排序,使用一个键和一个反转布尔值,可以使用functools.reduce()函数来应用这些规则。请参考functools.reduce()文档。
from functools import reduce
from operator import itemgetter

def sort_multiple(sequence, *sort_order):
    """Sort a sequence by multiple criteria.

    Accepts a sequence and 0 or more (key, reverse) tuples, where
    the key is a callable used to extract the value to sort on
    from the input sequence, and reverse is a boolean dictating if
    this value is sorted in ascending or descending order.

    """
    return reduce(
        lambda s, order: sorted(s, key=order[0], reverse=order[1]),
        reversed(sort_order),
        sequence
    )

sort_multiple(my_list2, (itemgetter(0), True), (itemgetter(1), False))

如果我需要按超过两个值进行排序怎么办?一行代码会看起来很难读。你能提供一个例子,在其中排序是通过单独的代码行完成的吗? - niekas
1
@MartijnPieters,你的回答太棒了!我这辈子都想不到这个! - Devesh Kumar Singh

3
如果'my_list2'只包含ASCII字符,你可以尝试以下方法:
sorted(my_list2, key=lambda t: (t[0],[255-ord(c) for c in list(t[1])]), reverse=True)                                
[('bbb', 'aaa'), ('bbb', 'ccc'), ('aaa', 'bbb'), ('aaa', 'ccc')]

不错的方法,但在我的情况下,字符串是UTF-8编码的,并包含非ASCII字符。有趣的是要检查-ord(c)是否涵盖了所有UTF-8的情况。 - niekas
@niekas 你说得对。它必须包含,因为“字符串的词典排序使用Unicode代码点…”(https://docs.python.org/3/tutorial/datastructures.html#comparing-sequences-and-other-types)。而且在我的代码中255是不必要的。 - kantal

1
另一种解决方案是创建一个带有比较函数的类作为键。您只需要定义__lt__,用于sort/sorted使用。
def reverse_key(reverse):
    class C:
        def __init__(self, obj):
            self.obj = obj
        def __lt__(self, other):
            for a, b, r in zip(self.obj, other.obj, reverse):
                if a < b:
                    return not r
                elif a > b:
                    return r
            return False
    return C

这可以与 sort/sorted by 一起使用。

my_list2 = [('aaa', 'bbb'), ('aaa', 'ccc'), ('bbb', 'aaa'), ('bbb', 'ccc')]
sorted(my_list2, key=reverse_key([True, False]))

正如所见,这样可以仅调用一次sort/sorted。在性能方面,如果您要排序的列表中的每个元组都包含大量项目,则可能比已接受的答案更快。

0

您可以对该值进行负ord操作,这对ASCII字符串有效:

>>> sorted(my_list2, key=lambda x: ([-ord(l) for l in x[0]], x[1]))
[('bbb', 'aaa'), ('bbb', 'ccc'), ('aaa', 'bbb'), ('aaa', 'ccc')]

对于非ASCII字符,您可以选择如何进行排序:

>>> sorted(my_list2, key=lambda x: ([-ord(l) for l in x[0]], x[1]))
[('ébb', 'écc'), ('bbb', 'aaa'), ('aaa', 'bbb'), ('aaa', 'ccc')]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接