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

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

我有一个清单清单:

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

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

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

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

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


回答 0

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

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

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

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

And notice that here you can use sort instead of using sorted and then reassigning:

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

回答 1

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

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)

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

回答 2

看来您可以使用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中的某些操作进行排序,而只需给您一个列表,而不是将它们耦合到您的后端实现。

It appears you could use a list instead of a tuple. This becomes more important I think when you are grabbing attributes instead of ‘magic indexes’ of a list/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.attrA and then object.attrB assuming object has a getter corresponding to the string names provided. And the second case would sort by object.attrC then 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.


回答 3

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

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

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

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

回答 4

这是一种方法:您基本上是重写您的排序函数以获取一个排序函数列表,每个排序函数都会比较您要测试的属性,在每次排序测试中,您都会查看并查看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

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.

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.

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.

#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