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


454

我有一个清单清单:

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

如果我想按一个元素(例如,高/短元素)排序,则可以通过进行s = sorted(s, key = itemgetter(1))

如果我想同时根据高/短和颜色进行排序,我可以进行两次排序,每个元素一次,但是有一种更快的方法吗?



8
如果您使用元组而不是列表,则在运行时python订单将按条目从左到右排序sort。也就是说,sorted([(4, 2), (0, 3), (0, 1)]) == [(0, 1), (0, 3), (4, 2)]
Mateen Ulhaq '18 -10-24

Answers:


769

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

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

20
为了确保时间的完整性:对我来说,第一个循环给我们6个,第二个循环给我们4.4个
Brian Larsen

10
有没有一种方法可以对第一个升序和第二个降序进行排序?(假设两个属性都是字符串,那么就没有像-整数一样的技巧)
Martin Thoma

73
怎么样,如果我想申请revrse=Truex[1]可能呢?
Amyth,2014年

28
@ moose,@ Amyth,要反转为仅一个属性,您可以进行两次排序:首先是次要的,s = sorted(s, key = operator.itemgetter(2))然后是主要的。s = sorted(s, key = operator.itemgetter(1), reverse=True)不理想,但是可以。
tomcounsell 2015年

52
@Amyth或其他选项(如果键是数字),要使其反转,可以将其乘以-1
Serge 2016年

37

我不确定这是否是最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)]

12
由于2nd是一个数字,因此它的工作方式类似于b = sorted(a, key = lambda x: (-x[1], x[0]))在第一个应用标准时更明显。至于效率,我不确定,有人需要时间。
Andrei-Niculae Petre's

4

看来您可以使用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.attrA,然后object.attrB假定object具有与提供的字符串名称相对应的getter。到object.attrC那时,第二种情况将得到解决object.attrA

这还允许您潜在地暴露向外的排序选择,以供使用者,单元测试共享,或者让他们告诉您他们希望如何对api中的某些操作进行排序,而只需给您一个列表,而不是将它们耦合到您的后端实现。


辛苦了 如果属性应该以不同顺序排序怎么办?假设应将attrA升序排序并将attrB降序排序?在此之上有快速解决方案吗?谢谢!
mhn_namak

3

几年迟到了,但我想这两个排序2个标准使用reverse=True。如果其他人想知道如何做,则可以将您的条件(函数)括在括号中:

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

1

这是一种方法:您基本上是重写您的排序函数以获取一个排序函数列表,每个排序函数都会比较您要测试的属性,在每次排序测试中,您都会查看并查看cmp函数是否返回非零返回值如果是这样,则中断并发送返回值。您可以通过调用Lambda列表功能的Lambda来调用它。

它的优点是它可以单次通过数据,而不像其他方法那样通过以前的排序。另一件事是,它排序到位,而排序似乎可以复制。

我用它编写了一个等级函数,该函数对每个对象在一个组中并具有得分函数的类列表进行排名,但是您可以添加任何属性列表。请注意类似unlambda的内容,尽管会使用lambda来调用setter。等级部分不适用于列表数组,但排序可以。

#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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.