Нарезка Python OrderedDict

В моем коде мне часто нужно взять подмножество ключей и значений из Python OrderedDict (от collections пакет). Нарезка не работает (бросает TypeError: unhashable type) и альтернатива, повторяющаяся, громоздка:

from collections import OrderedDict

o = OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 4)])

# want to do:
# x = o[1:3]
# need to do:
x = OrderedDict()
for idx, key in enumerate(o):
    if 1 <= idx < 3:
        x[key] = o[key]

Есть ли лучший способ сделать это?

8 ответов

Решение

Упорядоченный dict в стандартной библиотеке не обеспечивает эту функциональность. Несмотря на то, что библиотеки существовали в течение нескольких лет до коллекций.OrderDict, которые имеют эту функциональность (и предоставляют по существу расширенный набор OrderedDict): voidspace odict и ruamel.ordereddict (я являюсь автором последнего пакета, который является переопределением odict в C):

from odict import OrderedDict as odict
p = odict([('a', 1), ('b', 2), ('c', 3), ('d', 4)])
print p[1:3]

В ruamel.ordereddict вы можете ослабить требование упорядоченного ввода (AFAIK вы не можете запрашивать производную от dict, если упорядочены его ключи (было бы хорошим дополнением к ruamel.ordereddict для распознавания collection.OrderedDicts)):

from ruamel.ordereddict import ordereddict

q = ordereddict(o, relax=True)
print q[1:3]
r = odict([('a', 1), ('b', 2), ('c', 3), ('d', 4)])
print r[1:3]

Если вы хотите (или должны) оставаться в стандартной библиотеке, вы можете создать подкласс collections.OrderedDict"s __getitem__:

class SlicableOrderedDict(OrderedDict):
    def __getitem__(self, k):
        if not isinstance(k, slice):
            return OrderedDict.__getitem__(self, k)
        x = SlicableOrderedDict()
        for idx, key in enumerate(self.keys()):
            if k.start <= idx < k.stop:
                x[key] = self[key]
        return x

s = SlicableOrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 4)])
print s[1:3]

Конечно, вы можете использовать более короткие версии Мартина или Джимми, чтобы получить реальный фрагмент, который нужно вернуть:

from itertools import islice
class SlicableOrderedDict(OrderedDict):
    def __getitem__(self, k):
        if not isinstance(k, slice):
            return OrderedDict.__getitem__(self, k)
        return SlicableOrderedDict(islice(self.viewitems(), k.start, k.stop))

t = SlicableOrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 4)])
print t[1:3]

или если вы просто хотите, чтобы все существующие OrderedDictбез подклассов:

def get_item(self, k):
    if not isinstance(k, slice):
        return OrderedDict._old__getitem__(self, k)
    return OrderedDict(islice(self.viewitems(), k.start, k.stop))

OrderedDict._old__getitem__ = OrderedDict.__getitem__
OrderedDict.__getitem__ = get_item

u = OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 4)])
print u[1:3]

Вы можете использовать itertools.islice функция, которая принимает итерацию и выводит stop первые элементы. Это полезно, так как итерации не поддерживают общий метод нарезки, и вам не нужно создавать целое items список из OrderedDict.

from collections import OrderedDict
from itertools import islice
o = OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 4)])
sliced = islice(o.iteritems(), 3)  # o.iteritems() is o.items() in Python 3
sliced_o = OrderedDict(sliced)

В Python 2 вы можете нарезать ключи:

x.keys()[1:3]

и для поддержки как Python 2, так и Python 3 вы должны сначала преобразовать в список:

list(k)[1:3]

Питон 2 OrderedDict.keys() реализация делает именно это.

В обоих случаях вам выдается список ключей в правильном порядке. Если создание первого списка является проблемой, вы можете использовать itertools.islice() и преобразовать итерируемые в список:

from itertools import islice

list(islice(x, 1, 3))

Все вышеперечисленное также может быть применено к предметам; использование dict.viewitems() в Python 2, чтобы получить то же итерационное поведение, что и в Python 3 dict.items() обеспечивает. Вы можете передать islice() возражать прямо другому OrderedDict() в этом случае:

OrderedDict(islice(x.items(), 1, 3))  # x.viewitems() in Python 2

Мне удалось нарезать OrderedDict, используя следующее:

      list(myordereddict.values())[start:stop]

Производительность не тестировал.

      x = OrderedDict(o.items()[1:3])
def slice_odict(odict, start=None, end=None):
    return OrderedDict([
        (k,v) for (k,v) in odict.items() 
        if k in list(odict.keys())[start:end]
    ])

Это позволяет:

>>> x = OrderedDict([('a',1), ('b',2), ('c',3), ('d',4)])
>>> slice_odict(x, start=-1)
OrderedDict([('d', 4)])
>>> slice_odict(x, end=-1)
OrderedDict([('a', 1), ('b', 2), ('c', 3)])
>>> slice_odict(x, start=1, end=3)
OrderedDict([('b', 2), ('c', 3)])

Я хотел нарезать, используя ключ, так как я не знал индекс заранее:

o = OrderedDict(zip(list('abcdefghijklmnopqrstuvwxyz'),range(1,27)))

stop = o.keys().index('e')           # -> 4
OrderedDict(islice(o.items(),stop))  # -> OrderedDict([('a', 1), ('b', 2), ('c', 3)])

или нарезать из start в stop:

start = o.keys().index('c')                    # -> 2
stop = o.keys().index('e')                     # -> 4
OrderedDict(islice(o.iteritems(),start,stop))  # -> OrderedDict([('c', 3), ('d', 4)])

Вы имеете дело с двусторонней очередью или двухсторонней очередью, которая является классической структурой данных. Вот ссылка: https://www.geeksforgeeks.org/deque-in-python/

Другие вопросы по тегам