リストを複数の属性でソートしますか?


456

リストのリストがあります:

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

1つの要素(tall / short要素など)で並べ替える場合は、を使用してそれを行うことができますs = sorted(s, key = itemgetter(1))

背の高い/短いと色の両方で並べ替える場合は、要素ごとに1回ずつ、2回並べ替えることができますが、もっと速い方法はありますか?



8
リストの代わりにタプルを使用する場合、Pythonはを実行すると、エントリを左から右に並べ替えますsort。それは、ありますsorted([(4, 2), (0, 3), (0, 1)]) == [(0, 1), (0, 3), (4, 2)]
Mateen Ulhaq 2018年

回答:


771

キーはタプルを返す関数にすることができます:

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
timeitからの完全性のために:私にとって、最初はループごとに6 us、2番目はループごとに4.4 us
Brian Larsen

10
最初のものを昇順でソートし、2番目のものを降順でソートする方法はありますか?(両方の属性が文字列であるため-、整数を追加するようなハックはありません)
Martin Thoma

73
応募したいrevrse=Trueだけならx[1]可能ですか?
Amyth 2014年

28
@moose、@Amythは、あなたがソート二回、1つの属性のみにすることができます逆に:第一の二次によってs = sorted(s, key = operator.itemgetter(2))一次でs = sorted(s, key = operator.itemgetter(1), reverse=True)はないのに理想的な、しかし作品。
tomcounsell 2015

52
@Amythまたは別のオプションで、キーが数値の場合、それを逆にするために、で乗算できます-1
Serge

37

これが最もpythonicメソッドであるかどうかはわかりません...整数値を降順で1番目と2番目をアルファベット順にソートする必要があるタプルのリストがありました。これには、整数の並べ替えを逆にする必要がありますが、アルファベット順の並べ替えは必要ありません。ここに私の解決策があります:(その間、その場で試験で、ソートされた関数を「ネスト」できることに気づきませんでした)

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

13
2ndは数値なので、b = sorted(a, key = lambda x: (-x[1], x[0]))どの基準が最初に適用されるかによりわかりやすいように機能します。効率についてはわかりませんが、誰かが時間を計る必要があります。
Andrei-Niculae Petre

5

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

これは、生成されたラムダ関数を使用してリストをソートし、指定された文字列名に対応するゲッターがあるobject.attrAobject.attrB想定しobjectます。第2の場合は、ソートなりobject.attrC、その後object.attrA

これにより、コンシューマーやユニットテストで同様に共有される外部ソートの選択肢を公開したり、リストを提供するだけでなく、APIの一部の操作でソートを実行する方法を通知したりすることもできますそれらをバックエンド実装に結合します。


よくやった。属性を異なる順序でソートする必要がある場合はどうなりますか?attrAを昇順で、attrBを降順でソートするとしますか?これの上に簡単な解決策はありますか?ありがとう!
mhn_namak

4

数年遅れて相手に私はしたい、両方の 2つの基準でソートして使用reverse=True。他の誰かが方法を知りたい場合は、条件(関数)を括弧で囲むことができます。

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

1

1つの方法は次のとおりです。基本的に、並べ替え関数のリストを取得するように並べ替え関数を書き換えます。各並べ替え関数は、テストする属性を比較し、各並べ替えテストで、cmp関数がゼロ以外の戻り値を返すかどうかを確認しますもしそうなら、ブレークして戻り値を送信します。ラムダのリストの関数のラムダを呼び出すことによってそれを呼び出します。

その利点は、他のメソッドのように以前の並べ替えではなく、データを1回通過することです。もう1つは、並べ替えが適切に行われるのに対し、並べ替えはコピーを作成するように見えることです。

これを使用して、各オブジェクトがグループ内にあり、スコア関数があるクラスのリストをランク付けするランク関数を記述しましたが、属性のリストを追加できます。セッターを呼び出すためのラムダのハックな使用にもかかわらず、非ラムダのようなことに注意してください。ランク部分はリストの配列に対しては機能しませんが、ソートは機能します。

#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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.