Jak mogę dodać element na górze OrderedDict w python?


42

mam to

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

Jeśli mogę to zrobić

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

następnie uzyskać ten

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

ale chcę to

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

bez tworzenia nowego słownika

+2

myślę, że należy przeprojektować swój programm 21 maj. 132013-05-21 08:04:45

+1

„An OrderedDict jest DICT że pamięta kolejność że klucze zostały po raz pierwszy wstawiony”. @ZagorulkinDmitry ma rację :) (http://docs.python.org/2/library/collections.html#collections.OrderedDict) 21 maj. 132013-05-21 08:41:45

  0

http: // stackoverflow.com/questions/38987/how-can-i-merge-union-dwa-python-słowniki-w-jednym-wyrażeniu? rq = 1 14 lis. 132013-11-14 13:18:25

44

Nie ma wbudowany w sposób robi to w Pythonie 2. Jeśli to potrzebne, trzeba napisać prepend() metody/funkcji, które działają na OrderedDict wewnętrznych z O (1) złożoność.

dla Pythona 3.2 i później, można użyć metody move_to_end . Metoda przyjmuje argument last wskazujący, czy element zostanie przeniesiony na spód (last=True), czy też górny (last=False) z OrderedDict.

koniec, jeśli chcesz szybki, brudne i powolny rozwiązanie, można po prostu stworzyć nową OrderedDict od podstaw.

Szczegóły dla czterech różnych rozwiązań:


Extend OrderedDict i dodać nową metodę instancji

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

Samodzielna funkcja, która manipuluje obiektami OrderedDict

Ta funkcja robi to samo, akceptując obiekt, klucz i wartość dict. Osobiście preferuje klasy:

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

Używaj OrderedDict.move_to_end() (Pyton> = 3,2)

Python 3.2 introduced sposób OrderedDict.move_to_end(). Używając go, możemy przenieść istniejący klucz do obu końców słownika w czasie 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')]) 

Jeśli musimy wstawić element i przenieść go na górę, wszystko w jednym kroku, możemy bezpośrednio wykorzystać go do stworzenia prepend() owijkę (nie przedstawione tutaj).


Utwórz nowy OrderedDict - powolny !!!

Jeśli nie chcesz tego zrobić, a wydajność nie jest problemem następnie Najprostszym sposobem jest stworzenie nowego 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 

wyjściowa:

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

  0

Uwaga, jeśli 'c' już istnieje, to nie zaktualizuje starej wartości 21 maj. 132013-05-21 08:03:11

  0

@jamylak Dzięki, naprawiłem to. 21 maj. 132013-05-21 08:42:06

  0

ta odpowiedź jest po prostu błędna. możesz, zobacz odpowiedź poniżej. 16 lis. 152015-11-16 09:48:00

+1

@IARI Jeśli chodzi o 'move_to_end', to nie ma żadnego tagu Python 3 na pytanie,' move_to_end' działa tylko w Pythonie 3.2+. Zaktualizuję moją odpowiedź, aby uwzględnić rozwiązanie oparte na Pythonie 3. Bardzo dziękuję za aktualizację! 16 lis. 152015-11-16 11:16:51

  0

@AshwiniChaudhary popraw mnie, jeśli się mylę, ale 'move_to_end' może być symulowany przez usunięcie i odczytanie klucza - więc technicznie nie jest nawet wymagany. 16 lis. 152015-11-16 11:49:13

  0

@IARI Tak, ale w tym przypadku dodajemy go jako pierwszy klucz nie ostatni. 'move_to_end' obejmuje obie części z opcjonalnym argumentem' last'. 16 lis. 152015-11-16 11:51:22

  0

"Usunięcie wpisu i ponowne wstawienie go spowoduje przeniesienie go do końca." - z docs.python.org (dla dowolnej wersji python) 16 lis. 152015-11-16 11:53:41

  0

@IARI Pytanie dotyczy dodania go jako ** top ** nie na końcu. 16 lis. 152015-11-16 11:55:38

  0

Dobra, przepraszam - ale można to zasymulować, przenosząc wszystko do końca. Chodzi mi o to, że jeśli znajdziesz to pytanie i nie dbasz o wydajność, ale chcesz po prostu przenieść coś na wierzch i zachować stare referencje i przeczytać to pytanie, odpowiedź "nie możesz" po prostu nie odpowiada wydaje mi się poprawne. Przepraszam, że wcześniej byłam raczej nieprecyzyjna. 16 lis. 152015-11-16 11:58:25

  0

@IARI Sprawdź aktualizację. 16 lis. 152015-11-16 12:45:58

  0

Nie rozumiem odwołania ifilterfalse (patrz .__ zawiera ___, iterowalne), podczas gdy możesz po prostu zrobić (x nie jest widoczny dla x w iteracji) lub zdarzenie tylko jeśli w pętli for. Jest krótszy, łatwiejszy do odczytania i nie wymaga dodatkowego importu. 23 mar. 162016-03-23 16:39:25

+1

@ m000 Dlaczego 'O (n)'? 11 cze. 162016-06-11 14:02:11

  0

@AshwiniChaudhary Masz rację. Patrząc na kod przedrostka, rzeczywiście jest to O (1). Dzięki, że zauważyłeś moje przeoczenie. Źle zinterpretowałem twoje pierwsze zdanie i założona złożoność była O (n). "Nie ma wbudowanego sposobu, aby to zrobić w Pythonie 2 w O (1) czasie." może być również interpretowane jako sugerujące podstawowe niedociągnięcie w implementacji Pythona 2. Przeformułowałem zdanie, aby było bardziej zrozumiałe. 12 cze. 162016-06-12 15:35:04

  0

@AshwiniChaudhary Ponieważ Python 3 już dodał 'move_to_front', może lepiej jest zaimplementować metodę' move_to_front' zamiast osobnej metody 'prepend'? Dzięki temu twój kod stanie się bardziej przenośny, jeśli będziesz potrzebować obsługi zarówno Pythona 2, jak i Pythona 3 z tej samej bazy kodu. 12 cze. 162016-06-12 15:39:08

+1

Jaki jest powód, dla którego 'dict_setitem = dict .__ setitem__' jest parametrem" przedrostek "? Dlaczego miałbym/powinienem przejść przez innego setera? 11 lip. 162016-07-11 15:32:23

+1

Musi być ** bug ** w 'ordered_dict_prepend' powyżej. Wywołanie 'ordered_dict_prepend (d, 'c', 100)' dwa razy i próba wydrukowania wynikowego dicta (po prostu wpisując 'd' w konsoli Pythona) powoduje, że proces Pythona nadal pobiera pamięć. Testowany przy użyciu Pythona 2.7.10 06 paź. 162016-10-06 09:41:55

  0

@Jir 'dict_setitem = dict .__ setitem__' to mikrooptymalizacja, która ma na celu niedopuszczenie dodatkowych wyszukiwań, w tym przypadku' dict' jest wbudowaną zmienną. 30 gru. 162016-12-30 06:46:28


1

Jeśli potrzebujesz funkcjonalności, której nie ma, po prostu rozszerz klasę o co chcesz:

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) 

Nie strasznie skuteczny, ale działa:

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

0

chciałbym zaproponować dodanie do tej metody prepend() czystym Pythonie recipe lub wyprowadzania podklasy z niego. Kod do tego może być dość skuteczny, ponieważ podstawową strukturą danych do zamawiania jest lista połączona.


10

Musisz wykonać nową instancję z OrderedDict. Jeśli klucze są wyjątkowe:

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

Ale jeśli nie, to uważaj jak tego zachowania mogą lub nie mogą być pożądane dla Ciebie:

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

W python3, metoda items nie zwraca już listy, ale raczej widok, który działa jak zestaw. W tym przypadku musisz wziąć zestaw union, ponieważ łączenie z + nie będzie działać: dict (x.items() | y.items()) 14 lis. 132013-11-14 13:19:46

+1

@ TheDemz Myślałem, że ujednolicenie nie zachowa porządku, co sprawia, że ​​ostateczne zamówienie pozycji w wynikowej 'OrderedDict' unstable? 16 kwi. 152015-04-16 02:28:58

  0

@max Tak, jest niestabilna. Obiekty zwrócone z dict.keys(), dict.values ​​() i dict.items() są nazywane widokami słownikowymi. Są to leniwe sekwencje, które zobaczą zmiany w podstawowym słowniku. Aby zmusić widok słownika, aby stał się pełną listą użycia listy (dyktview). Zobacz Obiekty widoku słownika. https://docs.python.org/3.4/library/stdtypes.html#dictionary-view-objects 20 kwi. 152015-04-20 21:46:14


13

Właśnie napisał podklasę OrderedDict w projekcie kopalni podobny cel. Here's the gist.

Operacje wstawiania to także stały czas O(1) (nie wymagają one odbudowania struktury danych), w przeciwieństwie do większości z tych rozwiązań.

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

Otrzymuję obiekt 'TypeError: '_Link' nie obsługuje indeksowania' podczas używania tego w Pythonie 3.4. 31 lip. 152015-07-31 12:40:40

+1

To również nie działa z pythonem 3.5: AttributeError: obiekt 'ListDict' nie ma atrybutu '_OrderedDict__map' 26 lip. 162016-07-26 11:23:20

  0

To już nie działa, ponieważ 'OrderedDict' został [przepisany w C] (https://bugs.python.org/issue16991) od Pythona 3.5, a ta podklasa popełniła tabu muckingowania z wewnętrznymi (w rzeczywistości odwróciła mangling na nazwę, aby uzyskać dostęp do właściwości __). 27 lip. 162016-07-27 01:58:50


3

Jest to teraz możliwe z move_to_end (klucz, ostatni = 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


6

Jeśli wiesz, że będzie chciał klawisz 'C', ale nie zna wartości , wstaw "c" z fałszywą wartością podczas tworzenia dyktowania.

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

i zmień wartość później.

d1['c'] = 3