2010-11-20 4 views
232

J'ai une liste de listes:Trier une liste par plusieurs attributs?

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

Si je voulais trier par un élément, par exemple l'élément de haut/court, je pouvais le faire via s = sorted(s, key = itemgetter(1)).

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

+0

[question PPCG connexes] (http://codegolf.stackexchange.com/q/85321/34718) – mbomb007

Répondre

406

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

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

Ou vous pouvez obtenir le même en utilisant itemgetter:

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

Et remarquez que là, vous pouvez utiliser sort au lieu d'utiliser sorted puis en réaffectant:

s.sort(key = operator.itemgetter(1, 2)) 
+2

Vous apprenez quelque chose de nouveau tous les jours ! Savez-vous si cela est plus rapide que la méthode précédente? Ou fait-il juste la même chose en arrière-plan? – headache

+2

@headache: Je ne sais pas ce qui est le plus rapide - je suppose qu'ils sont à peu près les mêmes. Vous pouvez utiliser le module 'timeit' pour mesurer les performances des deux si vous êtes intéressé. –

+14

Pour l'exhaustivité de timeit: pour moi d'abord donné 6 us par boucle et le deuxième 4,4 us par boucle –

18

Je suis Je ne savais pas si c'était la méthode la plus pythonique ... J'avais une liste de tuples qui nécessitaient un tri en commençant par les valeurs entières décroissantes et en 2ème par ordre alphabétique. Cela nécessite d'inverser le tri des entiers mais pas le tri alphabétique. Voici ma solution était: (à la volée dans un examen BTW, je ne savais même pas que vous pourriez « nid » trié fonctions)

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

puisque le 2e est un nombre, ça marche de le faire comme 'b = trié (a, = lambda x: (-x [1], x [0])) 'qui est plus visible sur quel critère s'applique en premier. quant à l'efficacité, je ne suis pas sûr, quelqu'un a besoin de temps. –

+0

@AndreiPetre votre réponse a eu tellement de sens pour moi. C'est tellement facile à retenir et à coder. Je vous remercie. – Sriram

1

Il semble que vous pouvez utiliser un list au lieu d'un tuple. Cela devient plus important, je pense que lorsque vous saisissez des attributs à la place des «index magiques» d'une liste/tuple.

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

Alors d'abord je définissais 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 de 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)) 

Cela utilisera le type de fonction lambda générée par la liste object.attrA puis object.attrB en supposant object a un getter correspondant aux noms de chaîne fournis. Et le deuxième cas trierait par object.attrC puis object.attrA. Cela vous permet également d'exposer potentiellement les choix de tri sortants par un consommateur, un test unitaire, ou de vous dire peut-être comment ils veulent que le tri soit fait pour une opération dans votre API simplement pour vous donner une liste et ne pas les coupler à votre implémentation back-end.

0

Voici une façon: Vous réécrivez votre fonction de tri pour prendre une liste de fonctions de tri, chaque fonction de tri compare les attributs que vous voulez tester, à chaque test de tri, vous regardez et voyez si la fonction cmp renvoie un retour non nul si c'est casse et envoie la valeur de retour. Vous l'appelez en appelant un Lambda d'une fonction d'une liste de Lambda.

Son avantage est qu'il fait un seul passage à travers les données pas une sorte de tri préalable comme le font d'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 où chaque objet est dans un groupe et a une fonction de score, mais vous pouvez ajouter n'importe quelle liste d'attributs. Notez l'utilisation semblable à un lambda, bien que hackish d'un lambda pour appeler un setter. La partie rank ne fonctionnera pas pour un tableau de listes, mais le tri le fera.

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

est ici 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 
Questions connexes