按多个属性对列表进行排序?

696

我有一个列表的列表:

[[12, 'tall', 'blue', 1],
[2, 'short', 'red', 9],
[4, 'tall', 'blue', 13]]
如果我想按一个元素进行排序,比如高矮元素,我可以通过 s = sorted(s, key=itemgetter(1)) 来实现。
如果我想按高矮和颜色两个元素进行排序,我可以分别对每个元素进行排序,但是有更快的方法吗?

相关PPCG问题 - mbomb007
35
如果你使用元组而不是列表,当你运行 sort 命令时,Python 会按照从左到右的顺序对条目进行排序。也就是说,sorted([(4, 2), (0, 3), (0, 1)]) == [(0, 1), (0, 3), (4, 2)] - Mateen Ulhaq
1
如果两个键的排序顺序不同,请参考以下链接:sorting - sort Python list with two keys but only one in reverse order - Stack Overflow当前已接受答案下的评论 - user202729
8个回答

1197

一个键可以是返回元组的函数:

s = sorted(s, key = lambda x: (x[1], x[2]))

或者你可以使用itemgetter来实现同样的功能(它更快且避免了Python函数调用):

import operator
s = sorted(s, key = operator.itemgetter(1, 2))

请注意在这里您可以使用sort而不是使用sorted,然后重新分配:

s.sort(key = operator.itemgetter(1, 2))

28
为了完整性,从timeit测试结果来看:第一个测试每个循环花费了6微秒,而第二个测试每个循环花费了4.4微秒。 - Brian Larsen
31
有没有一种方法可以将第一个按升序排序,第二个按降序排序?(假设这两个属性都是字符串,因此不能像整数那样添加“-”之类的技巧) - Martin Thoma
98
如果我想仅对 x[1] 应用 reverse=True,是否可以实现? - Amyth
37
@moose,@Amyth,若想将结果反转至只有一个属性,请进行两次排序: 首先按第二属性排序: s = sorted(s, key=operator.itemgetter(2)) 然后按主属性逆序排序: s = sorted(s, key=operator.itemgetter(1), reverse=True) 虽不完美,但可行。 - tomcounsell
73
如果关键字是数字,要使其反向,可以将其乘以“-1”。 - Serge
显示剩余13条评论

58

我不确定这是否是最Pythonic的方法... 我有一个需要按照整数值降序和字母顺序排序的元组列表。这需要反转整数排序,但不需要反转字母排序。这是我的解决方案:(在考试中即兴发挥,顺便提一下,我甚至不知道你可以“嵌套”排序函数)。

a = [('Al', 2),('Bill', 1),('Carol', 2), ('Abel', 3), ('Zeke', 2), ('Chris', 1)]  
b = sorted(sorted(a, key = lambda x : x[0]), key = lambda x : x[1], reverse = True)  
print(b)  
[('Abel', 3), ('Al', 2), ('Carol', 2), ('Zeke', 2), ('Bill', 1), ('Chris', 1)]

28
由于2是一个数字,可以这样做:b = sorted(a, key=lambda x: (-x[1], x[0])),更容易看出先应用哪个标准。至于效率方面,我不确定,需要有人进行时间测试。 - Andrei-Niculae Petre

24

虽然有些晚,但是我想实现排序的两个条件使用reverse=True。以防其他人也想知道如何做到,可以将您的条件(函数)括在括号中:

s = sorted(my_list, key=lambda i: ( criteria_1(i), criteria_2(i) ), reverse=True)

你能举个例子吗? - undefined

7

看起来你可以使用列表(list)而不是元组(tuple)。我认为,当你获取列表/元组的属性而不是使用“魔术索引”时,这更加重要。

在我的情况下,我想按类的多个属性进行排序,其中传入的键是字符串。我需要在不同的位置进行不同的排序,并且我希望父类有一个通用的默认排序,客户端与之交互; 仅在真正“需要”的时候覆盖“排序键”,但也以一种我可以将它们存储为类可以共享的列表的方式。

因此,首先我定义了一个辅助方法:

def attr_sort(self, attrs=['someAttributeString']:
  '''helper to sort by the attributes named by strings of attrs in order'''
  return lambda k: [ getattr(k, attr) for attr in attrs ]

然后使用它。
# would defined elsewhere but showing here for consiseness
self.SortListA = ['attrA', 'attrB']
self.SortListB = ['attrC', 'attrA']
records = .... #list of my objects to sort
records.sort(key=self.attr_sort(attrs=self.SortListA))
# perhaps later nearby or in another function
more_records = .... #another list
more_records.sort(key=self.attr_sort(attrs=self.SortListB))

这将使用生成的 lambda 函数按照 object.attrAobject.attrB 的顺序对列表进行排序,假设 object 具有与提供的字符串名称相对应的 getter。第二个案例将按照 object.attrC 然后按照 object.attrA 进行排序。
这还允许您潜在地公开外部排序选择,以便由消费者、单元测试或者告诉您他们想要对 API 中的某些操作进行排序的人共享,只需给您一个列表而不是将其耦合到您的后端实现中。

干得好。如果属性应该按不同的顺序排序怎么办?假设attrA应该按升序排序,而attrB应该按降序排序?有没有一个快速的解决方案呢?谢谢! - mhn_namak
@mhn_namak 请查看 https://dev59.com/trPma4cB1Zd3GeqPrX_y#55866810,这是一种优美的方法,可以按照 n 个标准进行排序,每个标准可以是升序或降序。 - Razzle Shazl
我们对美的看法显然非常不同。虽然它能够完成工作,但它是我见过的最丑陋的东西。而且效率成为一个函数 (n*m),其中 m 是要排序的属性数,而不仅仅是列表长度的函数。我认为其他答案可能有更好的解决方案,或者如果你真的需要这种行为,你可以编写自己的排序函数来实现它。 - UpAndAdam

6

将列表列表转换为元组列表,然后按多个字段对元组进行排序。

 data=[[12, 'tall', 'blue', 1],[2, 'short', 'red', 9],[4, 'tall', 'blue', 13]]

 data=[tuple(x) for x in data]
 result = sorted(data, key = lambda x: (x[1], x[2]))
 print(result)

输出:

 [(2, 'short', 'red', 9), (12, 'tall', 'blue', 1), (4, 'tall', 'blue', 13)]

2
这是一种方法:您可以重新编写排序函数,以接受一个排序函数列表,每个排序函数都比较您要测试的属性,在每个排序测试中,您查看并查看cmp函数是否返回非零返回值,如果是,则中断并发送返回值。 通过调用Lambda函数的列表函数来调用它。
它的优点是它只需单次通过数据而不是先前排序的排序。另一件事是它在原地排序,而sorted似乎会复制。
我用它来编写排名函数,该函数对类列表进行排名,其中每个对象都在一个组中并具有得分函数,但您可以添加任何属性列表。 请注意,虽然使用lambda调用setter的方式不像lambda,但很hackish。 排名部分不适用于数组列表,但排序将适用。
#First, here's  a pure list version
my_sortLambdaLst = [lambda x,y:cmp(x[0], y[0]), lambda x,y:cmp(x[1], y[1])]
def multi_attribute_sort(x,y):
    r = 0
    for l in my_sortLambdaLst:
        r = l(x,y)
        if r!=0: return r #keep looping till you see a difference
    return r

Lst = [(4, 2.0), (4, 0.01), (4, 0.9), (4, 0.999),(4, 0.2), (1, 2.0), (1, 0.01), (1, 0.9), (1, 0.999), (1, 0.2) ]
Lst.sort(lambda x,y:multi_attribute_sort(x,y)) #The Lambda of the Lambda
for rec in Lst: print str(rec)

这是一种对对象列表进行排名的方法。
class probe:
    def __init__(self, group, score):
        self.group = group
        self.score = score
        self.rank =-1
    def set_rank(self, r):
        self.rank = r
    def __str__(self):
        return '\t'.join([str(self.group), str(self.score), str(self.rank)]) 


def RankLst(inLst, group_lambda= lambda x:x.group, sortLambdaLst = [lambda x,y:cmp(x.group, y.group), lambda x,y:cmp(x.score, y.score)], SetRank_Lambda = lambda x, rank:x.set_rank(rank)):
    #Inner function is the only way (I could think of) to pass the sortLambdaLst into a sort function
    def multi_attribute_sort(x,y):
        r = 0
        for l in sortLambdaLst:
            r = l(x,y)
            if r!=0: return r #keep looping till you see a difference
        return r

    inLst.sort(lambda x,y:multi_attribute_sort(x,y))
    #Now Rank your probes
    rank = 0
    last_group = group_lambda(inLst[0])
    for i in range(len(inLst)):
        rec = inLst[i]
        group = group_lambda(rec)
        if last_group == group: 
            rank+=1
        else:
            rank=1
            last_group = group
        SetRank_Lambda(inLst[i], rank) #This is pure evil!! The lambda purists are gnashing their teeth

Lst = [probe(4, 2.0), probe(4, 0.01), probe(4, 0.9), probe(4, 0.999), probe(4, 0.2), probe(1, 2.0), probe(1, 0.01), probe(1, 0.9), probe(1, 0.999), probe(1, 0.2) ]

RankLst(Lst, group_lambda= lambda x:x.group, sortLambdaLst = [lambda x,y:cmp(x.group, y.group), lambda x,y:cmp(x.score, y.score)], SetRank_Lambda = lambda x, rank:x.set_rank(rank))
print '\t'.join(['group', 'score', 'rank']) 
for r in Lst: print r

0

在编程中,列表之间可以使用运算符 < 进行比较,例如:

[12, 'tall', 'blue', 1] < [4, 'tall', 'blue', 13]

将会给予

False

0
多重排序,能够针对每个属性指定升序/降序顺序。
from operator import itemgetter, attrgetter
from functools import cmp_to_key


def multikeysort(items, *columns, attrs=True) -> list:
    """
    Perform a multiple column sort on a list of dictionaries or objects.
    Args:
        items (list): List of dictionaries or objects to be sorted.
        *columns: Columns to sort by, optionally preceded by a '-' for descending order.
        attrs (bool): True if items are objects, False if items are dictionaries.

    Returns:
        list: Sorted list of items.
    """
    getter = attrgetter if attrs else itemgetter

    def get_comparers():
        comparers = []

        for col in columns:
            col = col.strip()
            if col.startswith('-'):  # If descending, strip '-' and create a comparer with reverse order
                key = getter(col[1:])
                order = -1
            else:  # If ascending, use the column directly
                key = getter(col)
                order = 1

            comparers.append((key, order))
        return comparers

    def custom_compare(left, right):
        """Custom comparison function to handle multiple keys"""
        for fn, reverse in get_comparers():
            result = (fn(left) > fn(right)) - (fn(left) < fn(right))
            if result != 0:
                return result * reverse
        return 0

    return sorted(items, key=cmp_to_key(custom_compare))

使用/测试SORT by DESC('opens'), ASC('clicks')

def test_sort_objects(self):
    Customer = namedtuple('Customer', ['id', 'opens', 'clicks'])

    customer1 = Customer(id=1, opens=4, clicks=8)
    customer2 = Customer(id=2, opens=4, clicks=7)
    customer3 = Customer(id=2, opens=5, clicks=1)
    customers = [customer1, customer2, customer3]

    sorted_customers = multikeysort(customers, '-opens', 'clicks')
    exp_sorted_customers = [customer3, customer2, customer1]
    self.assertEqual(exp_sorted_customers, sorted_customers)

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