Sortowanie listy Pythona według dwóch pól

172

Mam następującą listę utworzoną z posortowanego pliku csv

list1 = sorted(csv1, key=operator.itemgetter(1))

Chciałbym właściwie posortować listę według dwóch kryteriów: najpierw według wartości w polu 1, a następnie według wartości w polu 2. Jak to zrobić?

w połowie pełna
źródło
3
Możliwy duplikat sortowania listy według wielu atrybutów?
Chris_Rands
Czy zostawimy to pytanie i ograniczymy jego zakres do "listy-list-o-długości-dwa-wbudowane-typy (np. String / int / float)" . Czy też zezwalamy na „listę obiektów zdefiniowanych przez użytkownika” , jak sugeruje tytuł, w takim przypadku odpowiedź brzmi: „Zdefiniuj __lt__()metodę w swojej klasie lub dziedzicz z jakiejś klasy, która to robi” ? To uczyniłoby go znacznie lepszym kanonicznym.
smci

Odpowiedzi:

157

lubię to:

import operator
list1 = sorted(csv1, key=operator.itemgetter(1, 2))
mouad
źródło
1
+1: Bardziej eleganckie niż moje. Zapomniałem, że itemgetter może przyjmować wiele indeksów.
dappawit
7
operatorto moduł, który należy zaimportować.
trapicki 28.08.13
3
Jak postąpię, jeśli chcę posortować rosnąco na jednym elemencie i malejąco na drugim, używając itemgetter ??.
ashish
3
@ashish, zobacz moją odpowiedź poniżej z funkcjami lambda, to jest jasne, sortuj według „-x [1]” lub nawet „x [0] + x [1]”, jeśli chcesz
jaap
a co jeśli jedno kryterium w trybie odwróconym?
YaserKH
328

Nie ma potrzeby importowania niczego podczas korzystania z funkcji lambda.
Poniższe sortuje listwedług pierwszego elementu, a następnie według drugiego elementu.

sorted(list, key=lambda x: (x[0], -x[1]))
jaap
źródło
12
Miły. Jak zauważyłeś w komentarzu do głównej odpowiedzi powyżej, jest to najlepszy (jedyny?) Sposób sortowania z różnymi porządkami sortowania. Może to podkreśl. Twój tekst nie wskazuje również, że posortowałeś malejąco według drugiego elementu.
PeterVermont
2
@ user1700890 Zakładałem, że pole jest już ciągiem. Powinien domyślnie sortować ciągi w kolejności alfabetycznej. Powinieneś zamieścić własne pytanie osobno na SO, jeśli nie jest ono konkretnie związane z odpowiedzią tutaj lub oryginalnym pytaniem PO.
biblia
5
co robi -w -x[1]skrót?
styczeń
7
@jan It's reverse sort
jaap
3
Nie zadziała w jednym konkretnym przypadku. Przyjęte rozwiązanie też się nie sprawdzi. Na przykład wszystkie kolumny używane jako klucze to ciągi znaków, których nie można przekształcić w liczby. Po drugie, chce się sortować rosnąco według jednej kolumny i malejąco według innej kolumny.
coder.in.me
20

Python ma stabilne sortowanie, więc jeśli wydajność nie jest problemem, najprostszym sposobem jest posortowanie go według pola 2, a następnie sortowanie ponownie według pola 1.

To da ci pożądany wynik, jedyny haczyk polega na tym, że jeśli jest to duża lista (lub chcesz ją często sortować) dwukrotne wywołanie sort może być niedopuszczalnym narzutem.

list1 = sorted(csv1, key=operator.itemgetter(2))
list1 = sorted(list1, key=operator.itemgetter(1))

Zrobienie tego w ten sposób ułatwia również obsługę sytuacji, w której chcesz, aby niektóre kolumny były sortowane odwrotnie, po prostu dołącz parametr „reverse = True”, gdy jest to konieczne.

W przeciwnym razie możesz przekazać wiele parametrów do itemgetter lub ręcznie utworzyć krotkę. Prawdopodobnie będzie to szybsze, ale problem polega na tym, że nie uogólnia się dobrze, jeśli niektóre kolumny chcą być sortowane odwrotnie (kolumny numeryczne nadal można odwrócić, negując je, ale to zatrzymuje sortowanie).

Więc jeśli nie potrzebujesz żadnych kolumn posortowanych odwrotnie, przejdź do wielu argumentów do itemgetter, jeśli możesz, a kolumny nie są numeryczne lub chcesz, aby sortowanie było stabilne dla wielu kolejnych sortowań.

Edycja: dla komentujących, którzy mają problemy ze zrozumieniem, w jaki sposób odpowiada to pierwotnemu pytaniu, oto przykład, który dokładnie pokazuje, w jaki sposób stabilny charakter sortowania zapewnia, że ​​możemy wykonać oddzielne sortowanie dla każdego klucza i skończyć z danymi posortowanymi według wielu kryteriów:

DATA = [
    ('Jones', 'Jane', 58),
    ('Smith', 'Anne', 30),
    ('Jones', 'Fred', 30),
    ('Smith', 'John', 60),
    ('Smith', 'Fred', 30),
    ('Jones', 'Anne', 30),
    ('Smith', 'Jane', 58),
    ('Smith', 'Twin2', 3),
    ('Jones', 'John', 60),
    ('Smith', 'Twin1', 3),
    ('Jones', 'Twin1', 3),
    ('Jones', 'Twin2', 3)
]

# Sort by Surname, Age DESCENDING, Firstname
print("Initial data in random order")
for d in DATA:
    print("{:10s} {:10s} {}".format(*d))

print('''
First we sort by first name, after this pass all
Twin1 come before Twin2 and Anne comes before Fred''')
DATA.sort(key=lambda row: row[1])

for d in DATA:
    print("{:10s} {:10s} {}".format(*d))

print('''
Second pass: sort by age in descending order.
Note that after this pass rows are sorted by age but
Twin1/Twin2 and Anne/Fred pairs are still in correct
firstname order.''')
DATA.sort(key=lambda row: row[2], reverse=True)
for d in DATA:
    print("{:10s} {:10s} {}".format(*d))

print('''
Final pass sorts the Jones from the Smiths.
Within each family members are sorted by age but equal
age members are sorted by first name.
''')
DATA.sort(key=lambda row: row[0])
for d in DATA:
    print("{:10s} {:10s} {}".format(*d))

To jest przykład, który można uruchomić, ale aby uratować ludzi, którzy go uruchamiają, otrzymujemy:

Initial data in random order
Jones      Jane       58
Smith      Anne       30
Jones      Fred       30
Smith      John       60
Smith      Fred       30
Jones      Anne       30
Smith      Jane       58
Smith      Twin2      3
Jones      John       60
Smith      Twin1      3
Jones      Twin1      3
Jones      Twin2      3

First we sort by first name, after this pass all
Twin1 come before Twin2 and Anne comes before Fred
Smith      Anne       30
Jones      Anne       30
Jones      Fred       30
Smith      Fred       30
Jones      Jane       58
Smith      Jane       58
Smith      John       60
Jones      John       60
Smith      Twin1      3
Jones      Twin1      3
Smith      Twin2      3
Jones      Twin2      3

Second pass: sort by age in descending order.
Note that after this pass rows are sorted by age but
Twin1/Twin2 and Anne/Fred pairs are still in correct
firstname order.
Smith      John       60
Jones      John       60
Jones      Jane       58
Smith      Jane       58
Smith      Anne       30
Jones      Anne       30
Jones      Fred       30
Smith      Fred       30
Smith      Twin1      3
Jones      Twin1      3
Smith      Twin2      3
Jones      Twin2      3

Final pass sorts the Jones from the Smiths.
Within each family members are sorted by age but equal
age members are sorted by first name.

Jones      John       60
Jones      Jane       58
Jones      Anne       30
Jones      Fred       30
Jones      Twin1      3
Jones      Twin2      3
Smith      John       60
Smith      Jane       58
Smith      Anne       30
Smith      Fred       30
Smith      Twin1      3
Smith      Twin2      3

Zwróć uwagę w szczególności, jak w drugim kroku reverse=Trueparametr utrzymuje pierwsze nazwiska w kolejności, podczas gdy zwykłe sortowanie, a następnie odwracanie listy spowoduje utratę żądanej kolejności dla trzeciego klucza sortowania.

Duncan
źródło
1
Stabilne sortowanie nie oznacza, że ​​nie zapomni, jakie było poprzednie sortowanie. Ta odpowiedź jest błędna.
Mike Axiak
7
Stabilne sortowanie oznacza, że ​​możesz sortować według kolumn a, b, c po prostu sortując według kolumny c, następnie b, a następnie a. Jeśli nie chcesz rozwinąć swojego komentarza, myślę, że to ty się mylisz.
Duncan
7
Ta odpowiedź jest zdecydowanie poprawna, chociaż w przypadku większych list jest nieidealna: jeśli lista była już częściowo posortowana, stracisz większość optymalizacji sortowania w Pythonie, przesuwając listę wokół dużo więcej. @Mike, mylisz się; Sugeruję przetestowanie odpowiedzi przed uznaniem ich za błędne.
Glenn Maynard
6
@MikeAxiak: docs.python.org/2/library/stdtypes.html#index-29 stwierdza w komentarzu 9: Począwszy od Pythona 2.3, metoda sort () jest stabilna. Sortowanie jest stabilne, jeśli gwarantuje, że nie zmieni względnej kolejności elementów, które porównują się równo - jest to pomocne przy sortowaniu w wielu zdaniach (na przykład sortowanie według działu, a następnie według poziomu wynagrodzenia).
trapicki 28.08.13
To nie jest poprawne, ponieważ nie odpowiada na zadane przez niego pytanie. chce listy posortowanej według pierwszego indeksu, aw przypadku, gdy w pierwszym indeksie są powiązania, chce użyć drugiego indeksu jako kryterium sortowania. Stabilne sortowanie gwarantuje tylko, że wszystkie rzeczy są równe, a pierwotnie przekazana kolejność będzie kolejnością, w jakiej pojawiają się elementy.
Jon
14
list1 = sorted(csv1, key=lambda x: (x[1], x[2]) )
dappawit
źródło
4
Myślę, że nie tuple()mogę otrzymać dwóch argumentów (a raczej trzech, jeśli liczyć self)
Filipe Correia
3
krotka może przyjąć tylko jeden argument
aktualny
1
returnpowinno być return tuple((x[1], x[2]))lub po prostu return x[1], x[2]. Skorzystaj z odpowiedzi @jaap poniżej, jeśli szukasz sortowania w różnych kierunkach
Jo Kachikaran,
… Lub tuple(x[1:3]), jeśli z jakiegoś powodu chcesz użyć konstruktora krotki zamiast tylko listy wyświetlania krotki x[1], x[2]. Lub keyfunc = operator.itemgetter(1, 2)i nawet nie pisz funkcji samodzielnie.
abarnert
3
employees.sort(key = lambda x:x[1])
employees.sort(key = lambda x:x[0])

Możemy również użyć .sort z lambda 2 razy, ponieważ sortowanie w Pythonie jest na miejscu i stabilne. Spowoduje to najpierw posortowanie listy według drugiego elementu, x [1]. Następnie posortuje pierwszy element, x [0] (najwyższy priorytet).

employees[0] = Employee's Name
employees[1] = Employee's Salary

Jest to równoważne wykonaniu następujących czynności: workers.sort (key = lambda x: (x [0], x [1]))

Deepak Yadav
źródło
1
nie, ta reguła sortowania musi mieć pierwszeństwo przed drugą.
CodeFarmer
1

W porządku rosnącym możesz użyć:

sorted_data= sorted(non_sorted_data, key=lambda k: (k[1],k[0]))

lub malejąco możesz użyć:

sorted_data= sorted(non_sorted_data, key=lambda k: (k[1],k[0]),reverse=True)
Majid Arasteh
źródło
0

Sortowanie listy dykt za pomocą poniższego posortuje listę w porządku malejącym w pierwszej kolumnie jako wynagrodzenie, a w drugiej jako wiek

d=[{'salary':123,'age':23},{'salary':123,'age':25}]
d=sorted(d, key=lambda i: (i['salary'], i['age']),reverse=True)

Wynik: [{'salary': 123, 'age': 25}, {'salary': 123, 'age': 23}]

Saurabh
źródło