¿Cómo puedo añadir un elemento en la parte superior de un OrderedDict en python?


Tengo esto

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

Si hago esto

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

Entonces obtengo esto

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

Pero quiero esto

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

Sin crear un nuevo Diccionario

Author: m000, 2013-05-21

9 answers

No hay un método integrado para hacer esto en Python 2. Si necesita esto, necesita escribir un método/función prepend() que opere en el interior de OrderedDict con complejidad O(1).

Para Python 3.2 y posteriores, puede utilizar el move_to_end1 método. El método acepta un argumento last que indica si el elemento se moverá a la parte inferior (last=True) o a la parte superior (last=False) del OrderedDict.

Finalmente, si quieres un rápido, sucio y lento solución, solo puede crear un nuevo OrderedDict desde cero.

Detalles para las cuatro soluciones diferentes:


Extender OrderedDict y añadir un nuevo método de instancia

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)

Demo:

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

Función Independiente que manipula OrderedDict objetos

Esta función hace lo mismo aceptando el objeto dict, la clave y el valor. Personalmente prefiero la clase:

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)

Demo:

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

Use OrderedDict.move_to_end() (Python >= 3.2)

1Python 3.2 introducido la OrderedDict.move_to_end() método. Usándolo, podemos mover una clave existente a cualquiera de los extremos del diccionario en tiempo 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')])

Si necesitamos insertar un elemento y moverlo a la parte superior, todo en un solo paso, podemos usarlo directamente para crear un contenedor prepend() (no presentado aquí).


Crear un nuevo OrderedDict - lento!!!

Si no quieres hacer eso y el rendimiento no es un problema entonces la forma más fácil es crear un nuevo dict:

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

Salida:

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

 51
Author: Ashwini Chaudhary,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-03 14:54:30

Acabo de escribir una subclase de OrderedDict en un proyecto mío con un propósito similar. Aquí está la esencia.

Las operaciones de inserción también son constantes O(1) (no requieren reconstruir la estructura de datos), a diferencia de la mayoría de estas soluciones.

>>> d1 = ListDict([('a', '1'), ('b', '2')])
>>> d1.insert_before('a', ('c', 3))
>>> d1
ListDict([('c', 3), ('a', '1'), ('b', '2')])
 13
Author: Jared,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-20 04:20:20

Tienes que crear una nueva instancia de OrderedDict. Si sus claves son únicas:

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

Pero si no, tenga cuidado ya que este comportamiento puede o no ser deseado para usted:

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)])
 10
Author: dolphin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-29 14:47:06

Si sabe que querrá una clave 'c', pero no conoce el valor, inserte 'c' con un valor ficticio cuando cree el diccionario.

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

Y cambie el valor más tarde.

d1['c'] = 3
 6
Author: Terry Jan Reedy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-25 16:45:13

Esto ahora es posible con 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

 3
Author: simP,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-22 11:28:42

Si necesitas una funcionalidad que no está ahí, simplemente extiende la clase con lo que quieras:

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)

No es terriblemente eficiente, pero funciona:

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)])
 1
Author: georg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-21 08:55:06

Sugeriría agregar un método prepend()a esta receta pura de Python o derivar una subclase de ella. El código para hacerlo podría ser bastante eficiente dado que la estructura de datos subyacente para ordenar es una lista vinculada.

 0
Author: martineau,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-21 09:39:53

FWIW Aquí hay un código quick-n-dirty que escribí para insertar en una posición de índice arbitraria. No necesariamente eficiente, pero funciona en el lugar.

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)
 0
Author: Tom Aldcroft,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-18 16:39:31

Es posible que desee utilizar una estructura diferente por completo, pero hay formas de hacerlo en python 2.7.

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

D2 contendrá entonces

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

Como han mencionado otros, en python 3.2 puede utilizar OrderedDict.move_to_end('c', last=False) para mover una tecla dada después de la inserción.

Nota: Tenga en cuenta que la primera opción es más lenta para conjuntos de datos grandes debido a la creación de un nuevo OrderedDict y la copia de valores antiguos.

 0
Author: wihlke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-14 19:59:36