web-dev-qa-db-ja.com

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

私はリストのリストを持っています:

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

1つの要素でソートしたい場合、例えば高さの短い要素を言うと、s = sorted(s, key = itemgetter(1))を使ってそれを行うことができます。

もし両方の縦/横と色でソートしたいのであれば、ソートを要素ごとに1回、2回行うことができますが、もっと速い方法はありますか?

346
headache

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

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))
593
Mark Byers

これが最もPythonicの方法であるかどうか私にはわかりません...私は整数値を降順にして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)]
29

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.attrAでソートし、objectが与えられた文字列名に対応するゲッターを持っていると仮定してobject.attrBを使います。そして2番目のケースはobject.attrC、次にobject.attrAでソートします。

これはまた、あなたが潜在的に消費者、単体テスト、またはそれらがおそらくあなたにリストを与えるだけであなたのapiのいくつかの操作のためにソートがどのように行われるかそれらをあなたのバックエンドの実装に結びつけます。

3
UpAndAdam

1つの方法は次のとおりです。基本的に並べ替え関数を並べ替えて並べ替え関数のリストを作成する。各並べ替え関数はテストする属性を比較します。もしそうなら中断して戻り値を送る。あなたはラムダのリストの関数のラムダを呼び出すことによってそれを呼び出します。

その利点は、他の方法のように以前の種類のデータではなく、データを1回通過できることです。もう1つのことは、ソートされているとコピーが作成されているように見えるのに対し、ソートされているとソートされていることです。

私はこれを使ってランク付け関数を書いていました。ランク付け関数は、各オブジェクトがグループに属し、スコア関数を持つクラスのリストをランク付けするものですが、属性のリストを追加することもできます。セッターを呼び出すためのラムダのハックな使用方法ですが、ラムダに似ていないことに注意してください。 rank部分はリストの配列に対しては機能しませんが、sortは機能します。

#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
1
Dominic Suciu

数年遅くパーティーに遅れますが、私はboth2つの基準でソートしたいreverse=Trueを使います。他の人がその方法を知りたい場合は、括弧で基準(関数)を囲むことができます。

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