web-dev-qa-db-fra.com

Trier une liste par plusieurs attributs?

J'ai une liste de listes:

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

Si je voulais trier par un élément, disons le grand/court élément, je pourrais le faire via s = sorted(s, key = itemgetter(1)).

Si je voulais trier par les deux haut/court et par couleur, je pourrais faire le tri deux fois, une fois pour chaque élément, mais existe-t-il un moyen plus rapide?

373
headache

Une clé peut être une fonction qui retourne un tuple:

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

Vous pouvez également atteindre le même résultat avec itemgetter (qui est plus rapide et évite un appel de fonction Python:):

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

Et remarquez qu'ici vous pouvez utiliser sort au lieu d’utiliser sorted, puis de réaffecter:

s.sort(key = operator.itemgetter(1, 2))
626
Mark Byers

Je ne suis pas sûr qu'il s'agisse de la méthode la plus pythonique ... J'avais une liste de n-uplets devant être triés en premier par valeurs entières décroissantes et par ordre alphabétique en deuxième. Cela nécessitait d'inverser le tri de nombre entier mais pas le tri alphabétique. Voici ma solution: (à la volée lors d'un examen, je ne savais même pas que vous pouviez "imbriquer" des fonctions triées)

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)]
30
Clint Blatchford

Il semble que vous pourriez utiliser un list au lieu d'un Tuple. Cela devient plus important, je pense, lorsque vous saisissez des attributs au lieu des "index magiques" d'une liste/tuple.

Dans mon cas, je voulais trier plusieurs attributs d'une classe, où les clés entrantes étaient des chaînes. J'avais besoin d'un tri différent en différents endroits et d'un tri par défaut commun pour la classe parente avec laquelle les clients interagissaient; ne pas avoir à écraser les 'clés de tri' quand j'ai vraiment 'besoin', mais aussi de manière à pouvoir les stocker sous forme de listes que la classe pourrait partager

J'ai donc d'abord défini une méthode d'assistance

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 ]

puis l'utiliser

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

Ceci utilisera la fonction lambda générée pour trier la liste par object.attrA puis object.attrB en supposant que object ait un getter correspondant aux noms de chaîne fournis. Et le second cas trierait par object.attrC puis object.attrA.

Cela vous permet également d’exposer potentiellement les choix de tri en sortie aux utilisateurs, aux tests unitaires, ou peut-être de vous dire comment ils souhaitent que le tri soit effectué pour certaines opérations dans votre API, il vous suffit de vous donner une liste et non. les coupler à votre implémentation dorsale.

3
UpAndAdam

Voici un moyen: vous réécrivez votre fonction de tri pour prendre une liste de fonctions de tri. Chaque fonction de tri compare les attributs que vous souhaitez tester. Pour chaque test de tri, vous regardez et voyez si la fonction cmp renvoie un retour non nul. si casser et envoyer la valeur de retour. Vous l'appelez en appelant un Lambda d'une fonction d'une liste de Lambdas.

Son avantage réside dans le fait qu'il passe en une seule fois dans les données et non comme une sorte de tri antérieur comme le font les autres méthodes. Une autre chose est qu'il trie en place, alors que trié semble faire une copie.

Je l'ai utilisé pour écrire une fonction de classement, qui classe une liste de classes dans lesquelles chaque objet est dans un groupe et possède une fonction de score, mais vous pouvez ajouter n'importe quelle liste d'attributs. Notez l'utilisation non-lambda, bien que féroce, d'un lambda pour appeler un passeur. La partie de rang ne fonctionnera pas pour un tableau de listes, mais le tri fonctionnera.

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

Voici un moyen de classer une liste d'objets

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

Plusieurs années de retard pour le parti mais je veux les deux trier sur 2 critères et utiliser reverse=True. Si quelqu'un d'autre veut savoir comment, vous pouvez envelopper vos critères (fonctions) entre parenthèses:

s = sorted(my_list, key=lambda i: ( criteria_1(i), criteria_2(i) ), reverse=True)
0
ron g