186

Lets say I have the following code:

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

Is there a way I can access the items in a numbered manner, like:

d(0) #foo's Output
d(1) #bar's Output
Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
Billjk
  • 10,387
  • 23
  • 54
  • 73
  • for only the values do `od.value()` for only the keys do `od.keys()` for a tuple of both do `od.items()`. – Charlie Parker Nov 09 '21 at 18:41
  • correct me if I'm wrong, nothing suggested here is both included in the python standard libraries and allowing arbitrary index access at O(1) unless your dict keys are assumed immutable and you initially extract the dict to a list before getting going. – matanster Apr 20 '23 at 21:14

9 Answers9

225

If its an OrderedDict() you can easily access the elements by indexing by getting the tuples of (key,value) pairs as follows

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

Note for Python 3.X

dict.items would return an iterable dict view object rather than a list. We need to wrap the call onto a list in order to make the indexing possible

>>> items = list(d.items())
>>> items
[('foo', 'python'), ('bar', 'spam')]
>>> items[0]
('foo', 'python')
>>> items[1]
('bar', 'spam')
Abhijit
  • 62,056
  • 18
  • 131
  • 204
  • Good answer. Lists are the TheRightWay(tm) for doing indexed lookups. – Raymond Hettinger Apr 07 '12 at 22:10
  • 32
    Note that in 3.x the `items` method returns an interable dictionary view object rather than a list, and don't support slicing or indexing. So you'd have to turn it into a list first. http://docs.python.org/3.3/library/stdtypes.html#dict-views – Peter DeGlopper Jun 05 '13 at 21:51
  • 13
    Copying items, values or keys into lists can be quite slow for big dictonaries. I created a rewrite of OrderedDict() with a different internal datastructure for applications that have to do this very often: https://github.com/niklasf/indexed.py – Niklas Sep 06 '13 at 20:07
  • 1
    @PeterDeGlopper how do I turn it into a list? – Dejell Jan 07 '14 at 21:57
  • 1
    @Dejel - use the constructor: `list(d.items())` – Peter DeGlopper Jan 07 '14 at 23:38
  • 1
    @Niklas if you are concerned with speed you should go with my [C implementation](https://pypi.python.org/pypi/ruamel.ordereddict/0.4.6) of which collections.OrderedDict is a subset. – Anthon Jan 28 '14 at 09:48
  • Might be pushing it, but since items returns an iterable dictionary view object, is there any chance we can modify the value in the tuple pair in d.items()[1]? Ex: d.items()[1].value = "Blah" – Ninja Sep 14 '15 at 18:14
  • Actually since it returns a iterable dictionary view object, I believe that modifying the value here is possible, but it is not possible to add or remove elements to the dictionary, just like any iterator would work. Correct me if I am wrong! – Ninja Sep 14 '15 at 18:20
  • 11
    If you only access one item, you can avoid the memory overhead of `list(d.items())` by using `next(islice(d.items(), 1))` to get `('bar', 'spam')` – Quantum7 Oct 20 '17 at 15:37
  • @CecilCurry Thanks for the encouragement. The OP seemed to want to access several different items of the dict, in which case it would be faster to convert it to a list. However, I've now started a community wiki including this technique. – Quantum7 Nov 09 '17 at 13:18
  • Why isn't `items = list(d.items())` -> ` items = tuple(d.items())`? – lucid_dreamer Jan 02 '18 at 03:35
  • It should be mentioned that this is O(n). See Quantum7's community wiki answer for evidence. – Evgeni Sergeev May 03 '18 at 18:07
  • I'd like only the values as a list/iterable. How does one do that? ok one does `od.value()` – Charlie Parker Nov 09 '21 at 18:39
27

Do you have to use an OrderedDict or do you specifically want a map-like type that's ordered in some way with fast positional indexing? If the latter, then consider one of Python's many sorted dict types (which orders key-value pairs based on key sort order). Some implementations also support fast indexing. For example, the sortedcontainers project has a SortedDict type for just this purpose.

>>> 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
  • 8,162
  • 3
  • 52
  • 46
  • 1
    You could also use `SortedDict` with a key function to avoid comparisons. Like: `SortedDict(lambda key: 0, ...)`. Keys will then be unsorted but will remain in a stable order and are indexable. – GrantJ Jan 04 '16 at 23:48
  • I'd like only the values as a list/iterable. How does one do that? ok one does `od.values()` For only the values do `od.value()` for only the keys do `od.keys()` for a tuple of both do `od.items()`. – Charlie Parker Nov 09 '21 at 18:39
22

Here is a special case if you want the first entry (or close to it) in an OrderedDict, without creating a list. (This has been updated to 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'

(The first time you say "next()", it really means "first.")

In my informal test, next(iter(d.items())) with a small OrderedDict is only a tiny bit faster than items()[0]. With an OrderedDict of 10,000 entries, next(iter(d.items())) was about 200 times faster than items()[0].

BUT if you save the items() list once and then use the list a lot, that could be faster. Or if you repeatedly { create an items() iterator and step through it to to the position you want }, that could be slower.

  • 10
    Python 3 `OrderedDict`s don't have an `iteritems()` method, so you will need to do the following in order to obtain the first item: `next(iter(d.items()))`. – Nathan Osman Apr 05 '15 at 21:46
  • In Python 3 `d.items()` does not seem to be an iterator, so iter in front will not help? It will still return the complete list :( – asksol Jun 21 '16 at 00:42
  • 1
    Update: I was wrong, iter(d.items()) returns `odict_iterator` and was confirmed to me on IRC #python that this does not make a copy of the list. – asksol Jun 21 '16 at 00:47
  • @Nathan Osman, thanks for the nudge. I finally updated myself to Python 3 recently! – SteveWithamDuplicate Apr 16 '20 at 04:38
22

It is dramatically more efficient to use IndexedOrderedDict from the indexed package.

Following Niklas's comment, I have done a benchmark on OrderedDict and IndexedOrderedDict with 1000 entries.

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 is ~100 times faster in indexing elements at specific position in this specific case.

Other solutions listed require an extra step. IndexedOrderedDict is a drop-in replacement for OrderedDict, except it's indexable.

Jonathan B.
  • 2,742
  • 1
  • 21
  • 18
Jinguo Liu
  • 665
  • 6
  • 8
12

This community wiki attempts to collect existing answers.

Python 2.7

In python 2, the keys(), values(), and items() functions of OrderedDict return lists. Using values as an example, the simplest way is

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

For large collections where you only care about a single index, you can avoid creating the full list using the generator versions, iterkeys, itervalues and iteritems:

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

The indexed.py package provides IndexedOrderedDict, which is designed for this use case and will be the fastest option.

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

Using itervalues can be considerably faster for large dictionaries with random access:

$ 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 has the same two basic options (list vs generator), but the dict methods return generators by default.

List method:

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

Generator method:

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

Python 3 dictionaries are an order of magnitude faster than python 2 and have similar speedups for using generators.

+--------+-----------+----------------+---------+
|  size  | list (ms) | generator (ms) | indexed |
+--------+-----------+----------------+---------+
|   1000 | .0316     | .0165          | .00262  |
|  10000 | .288      | .166           | .00294  |
| 100000 | 3.53      | 1.48           | .00332  |
+--------+-----------+----------------+---------+
Giorgos Xou
  • 1,461
  • 1
  • 13
  • 32
Quantum7
  • 3,165
  • 3
  • 34
  • 45
7

It's a new era and with Python 3.6.1 dictionaries now retain their order. These semantics aren't explicit because that would require BDFL approval. But Raymond Hettinger is the next best thing (and funnier) and he makes a pretty strong case that dictionaries will be ordered for a very long time.

So now it's easy to create slices of a dictionary:

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

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

Note: Dictonary insertion-order preservation is now official in Python 3.7.

highpost
  • 1,263
  • 2
  • 14
  • 25
3

If you have pandas installed, you can convert the ordered dict to a pandas Series. This will allow random access to the dictionary elements.

>>> import collections
>>> import pandas as pd
>>> d = collections.OrderedDict()
>>> d['foo'] = 'python'
>>> d['bar'] = 'spam'

>>> s = pd.Series(d)

>>> s['bar']
spam
>>> s.iloc[1]
spam
>>> s.index[1]
bar
Yuval Atzmon
  • 5,645
  • 3
  • 41
  • 74
1

for OrderedDict() you can access the elements by indexing by getting the tuples of (key,value) pairs as follows or using '.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']
1

If you're dealing with fixed number of keys that you know in advance, use Python's inbuilt namedtuples instead. A possible use-case is when you want to store some constant data and access it throughout the program by both indexing and specifying keys.

import collections
ordered_keys = ['foo', 'bar']
D = collections.namedtuple('D', ordered_keys)
d = D(foo='python', bar='spam')

Access by indexing:

d[0] # result: python
d[1] # result: spam

Access by specifying keys:

d.foo # result: python
d.bar # result: spam

Or better:

getattr(d, 'foo') # result: python
getattr(d, 'bar') # result: spam
Jaladh Singhal
  • 393
  • 4
  • 10