web-dev-qa-db-fra.com

Tri du dictionnaire à l'aide de operator.itemgetter

Une question a été posée ici sur SO il y a quelques minutes, sur le tri des clés de dictionnaire en fonction de leurs valeurs. 

Je viens de lire à propos de la méthode de tri operator.itemgetter quelques jours en arrière et j'ai décidé de l'essayer, mais cela ne semble pas fonctionner.

Même si les réponses aux questions ne me posent aucun problème, je voulais juste essayer avec operator.itemgetter

Le dict était donc:

>>> mydict = { 'a1': ['g',6],
           'a2': ['e',2],
           'a3': ['h',3],
           'a4': ['s',2],
           'a5': ['j',9],
           'a6': ['y',7] }

J'ai essayé ceci:

>>> l = sorted(mydict.itervalues(), key=operator.itemgetter(1))
>>> l
[['e', 2], ['s', 2], ['h', 3], ['g', 6], ['y', 7], ['j', 9]]

Et cela fonctionne comme je le veux. Cependant, comme je n'ai pas le dictionnaire complet (mydict.itervalues()), j'ai essayé ceci:

>>> complete = sorted(mydict.iteritems(), key=operator.itemgetter(2))

Cela ne fonctionne pas (comme je m'y attendais).

Alors, comment puis-je trier le dict en utilisant operator.itemgetter et appeler itemgetter sur la paire clé-valeur imbriquée. 

19
user225312
In [6]: sorted(mydict.iteritems(), key=lambda (k,v): operator.itemgetter(1)(v))
Out[6]: 
[('a2', ['e', 2]),
 ('a4', ['s', 2]),
 ('a3', ['h', 3]),
 ('a1', ['g', 6]),
 ('a6', ['y', 7]),
 ('a5', ['j', 9])]

Le paramètre clé est toujours une fonction qui alimente un élément à la fois de l'itérable (mydict.iteritems()). Dans ce cas, un élément pourrait être quelque chose comme 

('a2',['e',2])

Nous avons donc besoin d’une fonction pouvant prendre ('a2',['e',2]) en entrée et renvoyer 2.

lambda (k,v): ... est une fonction anonyme qui prend un argument - un 2-Tuple - et le décompresse en k et v. Ainsi, lorsque la fonction lambda est appliquée à notre élément, k serait 'a2' et v serait ['e',2]

lambda (k,v): operator.itemgetter(1)(v) appliqué à notre élément renvoie donc operator.itemgetter(1)(['e',2]), qui "élément" le deuxième élément de ['e',2], qui est 2.

Notez que lambda (k,v): operator.itemgetter(1)(v) n'est pas un bon moyen de coder en Python. Comme le fait remarquer gnibbler, operator.itemgetter(1) est recalculé pour chaque élément . C'est inefficace. Le but de operator.itemgetter(1) est de créer une fonction qui peut être appliquée plusieurs fois. Vous ne voulez pas recréer la fonction à chaque fois. lambda (k,v): v[1] est plus lisible et plus rapide:

In [15]: %timeit sorted(mydict.iteritems(), key=lambda (k,v): v[1])
100000 loops, best of 3: 7.55 us per loop

In [16]: %timeit sorted(mydict.iteritems(), key=lambda (k,v): operator.itemgetter(1)(v))
100000 loops, best of 3: 11.2 us per loop
30
unutbu

La réponse est: vous ne pouvez pas. operator.itemgetter(i) renvoie un appelable qui renvoie l'élément i de son argument, c'est-à-dire

f = operator.itemgetter(i)
f(d) == d[i]

il ne retournera jamais quelque chose comme d[i][j]. Si vous voulez vraiment faire cela dans un style purement fonctionnel, vous pouvez écrire votre propre fonction compose():

def compose(f, g):
    return lambda *args: f(g(*args))

et utilise

sorted(mydict.iteritems(), key=compose(operator.itemgetter(1),
                                       operator.itemgetter(1)))

Notez que je n'ai pas recommandé de faire cela :)

5
Sven Marnach

itemgetter ne prend pas en charge l'imbrication (bien que attrgetter l'utilise)

vous auriez besoin d'aplatir le dict comme ça

sorted(([k]+v for k,v in mydict.iteritems()), key=itemgetter(2))
5
John La Rooy

L'indexation normalement à la kv[1][1] est plus rapide:

>>> from timeit import timeit
>>> setup = 'import operator; g = operator.itemgetter(1); '
>>> setup += 'd = {i: list(range(i+2)) for i in range(100)}'
>>> kwargs = {'setup': setup, 'number': 10000}

>>> timeit('sorted(d.items(), key=lambda kv: kv[1][1])', **kwargs)
0.5251589557155967

>>> timeit('sorted(d.items(), key=lambda kv: g(kv[1]))', **kwargs)
0.7175205536186695

>>> timeit('sorted(d.items(), key=lambda kv: g(kv)[1])', **kwargs)
0.7915238151326776

>>> timeit('sorted(d.items(), key=lambda kv: g(g(kv)))', **kwargs)
0.9781978335231543
0
Mateen Ulhaq