Sortir daftar berdasarkan beberapa atribut?

457

Saya punya daftar daftar:

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

Jika saya ingin mengurutkan berdasarkan satu elemen, katakan elemen tinggi / pendek, saya bisa melakukannya melalui s = sorted(s, key = itemgetter(1)).

Jika saya ingin mengurutkan berdasarkan kedua tinggi / pendek dan warna, aku bisa melakukan semacam dua kali, sekali untuk setiap elemen, tetapi ada cara yang lebih cepat?

sakit kepala
sumber
8
Jika Anda menggunakan tuple sebagai ganti daftar, python memerintahkan pengurutan dengan entri dari kiri ke kanan saat Anda menjalankan sort. Yaitu sorted([(4, 2), (0, 3), (0, 1)]) == [(0, 1), (0, 3), (4, 2)],.
Mateen Ulhaq

Jawaban:

772

Kunci dapat berupa fungsi yang mengembalikan tuple:

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

Atau Anda dapat mencapai penggunaan yang sama itemgetter(yang lebih cepat dan menghindari panggilan fungsi Python):

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

Dan perhatikan bahwa di sini Anda dapat menggunakan sortalih-alih menggunakan sorteddan kemudian menetapkan kembali:

s.sort(key = operator.itemgetter(1, 2))
Mark Byers
sumber
20
Untuk kelengkapan dari timeit: bagi saya pertama memberi 6 us per loop dan yang kedua 4.4 us per loop
Brian Larsen
10
Apakah ada cara untuk mengurutkan yang pertama naik dan yang kedua turun? (Asumsikan kedua atribut adalah string, jadi tidak ada peretasan yang suka menambahkan -bilangan bulat)
Martin Thoma
73
bagaimana jika saya ingin menerapkan revrse=Truehanya untuk x[1]itu mungkin?
Amyth
28
@moose, @Athth, untuk mundur ke hanya satu atribut, Anda dapat mengurutkan dua kali: pertama oleh sekunder s = sorted(s, key = operator.itemgetter(2))kemudian oleh primer s = sorted(s, key = operator.itemgetter(1), reverse=True)Tidak ideal, tetapi berfungsi.
tomcounsell
52
@ Mitos atau opsi lain, jika kuncinya adalah angka, untuk membuatnya terbalik, Anda dapat mengalikannya dengan -1.
Serge
37

Saya tidak yakin apakah ini adalah metode yang paling pythonic ... Saya punya daftar tuple yang perlu disortir 1 dengan menurunkan nilai integer dan 2 menurut abjad. Ini diperlukan untuk membalikkan bilangan bulat bilangan tetapi bukan jenis abjad. Inilah solusi saya: (dengan cepat dalam ujian btw, saya bahkan tidak sadar Anda bisa 'menyortir' fungsi yang diurutkan)

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)]
Clint Blatchford
sumber
13
karena ke-2 adalah angka, ia berfungsi untuk melakukannya seperti b = sorted(a, key = lambda x: (-x[1], x[0]))mana yang lebih terlihat pada kriteria mana yang lebih dulu. Adapun efisiensi saya tidak yakin, seseorang perlu mengatur waktu.
Andrei-Niculae Petre
5

Beberapa tahun terlambat ke pesta tapi saya ingin kedua semacam atas 2 kriteria dan penggunaan reverse=True. Jika ada orang lain yang ingin tahu caranya, Anda bisa membungkus kriteria Anda (fungsi) dalam tanda kurung:

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

Tampaknya Anda dapat menggunakan listbukan tuple. Ini menjadi lebih penting saya pikir ketika Anda mengambil atribut daripada 'indeks ajaib' dari daftar / tuple.

Dalam kasus saya, saya ingin mengurutkan berdasarkan beberapa atribut kelas, di mana kunci yang masuk adalah string. Saya membutuhkan pengurutan yang berbeda di tempat yang berbeda, dan saya ingin pengurutan standar umum untuk kelas induk yang berinteraksi dengan klien; hanya harus menimpa 'kunci penyortiran' ketika saya benar-benar 'perlu', tetapi juga dengan cara yang saya bisa menyimpannya sebagai daftar yang dapat dibagi kelas

Jadi pertama-tama saya mendefinisikan metode pembantu

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 ]

lalu menggunakannya

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

Ini akan menggunakan fungsi lambda yang dihasilkan mengurutkan daftar dengan object.attrAdan object.attrBdengan asumsi objectmemiliki pengambil sesuai dengan nama string yang disediakan. Dan kasus kedua akan beres pada object.attrCsaat itu object.attrA.

Ini juga memungkinkan Anda untuk mengekspos pilihan penyortiran luar untuk dibagikan sama oleh konsumen, tes unit, atau bagi mereka untuk memberi tahu Anda bagaimana mereka ingin penyortiran dilakukan untuk beberapa operasi di api Anda dengan hanya perlu memberi Anda daftar dan tidak menggabungkan mereka ke implementasi back end Anda.

UpAndAdam
sumber
Kerja bagus. Bagaimana jika atribut harus diurutkan dalam pesanan yang berbeda? Misalkan attrA harus diurutkan naik dan attrB turun? Apakah ada solusi cepat di atas ini? Terima kasih!
mhn_namak
1

Berikut ini salah satu caranya: Anda pada dasarnya menulis ulang fungsi sortir Anda untuk mengambil daftar fungsi sortir, setiap fungsi sortir membandingkan atribut yang ingin Anda uji, pada setiap tes sortir, Anda melihat dan melihat apakah fungsi cmp mengembalikan pengembalian yang bukan nol jika demikian pecahkan dan kirim nilai pengembalian. Anda menyebutnya dengan memanggil Lambda dari fungsi daftar Lambdas.

Keuntungannya adalah bahwa ia tidak melewati data bukan jenis yang sebelumnya seperti metode lainnya. Hal lain adalah bahwa itu di tempat, sedangkan diurutkan tampaknya membuat salinan.

Saya menggunakannya untuk menulis fungsi peringkat, yang memberi peringkat daftar kelas di mana setiap objek dalam sebuah kelompok dan memiliki fungsi skor, tetapi Anda dapat menambahkan daftar atribut apa pun. Perhatikan un-lambda-like, meskipun menggunakan lambda untuk memanggil setter. Bagian peringkat tidak akan berfungsi untuk array daftar, tetapi pengurutannya akan.

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

Berikut adalah cara untuk memberi peringkat daftar objek

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
Dominic Suciu
sumber