Как добавить элемент в верхней части OrderedDict в python?

у меня есть это

d1 = OrderedDict([('a', '1'), ('b', '2')])

если я сделаю это

d1.update({'c':'3'})

тогда я получаю это

OrderedDict([('a', '1'), ('b', '2'), ('c', '3')])

но я хочу, чтобы этот

[('c', '3'), ('a', '1'), ('b', '2')]

без создания нового словаря

9 ответов


в Python 2 нет встроенного метода для этого. Если вам это нужно, вам нужно написать prepend() метод/функция, которая работает на OrderedDict внутренние с O (1) сложностью.

для Python 3.2 и более поздних, вы можете использовать move_to_end1 метод. Метод принимает last аргумент, который указывает, будет ли элемент перемещен вниз (last=True) или верхней (last=False) из OrderedDict.

наконец, если вы хотите быстрый, грязный и медленно решение, вы можете просто создать новый OrderedDict С нуля.

детали для четырех различных решений:


расширения OrderedDict и добавьте новый метод экземпляра

from collections import OrderedDict

class MyOrderedDict(OrderedDict):

    def prepend(self, key, value, dict_setitem=dict.__setitem__):

        root = self._OrderedDict__root
        first = root[1]

        if key in self:
            link = self._OrderedDict__map[key]
            link_prev, link_next, _ = link
            link_prev[1] = link_next
            link_next[0] = link_prev
            link[0] = root
            link[1] = first
            root[1] = first[0] = link
        else:
            root[1] = first[0] = self._OrderedDict__map[key] = [root, first, key]
            dict_setitem(self, key, value)

демо:

>>> d = MyOrderedDict([('a', '1'), ('b', '2')])
>>> d
MyOrderedDict([('a', '1'), ('b', '2')])
>>> d.prepend('c', 100)
>>> d
MyOrderedDict([('c', 100), ('a', '1'), ('b', '2')])
>>> d.prepend('a', d['a'])
>>> d
MyOrderedDict([('a', '1'), ('c', 100), ('b', '2')])
>>> d.prepend('d', 200)
>>> d
MyOrderedDict([('d', 200), ('a', '1'), ('c', 100), ('b', '2')])

автономная функция, которая манипулирует OrderedDict объекты

эта функция делает то же самое, принимая объект dict, ключ и значение. Я лично предпочитаю класс:

from collections import OrderedDict

def ordered_dict_prepend(dct, key, value, dict_setitem=dict.__setitem__):
    root = dct._OrderedDict__root
    first = root[1]

    if key in dct:
        link = dct._OrderedDict__map[key]
        link_prev, link_next, _ = link
        link_prev[1] = link_next
        link_next[0] = link_prev
        link[0] = root
        link[1] = first
        root[1] = first[0] = link
    else:
        root[1] = first[0] = dct._OrderedDict__map[key] = [root, first, key]
        dict_setitem(dct, key, value)

демо:

>>> d = OrderedDict([('a', '1'), ('b', '2')])
>>> ordered_dict_prepend(d, 'c', 100)
>>> d
OrderedDict([('c', 100), ('a', '1'), ('b', '2')])
>>> ordered_dict_prepend(d, 'a', d['a'])
>>> d
OrderedDict([('a', '1'), ('c', 100), ('b', '2')])
>>> ordered_dict_prepend(d, 'd', 500)
>>> d
OrderedDict([('d', 500), ('a', '1'), ('c', 100), ('b', '2')])

использовать OrderedDict.move_to_end() (Python >= 3.2)

1в Python 3.2 введен the OrderedDict.move_to_end() метод. Используя его, мы можем переместить существующий ключ в любой конец словаря в O(1) времени.

>>> d1 = OrderedDict([('a', '1'), ('b', '2')])
>>> d1.update({'c':'3'})
>>> d1.move_to_end('c', last=False)
>>> d1
OrderedDict([('c', '3'), ('a', '1'), ('b', '2')])

если нам нужно вставить элемент и переместить его наверх, все в один шаг, мы можем напрямую использовать его для создания prepend() фантик (не представленных здесь).


создать новый OrderedDict - медленно!!!

если вы не хотите этого делать и производительность не является проблемой тогда самый простой способ-создать новый дикт:

from itertools import chain, ifilterfalse
from collections import OrderedDict


def unique_everseen(iterable, key=None):
    "List unique elements, preserving order. Remember all elements ever seen."
    # unique_everseen('AAAABBBCCDAABBB') --> A B C D
    # unique_everseen('ABBCcAD', str.lower) --> A B C D
    seen = set()
    seen_add = seen.add
    if key is None:
        for element in ifilterfalse(seen.__contains__, iterable):
            seen_add(element)
            yield element
    else:
        for element in iterable:
            k = key(element)
            if k not in seen:
                seen_add(k)
                yield element

d1 = OrderedDict([('a', '1'), ('b', '2'),('c', 4)])
d2 = OrderedDict([('c', 3), ('e', 5)])   #dict containing items to be added at the front
new_dic = OrderedDict((k, d2.get(k, d1.get(k))) for k in \
                                           unique_everseen(chain(d2, d1)))
print new_dic

выход:

OrderedDict([('c', 3), ('e', 5), ('a', '1'), ('b', '2')])


Я только что написал подкласс OrderedDict в моем проекте для аналогичной цели. вот так.

операции вставки также являются неизменными времени O(1) (они не требуют перестройки структуры данных), в отличие от большинства этих решений.

>>> d1 = ListDict([('a', '1'), ('b', '2')])
>>> d1.insert_before('a', ('c', 3))
>>> d1
ListDict([('c', 3), ('a', '1'), ('b', '2')])

вы должны сделать новый экземпляр OrderedDict. Если ваши ключи уникальны:

d1=OrderedDict([("a",1),("b",2)])
d2=OrderedDict([("c",3),("d",99)])
both=OrderedDict(list(d2.items()) + list(d1.items()))
print(both)

#OrderedDict([('c', 3), ('d', 99), ('a', 1), ('b', 2)])

но если нет, остерегайтесь, как это поведение может быть или не может быть желательным для вас:

d1=OrderedDict([("a",1),("b",2)])
d2=OrderedDict([("c",3),("b",99)])
both=OrderedDict(list(d2.items()) + list(d1.items()))
print(both)

#OrderedDict([('c', 3), ('b', 2), ('a', 1)])

Если вы знаете, что вам понадобится ключ "c", но не знаете значения, вставьте " c " с фиктивным значением при создании dict.

d1 = OrderedDict([('c', None), ('a', '1'), ('b', '2')])

и изменить его позже.

d1['c'] = 3

теперь это возможно с move_to_end (key, last=True)

>>> d = OrderedDict.fromkeys('abcde')
>>> d.move_to_end('b')
>>> ''.join(d.keys())
'acdeb'
>>> d.move_to_end('b', last=False)
>>> ''.join(d.keys())
'bacde'

https://docs.python.org/3/library/collections.html#collections.OrderedDict.move_to_end


Если вам нужна функциональность, которой нет, просто расширьте класс тем, что вы хотите:

from collections import OrderedDict

class OrderedDictWithPrepend(OrderedDict):
    def prepend(self, other):
        ins = []
        if hasattr(other, 'viewitems'):
            other = other.viewitems()
        for key, val in other:
            if key in self:
                self[key] = val
            else:
                ins.append((key, val))
        if ins:
            items = self.items()
            self.clear()
            self.update(ins)
            self.update(items)

не очень эффективно, но работает:

o = OrderedDictWithPrepend()

o['a'] = 1
o['b'] = 2
print o
# OrderedDictWithPrepend([('a', 1), ('b', 2)])

o.prepend({'c': 3})
print o
# OrderedDictWithPrepend([('c', 3), ('a', 1), ('b', 2)])

o.prepend([('a',11),('d',55),('e',66)])
print o
# OrderedDictWithPrepend([('d', 55), ('e', 66), ('c', 3), ('a', 11), ('b', 2)])

Я бы предложил добавить prepend() метод для этого чистого Python рецепт или вывод из него подкласса. Код для этого может быть довольно эффективным, учитывая, что базовая структура данных для заказа является связанным списком.


FWIW вот быстрый-N-грязный код, который я написал для вставки в произвольную позицию индекса. Не обязательно эффективный, но он работает на месте.

class OrderedDictInsert(OrderedDict):
    def insert(self, index, key, value):
        self[key] = value
        for ii, k in enumerate(list(self.keys())):
            if ii >= index and k != key:
                self.move_to_end(k)

вы можете использовать другую структуру в целом, но есть способы сделать это в python 2.7.

d1 = OrderedDict([('a', '1'), ('b', '2')])
d2 = OrderedDict(c='3')
d2.update(d1)

d2 будет содержать

>>> d2
OrderedDict([('c', '3'), ('a', '1'), ('b', '2')])

а, в python 3.2 можно использовать OrderedDict.move_to_end('c', last=False) для перемещения заданного ключа после вставки.

Примечание: примите во внимание, что первый вариант медленнее для больших наборов данных из-за создания нового OrderedDict и копирование старых значений.