【问题标题】:Sort a list by multiple attributes?按多个属性对列表进行排序?
【发布时间】:2011-05-13 02:52:15
【问题描述】:

我有一个列表列表:

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

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

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

【问题讨论】:

标签: python sorting


【解决方案1】:

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

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

【讨论】:

  • 为了从 timeit 开始的完整性:对我来说,第一次给每个循环 6 us,第二个给每个循环 4.4 us
  • 有没有办法让第一个升序,第二个降序? (假设两个属性都是字符串,所以没有像为整数添加 - 这样的技巧)
  • 如果我只想将revrse=True 应用于x[1] 可以吗?
  • @moose, @Amyth,要反转为仅一个属性,您可以排序两次:首先按次要 s = sorted(s, key = operator.itemgetter(2)) 然后按主要s = sorted(s, key = operator.itemgetter(1), reverse=True) 不理想,但有效。
  • @Amyth 或其他选项,如果键是数字,要使其反转,您可以将其与-1 相乘。
【解决方案2】:

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

【讨论】:

  • 因为 2nd 是一个数字,所以它可以像 b = sorted(a, key = lambda x: (-x[1], x[0])) 一样工作,这在哪个条件首先适用时更明显。至于效率我不确定,有人需要计时。
【解决方案3】:

看来您可以使用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 请参阅stackoverflow.com/a/55866810/2359945,这是对 n 条标准进行排序的好方法,每个标准都可以升序或降序。
  • 我们显然对美丽有着截然不同的看法。虽然它完成了我见过的最无聊的事情。效率成为 (n*m) 的函数,其中 m 是要排序的属性数,而不仅仅是列表长度的函数。我认为这里的其他答案有更好的解决方案,或者如果你真的需要这种行为,你可以编写自己的排序函数来自己做
【解决方案4】:

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

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

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

【讨论】:

    【解决方案5】:

    晚了几年,但我想同时按 2 个标准进行排序使用reverse=True。如果其他人想知道如何,您可以将您的条件(函数)括在括号中:

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

    【讨论】:

      【解决方案6】:

      列表之间有一个运算符

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

      会给

      False
      

      【讨论】:

        【解决方案7】:

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2021-08-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多