Доступ до предметів у колекціях. OrdDict by index


142

Скажімо, у мене є такий код:

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

Чи є спосіб я отримати доступ до предметів пронумерованим способом, наприклад:

d(0) #foo's Output
d(1) #bar's Output

Відповіді:


181

Якщо його an, OrderedDict()ви можете легко отримати доступ до елементів шляхом індексації, отримуючи кортежі пар (ключ, значення) наступним чином

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

Примітка для Python 3.X

dict.itemsповертає ітерабельний об'єкт перегляду зображень, а не список. Нам потрібно загорнути виклик у список, щоб зробити можливим індексацію

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

21
Зауважте, що в 3.x itemsметод повертає об'єкт подання словника, а не список, і не підтримує нарізання чи індексацію. Тому вам доведеться спочатку перетворити його на список. docs.python.org/3.3/library/stdtypes.html#dict-views
Peter DeGlopper

8
Копіювання елементів, значень чи ключів у списки може бути досить повільним для великих довідників. Я створив переписати OrdersDict () з іншою внутрішньою структурою даних для додатків, які мають робити це дуже часто: github.com/niklasf/indexed.py
Niklas

1
@PeterDeGlopper Як перетворити його у список?
Dejell

1
@Dejel - користуйся конструктором:list(d.items())
Peter DeGlopper

9
Якщо ви маєте доступ лише до одного елемента, ви можете уникнути накладних витрат на пам'ять list(d.items()), скориставшись next(islice(d.items(), 1))дістатися('bar', 'spam')
Quantum7

24

Чи потрібно використовувати OrdersDict або ви хочете спеціально тип, схожий на карту, який впорядкований якимось чином за допомогою швидкої позиційної індексації? Якщо останній, то розглянемо один із багатьох сортованих типів дикторів Python (який упорядковує пари ключ-значення на основі порядку сортування ключів). Деякі реалізації також підтримують швидку індексацію. Наприклад, проект sortedcontainers має тип SortedDict саме для цієї мети.

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

1
Ви також можете використовувати SortedDictключову функцію, щоб уникнути порівнянь. Як: SortedDict(lambda key: 0, ...). Ключі потім будуть несортованими, але залишаться у стабільному порядку та підлягають індексації.
GrantJ

19

Ось окремий випадок, якщо ви хочете, щоб перший запис (або близький до нього) в OrdersDict, без створення списку. (Це було оновлено до 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'

(Перший раз, коли ви говорите "наступний ()", це насправді означає "перший".)

У моєму неофіційному тесті next(iter(d.items()))з невеликим OrderedDict лише трохи - трохи швидше , ніж items()[0]. З OrdersDict в 10 000 записів next(iter(d.items()))було приблизно в 200 разів швидше, ніж items()[0].

АЛЕ якщо ви збережете список елементів () один раз, а потім скористаєтесь списком багато, це може бути швидше. Або якщо ви неодноразово {створюйте ітератор елементів () та переходите до нього до потрібного положення}, це може бути повільніше.


10
Python 3 OrderedDicts не має iteritems()методи, так що вам потрібно буде зробити наступне, щоб отримати перший елемент: next(iter(d.items())).
Натан Осман

У Python 3 d.items()не здається, що це ітератор, тож iter спереду не допоможе? Він все одно поверне повний список :(
askol

1
Оновлення: я помилявся, iter (d.items ()) повертається, odict_iteratorі мені було підтверджено на IRC #python, що це не робить копію списку.
askol

@Nathan Osman, дякую за натиск. Нарешті я нещодавно оновив себе на Python 3!
SteveWithamDuplicate

14

Значно ефективніше використовувати IndexedOrderedDict з indexedпакету.

Після коментаря Нікласа я зробив орієнтир на OrdersDict та IndexedOrderedDict з 1000 записами.

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 ~ в 100 разів швидше в індексуванні елементів у конкретній позиції в цьому конкретному випадку.


Приємно! На жаль, в Анаконда ще немає.
Костянтин

1
@Konstantin Фактична назва пакета indexed.py . Спробуйте встановити indexed.pyзамість indexed.
Свен Хайле

9

Ця вікі спільноти намагається зібрати наявні відповіді.

Python 2.7

В Python 2, keys(), values()і items()функції OrderedDictсписків повернення. Використовуючи valuesяк приклад, найпростіший спосіб

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

Для великих колекцій , де ви дбаєте тільки про одне індексі, можна уникнути створення повного списку з використанням версії генератора, iterkeys, itervaluesі iteritems:

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

Пакет indexed.py забезпечує IndexedOrderedDict, який призначений для цього випадку використання і буде найшвидшим варіантом.

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

Використання itervalues ​​може бути значно швидшим для великих словників із випадковим доступом:

$ 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  |
+--------+-----------+----------------+---------+

Пітон 3.6

У Python 3 є ті ж дві основні опції (список проти генератора), але методи dict повертають генераторів за замовчуванням.

Метод списку:

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

Спосіб генератора:

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

Словники Python 3 на порядок швидші, ніж python 2, і мають аналогічні прискорення використання генераторів.

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

7

Настала нова ера, і словники Python 3.6.1 тепер зберігають свій порядок. Ці семантики не є явними, оскільки для цього знадобиться схвалення BDFL. Але Реймонд Хеттінгер - це найкраща справа (і смішніше), і він робить досить вагомий випадок, що словники будуть замовлятись дуже довго.

Тому зараз легко створити фрагменти словника:

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

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

Примітка. Збереження порядку вставки диктонарів тепер офіційне в Python 3.7 .


0

для OrdersDict () ви можете отримати доступ до елементів шляхом індексації, отримуючи кортежі пар (ключ, значення) наступним чином або використовуючи ".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']
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.