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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4233476/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 14:52:35  来源:igfitidea点击:

Sort a list by multiple attributes?

pythonsorting

提问by headache

I have a list of lists:

我有一个列表列表:

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

If I wanted to sort by one element, say the tall/short element, I could do it via s = sorted(s, key = itemgetter(1)).

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

If I wanted to sort by bothtall/short and colour, I could do the sort twice, once for each element, but is there a quicker way?

如果我想作为排序依据两个高大/ short和颜色,我可以为每个元素做排序两次,一次,但有一个更快的方法?

采纳答案by Mark Byers

A key can be a function that returns a tuple:

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

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

Or you can achieve the same using itemgetter(which is faster and avoids a Python function call):

或者您可以使用itemgetter(更快并避免 Python 函数调用)来实现相同的效果:

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

And notice that here you can use sortinstead of using sortedand then reassigning:

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

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

回答by Clint Blatchford

I'm not sure if this is the most pythonic method ... I had a list of tuples that needed sorting 1st by descending integer values and 2nd alphabetically. This required reversing the integer sort but not the alphabetical sort. Here was my solution: (on the fly in an exam btw, I was not even aware you could 'nest' sorted functions)

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

回答by UpAndAdam

It appears you could use a listinstead of a tuple. This becomes more important I think when you are grabbing attributes instead of 'magic indexes' of a list/tuple.

看来您可以使用 alist代替 a tuple。我认为当您获取属性而不是列表/元组的“魔术索引”时,这变得更加重要。

In my case I wanted to sort by multiple attributes of a class, where the incoming keys were strings. I needed different sorting in different places, and I wanted a common default sort for the parent class that clients were interacting with; only having to override the 'sorting keys' when I really 'needed to', but also in a way that I could store them as lists that the class could share

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

So first I defined a helper method

所以首先我定义了一个辅助方法

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 ]

then to use it

然后使用它

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

This will use the generated lambda function sort the list by object.attrAand then object.attrBassuming objecthas a getter corresponding to the string names provided. And the second case would sort by object.attrCthen object.attrA.

这将使用生成的 lambda 函数对列表进行排序object.attrA,然后object.attrB假设object有一个对应于提供的字符串名称的 getter。第二种情况将按object.attrCthen排序object.attrA

This also allows you to potentially expose outward sorting choices to be shared alike by a consumer, a unit test, or for them to perhaps tell you how they want sorting done for some operation in your api by only have to give you a list and not coupling them to your back end implementation.

这也允许您潜在地公开向外排序选项以供消费者、单元测试共享,或者让他们可能告诉您他们希望如何对您的 api 中的某些操作进行排序,只需要给您一个列表而不是将它们耦合到您的后端实现。

回答by Dominic Suciu

Here's one way: You basically re-write your sort function to take a list of sort functions, each sort function compares the attributes you want to test, on each sort test, you look and see if the cmp function returns a non-zero return if so break and send the return value. You call it by calling a Lambda of a function of a list of Lambdas.

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

Its advantage is that it does single pass through the data not a sort of a previous sort as other methods do. Another thing is that it sorts in place, whereas sorted seems to make a copy.

它的优点是它对数据进行单次传递,而不是像其他方法那样进行一种先前的排序。另一件事是它就地排序,而 sorted 似乎是复制。

I used it to write a rank function, that ranks a list of classes where each object is in a group and has a score function, but you can add any list of attributes. Note the un-lambda-like, though hackish use of a lambda to call a setter. The rank part won't work for an array of lists, but the sort will.

我用它来编写一个排名函数,它对一个类列表进行排名,其中每个对象都在一个组中并具有一个评分函数,但您可以添加任何属性列表。注意使用 lambda 来调用 setter 的非 lambda-like,虽然是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)

Here's a way to rank a list of objects

这是一种对对象列表进行排名的方法

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 donrondadon

Several years late to the party but I want to bothsort on 2 criteria anduse reverse=True. In case someone else wants to know how, you can wrap your criteria (functions) in parenthesis:

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

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