Accéder aux éléments d'une collection.OrderedDict by index

142

Disons que j'ai le code suivant:

import collections
d = collections.OrderedDict()
d['foo'] = 'python'
d['bar'] = 'spam'

Existe-t-il un moyen d'accéder aux éléments de manière numérotée, comme:

d(0) #foo's Output
d(1) #bar's Output
Billjk
la source

Réponses:

181

Si c'est un, OrderedDict()vous pouvez facilement accéder aux éléments en les indexant en obtenant les tuples des paires (clé, valeur) comme suit

>>> import collections
>>> d = collections.OrderedDict()
>>> d['foo'] = 'python'
>>> d['bar'] = 'spam'
>>> d.items()
[('foo', 'python'), ('bar', 'spam')]
>>> d.items()[0]
('foo', 'python')
>>> d.items()[1]
('bar', 'spam')

Remarque pour Python 3.X

dict.itemsretournerait un objet de vue dict itérable plutôt qu'une liste. Nous devons encapsuler l'appel sur une liste afin de rendre l'indexation possible

>>> items = list(d.items())
>>> items
[('foo', 'python'), ('bar', 'spam')]
>>> items[0]
('foo', 'python')
>>> items[1]
('bar', 'spam')
Abhijit
la source
21
Notez que dans 3.x, la itemsméthode renvoie un objet de vue dictionnaire interable plutôt qu'une liste, et ne prend pas en charge le découpage ou l'indexation. Il faudrait donc d'abord en faire une liste. docs.python.org/3.3/library/stdtypes.html#dict-views
Peter DeGlopper
8
La copie d'éléments, de valeurs ou de clés dans des listes peut être assez lente pour les gros dictionnaires. J'ai créé une réécriture de OrderedDict () avec une structure de données interne différente pour les applications qui doivent le faire très souvent: github.com/niklasf/indexed.py
Niklas
1
@PeterDeGlopper comment puis-je en faire une liste?
Dejell
1
@Dejel - utilisez le constructeur:list(d.items())
Peter DeGlopper
9
Si vous list(d.items())next(islice(d.items(), 1))('bar', 'spam')
n'accédez
24

Devez-vous utiliser un OrderedDict ou voulez-vous spécifiquement un type de carte qui est ordonné d'une certaine manière avec une indexation positionnelle rapide? Dans ce dernier cas, considérez l'un des nombreux types de dict triés de Python (qui classe les paires clé-valeur en fonction de l'ordre de tri des clés). Certaines implémentations prennent également en charge l'indexation rapide. Par exemple, le projet sortedcontainers a un type SortedDict uniquement à cette fin.

>>> from sortedcontainers import SortedDict
>>> sd = SortedDict()
>>> sd['foo'] = 'python'
>>> sd['bar'] = 'spam'
>>> print sd.iloc[0] # Note that 'bar' comes before 'foo' in sort order.
'bar'
>>> # If you want the value, then simple do a key lookup:
>>> print sd[sd.iloc[1]]
'python'
GrantJ
la source
1
Vous pouvez également utiliser SortedDictune fonction clé pour éviter les comparaisons. Comme: SortedDict(lambda key: 0, ...). Les clés ne seront alors pas triées mais resteront dans un ordre stable et seront indexables.
GrantJ
19

Voici un cas particulier si vous souhaitez la première entrée (ou à proximité) dans un OrderedDict, sans créer de liste. (Cela a été mis à jour vers Python 3):

>>> from collections import OrderedDict
>>> 
>>> d = OrderedDict()
>>> d["foo"] = "one"
>>> d["bar"] = "two"
>>> d["baz"] = "three"
>>> next(iter(d.items()))
('foo', 'one')
>>> next(iter(d.values()))
'one'

(La première fois que vous dites «suivant ()», cela signifie vraiment «d'abord».)

Dans mon test informel, next(iter(d.items()))avec un petit OrderedDict, ce n'est qu'un tout petit peu plus rapide que items()[0]. Avec un OrderedDict de 10 000 entrées, next(iter(d.items()))était environ 200 fois plus rapide que items()[0].

MAIS si vous enregistrez la liste des éléments () une fois et que vous l'utilisez ensuite beaucoup, cela pourrait être plus rapide. Ou si vous {créez un itérateur items () à plusieurs reprises et que vous le parcourez jusqu'à la position souhaitée}, cela pourrait être plus lent.

SteveWithamDupliquer
la source
10
Python 3 OrderedDicts ne sont pas une iteritems()méthode, vous devez faire ce qui suit afin d'obtenir le premier élément: next(iter(d.items())).
Nathan Osman
En Python 3 d.items()ne semble pas être un itérateur, donc iter devant n'aidera pas? Il renverra toujours la liste complète :(
askol
1
Mise à jour: je me suis trompé, iter (d.items ()) revient odict_iteratoret m'a été confirmé sur IRC #python que cela ne faisait pas de copie de la liste.
askol
@Nathan Osman, merci pour le coup de pouce. Je me suis finalement mis à jour vers Python 3 récemment!
SteveWitham
14

Il est considérablement plus efficace d'utiliser IndexedOrderedDict à partir du indexedpackage.

Suite au commentaire de Niklas, j'ai fait un benchmark sur OrderedDict et IndexedOrderedDict avec 1000 entrées.

In [1]: from numpy import *
In [2]: from indexed import IndexedOrderedDict
In [3]: id=IndexedOrderedDict(zip(arange(1000),random.random(1000)))
In [4]: timeit id.keys()[56]
1000000 loops, best of 3: 969 ns per loop

In [8]: from collections import OrderedDict
In [9]: od=OrderedDict(zip(arange(1000),random.random(1000)))
In [10]: timeit od.keys()[56]
10000 loops, best of 3: 104 µs per loop

IndexedOrderedDict est environ 100 fois plus rapide dans l'indexation des éléments à une position spécifique dans ce cas spécifique.

刘金国
la source
Agréable! Pas encore à Anaconda malheureusement.
Konstantin
1
@Konstantin Le nom réel du package est indexed.py . Essayez d'installer à la indexed.pyplace de indexed.
Sven Haile
9

Ce wiki communautaire tente de collecter les réponses existantes.

Python 2.7

En python 2, les keys(), values()et les items()fonctions des OrderedDictlistes de retour. En utilisant valuescomme exemple, le moyen le plus simple est

d.values()[0]  # "python"
d.values()[1]  # "spam"

Pour les grandes collections où vous ne vous souciez que d'un seul index, vous pouvez éviter de créer la liste complète à l'aide des versions du générateur iterkeys, itervalueset iteritems:

import itertools
next(itertools.islice(d.itervalues(), 0, 1))  # "python"
next(itertools.islice(d.itervalues(), 1, 2))  # "spam"

Le package indexed.py fournit IndexedOrderedDict, qui est conçu pour ce cas d'utilisation et sera l'option la plus rapide.

from indexed import IndexedOrderedDict
d = IndexedOrderedDict({'foo':'python','bar':'spam'})
d.values()[0]  # "python"
d.values()[1]  # "spam"

L'utilisation d'itervalues ​​peut être considérablement plus rapide pour les grands dictionnaires à accès aléatoire:

$ python2 -m timeit -s 'from collections import OrderedDict; from random import randint; size = 1000;   d = OrderedDict({i:i for i in range(size)})'  'i = randint(0, size-1); d.values()[i:i+1]'
1000 loops, best of 3: 259 usec per loop
$ python2 -m timeit -s 'from collections import OrderedDict; from random import randint; size = 10000;  d = OrderedDict({i:i for i in range(size)})' 'i = randint(0, size-1); d.values()[i:i+1]'
100 loops, best of 3: 2.3 msec per loop
$ python2 -m timeit -s 'from collections import OrderedDict; from random import randint; size = 100000; d = OrderedDict({i:i for i in range(size)})' 'i = randint(0, size-1); d.values()[i:i+1]'
10 loops, best of 3: 24.5 msec per loop

$ python2 -m timeit -s 'from collections import OrderedDict; from random import randint; size = 1000;   d = OrderedDict({i:i for i in range(size)})' 'i = randint(0, size-1); next(itertools.islice(d.itervalues(), i, i+1))'
10000 loops, best of 3: 118 usec per loop
$ python2 -m timeit -s 'from collections import OrderedDict; from random import randint; size = 10000;  d = OrderedDict({i:i for i in range(size)})' 'i = randint(0, size-1); next(itertools.islice(d.itervalues(), i, i+1))'
1000 loops, best of 3: 1.26 msec per loop
$ python2 -m timeit -s 'from collections import OrderedDict; from random import randint; size = 100000; d = OrderedDict({i:i for i in range(size)})' 'i = randint(0, size-1); next(itertools.islice(d.itervalues(), i, i+1))'
100 loops, best of 3: 10.9 msec per loop

$ python2 -m timeit -s 'from indexed import IndexedOrderedDict; from random import randint; size = 1000;   d = IndexedOrderedDict({i:i for i in range(size)})' 'i = randint(0, size-1); d.values()[i]'
100000 loops, best of 3: 2.19 usec per loop
$ python2 -m timeit -s 'from indexed import IndexedOrderedDict; from random import randint; size = 10000;  d = IndexedOrderedDict({i:i for i in range(size)})' 'i = randint(0, size-1); d.values()[i]'
100000 loops, best of 3: 2.24 usec per loop
$ python2 -m timeit -s 'from indexed import IndexedOrderedDict; from random import randint; size = 100000; d = IndexedOrderedDict({i:i for i in range(size)})' 'i = randint(0, size-1); d.values()[i]'
100000 loops, best of 3: 2.61 usec per loop

+--------+-----------+----------------+---------+
|  size  | list (ms) | generator (ms) | indexed |
+--------+-----------+----------------+---------+
|   1000 | .259      | .118           | .00219  |
|  10000 | 2.3       | 1.26           | .00224  |
| 100000 | 24.5      | 10.9           | .00261  |
+--------+-----------+----------------+---------+

Python 3.6

Python 3 a les deux mêmes options de base (liste vs générateur), mais les méthodes dict renvoient des générateurs par défaut.

Méthode de liste:

list(d.values())[0]  # "python"
list(d.values())[1]  # "spam"

Méthode du générateur:

import itertools
next(itertools.islice(d.values(), 0, 1))  # "python"
next(itertools.islice(d.values(), 1, 2))  # "spam"

Les dictionnaires Python 3 sont un ordre de grandeur plus rapides que python 2 et ont des accélérations similaires pour l'utilisation des générateurs.

+--------+-----------+----------------+---------+
|  size  | list (ms) | generator (ms) | indexed |
+--------+-----------+----------------+---------+
|   1000 | .0316     | .0165          | .00262  |
|  10000 | .288      | .166           | .00294  |
| 100000 | 3.53      | 1.48           | .00332  |
+--------+-----------+----------------+---------+
Quantum7
la source
7

C'est une nouvelle ère et avec Python 3.6.1, les dictionnaires conservent désormais leur ordre. Cette sémantique n'est pas explicite car cela nécessiterait l'approbation du BDFL. Mais Raymond Hettinger est la deuxième meilleure chose (et plus drôle) et il fait un argument assez solide que les dictionnaires seront commandés pendant très longtemps.

Alors maintenant, il est facile de créer des tranches d'un dictionnaire:

test_dict = {
                'first':  1,
                'second': 2,
                'third':  3,
                'fourth': 4
            }

list(test_dict.items())[:2]

Remarque: la préservation de l'ordre d'insertion des dictionnaires est désormais officielle dans Python 3.7 .

highpost
la source
0

pour OrderedDict (), vous pouvez accéder aux éléments en les indexant en obtenant les tuples des paires (clé, valeur) comme suit ou en utilisant '.values ​​()'

>>> import collections
>>> d = collections.OrderedDict()
>>> d['foo'] = 'python'
>>> d['bar'] = 'spam'
>>> d.items()
[('foo', 'python'), ('bar', 'spam')]
>>>d.values()
odict_values(['python','spam'])
>>>list(d.values())
['python','spam']
Mehar Rahim
la source