web-dev-qa-db-fra.com

Comment trier une liste d'objets en fonction d'un attribut des objets?

J'ai une liste d'objets Python que je souhaiterais trier en fonction d'un attribut des objets eux-mêmes. La liste ressemble à:

>>> ut
[<Tag: 128>, <Tag: 2008>, <Tag: <>, <Tag: actionscript>, <Tag: addresses>,
 <Tag: aes>, <Tag: ajax> ...]

Chaque objet a un compte:

>>> ut[1].count
1L

Je dois trier la liste par nombre de comptes décroissant.

J'ai vu plusieurs méthodes pour cela, mais je recherche les meilleures pratiques en Python.

685
Nick Sergeant
# To sort the list in place...
ut.sort(key=lambda x: x.count, reverse=True)

# To return a new list, use the sorted() built-in function...
newlist = sorted(ut, key=lambda x: x.count, reverse=True)

Plus sur tri par clé "

1133
Triptych

Une façon qui peut être la plus rapide, en particulier si votre liste contient de nombreux enregistrements, consiste à utiliser operator.attrgetter("count"). Cependant, cela pourrait fonctionner sur une version pré-opérateur de Python, il serait donc agréable d'avoir un mécanisme de secours. Vous voudrez peut-être faire ce qui suit, alors:

try: import operator
except ImportError: keyfun= lambda x: x.count # use a lambda if no operator module
else: keyfun= operator.attrgetter("count") # use operator since it's faster than lambda

ut.sort(key=keyfun, reverse=True) # sort in-place
75
tzot

Les lecteurs devraient remarquer que la méthode clé =:

ut.sort(key=lambda x: x.count, reverse=True)

est plusieurs fois plus rapide que l'ajout d'opérateurs de comparaison riches aux objets. J'ai été surpris de lire ceci (page 485 de "Python in a Nutshell"). Vous pouvez le confirmer en lançant des tests sur ce petit programme:

#!/usr/bin/env python
import random

class C:
    def __init__(self,count):
        self.count = count

    def __cmp__(self,other):
        return cmp(self.count,other.count)

longList = [C(random.random()) for i in xrange(1000000)] #about 6.1 secs
longList2 = longList[:]

longList.sort() #about 52 - 6.1 = 46 secs
longList2.sort(key = lambda c: c.count) #about 9 - 6.1 = 3 secs

Mes tests, très minimes, montrent que le premier tri est plus de 10 fois plus lent, mais le livre dit que ce n’est que 5 fois plus lent en général. La raison qu'ils disent est due à l'algorithme de tri hautement optimisé utilisé dans python (timsort).

Pourtant, il est très étrange que .sort (lambda) soit plus rapide que plain old .sort (). J'espère qu'ils règlent ça.

59
Jose M Vidal
from operator import attrgetter
ut.sort(key = attrgetter('count'), reverse = True)
34
attrgetter

approche orientée objet

Il est recommandé de faire de la logique de tri d'objet, le cas échéant, une propriété de la classe plutôt que d'incorporer à chaque instance le classement est obligatoire.

Cela garantit la cohérence et élimine le besoin de code passe-partout.

Au minimum, vous devez spécifier les opérations __eq__ et __lt__ pour que cela fonctionne. Ensuite, utilisez simplement sorted(list_of_objects).

class Card(object):

    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit

    def __eq__(self, other):
        return self.rank == other.rank and self.suit == other.suit

    def __lt__(self, other):
        return self.rank < other.rank

hand = [Card(10, 'H'), Card(2, 'h'), Card(12, 'h'), Card(13, 'h'), Card(14, 'h')]
hand_order = [c.rank for c in hand]  # [10, 2, 12, 13, 14]

hand_sorted = sorted(hand)
hand_sorted_order = [c.rank for c in hand_sorted]  # [2, 10, 12, 13, 14]
31
jpp

Cela ressemble beaucoup à une liste de Django instances de modèle ORM.

Pourquoi ne pas les trier sur la requête comme ceci:

ut = Tag.objects.order_by('-count')
15
muhuk

Ajoutez des opérateurs de comparaison riches à la classe d’objets, puis utilisez la méthode sort () de la liste.
Voir comparaison riche en python .


Mise à jour : Bien que cette méthode fonctionne, je pense que la solution de Triptych est mieux adaptée à votre cas car beaucoup plus simple.

10
rob