Tri de liste Python personnalisé

93

J'étais en train de refactoriser un de mes vieux codes et je suis tombé sur ceci:

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

Le code fonctionne (et je l'ai écrit il y a environ 3 ans!) Mais je ne trouve pas cette chose documentée nulle part dans la documentation Python et tout le monde l'utilise sorted()pour implémenter le tri personnalisé. Quelqu'un peut-il expliquer pourquoi cela fonctionne?

Lorenzo
la source
sorted()et sort()offre un tri personnalisé à peu près de la même manière, modulo la différence de convention d'appel.
Russell Borogove
2
En effet, ce qui se passe, c'est que l'utilisation d'un keyparamètre est préférable au passage d'une cmpfonction. (Ce dernier n'est même pas implémenté dans Python 3)
jsbueno
C'est un peu ambigu, cela dépend de ce qu'étaient les éléments de la liste; votre code exige qu'ils aient un attribut foo, sinon il explose. Mieux vaut définir une __lt__()méthode personnalisée pour votre classe, alors sorted()et list.sort()fonctionnera immédiatement. (Btw, les objets n'ont plus besoin de définir __cmp__(), juste __lt__(). Voir ceci
smci

Réponses:

60

C'est documenté ici .

La méthode sort () prend des arguments optionnels pour contrôler les comparaisons.

cmp spécifie une fonction de comparaison personnalisée de deux arguments (éléments de liste) qui doit renvoyer un nombre négatif, zéro ou positif selon que le premier argument est considéré comme plus petit, égal ou supérieur au second argument: cmp = lambda x, y : cmp (x.lower (), y.lower ()). La valeur par défaut est Aucun.

miles82
la source
Merci miles82 Je vérifiais ici et je ne pouvais pas le voir dans la signature de la méthode docs.python.org/tutorial/datastructures.html
Lorenzo
Je ne vois pas le même texte sur la page à laquelle vous avez lié. La documentation a-t-elle changé. D'ailleurs, quand j'essaye d'utiliser cmp, j'obtiens TypeError: 'cmp' is an invalid keyword argument for this function. Qu'est-ce qui se passe ici?
HelloGoodbye
1
@HelloGoodbye sort () n'a pas d'argument cmp en Python 3. Il s'agit d'une ancienne réponse lorsque le lien vers la documentation était pour Python 2. Vous pouvez trouver les anciens documents ici ou en savoir plus ici . Si vous utilisez Python 3, utilisez plutôt l' argument clé .
miles82
Et si vous souhaitez réellement fournir une fonction de comparaison? Je veux traiter les nombres dans une chaîne (de n'importe quelle longueur, choisie avec gourmandise) comme des symboles, de manière équivalente à la façon dont les caractères individuels sont autrement traités. Je sais comment y parvenir de manière triviale si je peux fournir une fonction de comparaison, mais pas si je dois fournir une fonction clé. Pourquoi cela a-t-il été changé?
HelloGoodbye
Je suppose que cela peut toujours être réalisé si chaque numéro contenu dans la chaîne est codé à l'aide d'un codage qui ordonne les nombres lexicographiquement, tel que le codage Levenshtein . Mais je considère cela plus comme une solution de contournement au fait que sortne prend pas une fonction de comparaison comme argument dans Python 3, et non comme quelque chose que j'aimerais vraiment faire.
HelloGoodbye
104

En remarque, voici une meilleure alternative pour implémenter le même tri:

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

Ou bien:

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

Consultez le Guide de tri , il est très utile.

Andrew Clark
la source
1
TIL sur l'opérateur, très utile.
ffledgling
13

Tout comme cet exemple. Vous voulez trier cette liste.

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

production:

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

vous devez trier les tuples par le deuxième élément, puis le premier:

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

Finalement:

juste sort(letter_cmp)

RryLee
la source
4
Comment sait-il quelle liste trier?
Cameron Monks
2
@CameronMonks yourList.sort (letter_cmp)
Minyc510
7

Cela ne fonctionne pas dans Python 3.

Vous pouvez utiliser les functools cmp_to_key pour faire fonctionner les fonctions de comparaison à l'ancienne.

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)
Le chat impertinent
la source
1

En python3, vous pouvez écrire:

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

alist = sorted(alist, key=cmp_to_key(cmp_items))
mece1390
la source
0

Encore mieux:

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

Tiré de: https://docs.python.org/3/howto/sorting.html

Steven
la source