Niestandardowe sortowanie list w Pythonie

98

Refaktoryzowałem stary kod i natknąłem się na to:

alist.sort(cmp_items)

def cmp_items(a, b):
    if a.foo > b.foo:
        return 1
    elif a.foo == b.foo:
        return 0
    else:
        return -1

Kod działa (i napisałem go około 3 lata temu!), Ale nie mogę znaleźć tego udokumentowanego nigdzie w dokumentacji Pythona i wszyscy używają go sorted()do implementacji niestandardowego sortowania. Czy ktoś może wyjaśnić, dlaczego to działa?

Lorenzo
źródło
sorted()i sort()oferują niestandardowe sortowanie w bardzo podobny sposób, modulo różnicę w konwencji wywoływania.
Russell Borogove
2
Rzeczywiście, dzieje się tak, że użycie keyparametru jest lepsze niż przekazywanie cmpfunkcji. (Ta późniejsza nie jest nawet zaimplementowana w Pythonie 3)
jsbueno
To trochę niejednoznaczne, zależy od tego, jakie były pozycje na liście; twój kod wymaga, aby miały atrybut foo, w przeciwnym razie wybuchnie. Lepiej zdefiniować niestandardową __lt__()metodę dla swojej klasy, to sorted()i list.sort()będzie działać out-of-the-box. (Przy okazji, obiekty nie muszą już definiować __cmp__(), po prostu __lt__(). Zobacz to
smci

Odpowiedzi:

60

Jest to udokumentowane tutaj .

Metoda sort () przyjmuje opcjonalne argumenty do kontrolowania porównań.

cmp określa niestandardową funkcję porównania dwóch argumentów (elementów listy), która powinna zwracać liczbę ujemną, zerową lub dodatnią w zależności od tego, czy pierwszy argument jest uważany za mniejszy, równy lub większy od drugiego argumentu: cmp = lambda x, y : cmp (x.lower (), y.lower ()). Wartość domyślna to Brak.

mile82
źródło
Dzięki mile82 Sprawdzałem tutaj i nie mogłem tego zobaczyć w sygnaturze metody docs.python.org/tutorial/datastructures.html
Lorenzo
Nie widzę tego samego tekstu na stronie, do której prowadziło łącze. Czy dokumentacja uległa zmianie? Poza tym, kiedy próbuję użyć cmp, dostaję TypeError: 'cmp' is an invalid keyword argument for this function. Co tu się dzieje?
HelloGoodbye,
2
@HelloGoodbye sort () nie ma argumentu cmp w Pythonie 3. To jest stara odpowiedź, gdy link do dokumentacji był przeznaczony dla Pythona 2. Możesz znaleźć stare dokumenty tutaj lub przeczytać więcej na ten temat tutaj . Jeśli używasz Pythona 3, użyj zamiast tego argumentu klucza .
mile 82
A co jeśli faktycznie chcesz udostępnić funkcję porównawczą? Chcę traktować liczby w ciągu (dowolnej długości, chciwie wybierane) jako symbole, równoważnie z tym, jak traktuje się poszczególne znaki w inny sposób. Wiem, jak to osiągnąć w trywialny sposób, jeśli mogę zapewnić funkcję porównawczą, ale nie, jeśli muszę zapewnić funkcję kluczową. Dlaczego to się zmieniło?
HelloGoodbye
Myślę, że nadal można to osiągnąć, jeśli każda liczba zawarta w ciągu jest zakodowana przy użyciu kodowania, które porządkuje liczby leksykograficznie, takiego jak kodowanie Levenshteina . Ale uważam to bardziej za obejście faktu, że sortfunkcja porównania nie przyjmuje funkcji porównawczej jako argumentu w Pythonie 3, a nie jako coś, co naprawdę chciałbym zrobić.
HelloGoodbye
108

Na marginesie, oto lepsza alternatywa dla zaimplementowania tego samego sortowania:

alist.sort(key=lambda x: x.foo)

Lub alternatywnie:

import operator
alist.sort(key=operator.attrgetter('foo'))

Sprawdź jak sortować , jest to bardzo przydatne.

Andrew Clark
źródło
1
TIL o operatorze, bardzo przydatne.
ffledgling
16

Tak jak w tym przykładzie. Chcesz posortować tę listę.

[('c', 2), ('b', 2), ('a', 3)]

wynik:

[('a', 3), ('b', 2), ('c', 2)]

powinieneś posortować krotki według drugiej pozycji, a następnie pierwszej:

def letter_cmp(a, b):
    if a[1] > b[1]:
        return -1
    elif a[1] == b[1]:
        if a[0] > b[0]:
            return 1
        else:
            return -1
    else:
        return 1

Następnie przekonwertuj go na kluczową funkcję:

from functools import cmp_to_key
letter_cmp_key = cmp_to_key(letter_cmp))

Teraz możesz użyć własnego porządku sortowania:

[('c', 2), ('b', 2), ('a', 3)].sort(key=letter_cmp_key)
RryLee
źródło
4
Skąd wie, jaką listę sortować?
Cameron Monks
2
@CameronMonks yourList.sort (letter_cmp)
kebab-case
7

To nie działa w Pythonie 3.

Możesz jednak użyć functools cmp_to_key, aby działały funkcje porównawcze w starym stylu.

from functools import cmp_to_key

def cmp_items(a, b):
    if a.foo > b.foo:
        return 1
    elif a.foo == b.foo:
        return 0
    else:
        return -1

cmp_items_py3 = cmp_to_key(cmp_items)

alist.sort(cmp_items_py3)
The Unfun Cat
źródło
1

Wiem, że wielu już opublikowało dobre odpowiedzi. Jednak chcę zasugerować jedną przyjemną i łatwą metodę bez importowania żadnej biblioteki.

l = [(2, 3), (3, 4), (2, 4)]
l.sort(key = lambda x: (-x[0], -x[1]) )
print(l)
l.sort(key = lambda x: (x[0], -x[1]) )
print(l)

Wyjście będzie

[(3, 4), (2, 4), (2, 3)]
[(2, 4), (2, 3), (3, 4)]

Dane wyjściowe zostaną posortowane na podstawie kolejności parametrów, które podaliśmy w formacie krotki

peddiashrith
źródło
0

Nawet lepiej:

student_tuples = [
    ('john', 'A', 15),
    ('jane', 'B', 12),
    ('dave', 'B', 10),
]

sorted(student_tuples, key=lambda student: student[2])   # sort by age
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

Zaczerpnięte z: https://docs.python.org/3/howto/sorting.html

Steven
źródło