Bezpieczne usuwanie wielu kluczy ze słownika

129

Wiem, aby bezpiecznie usunąć wpis, „klucz” z mojego słownika d, robisz to:

if d.has_key('key'):
    del d['key']

Jednak muszę bezpiecznie usunąć wiele wpisów ze słownika. Myślałem o zdefiniowaniu wpisów w krotce, ponieważ będę musiał to zrobić więcej niż raz.

entitiesToREmove = ('a', 'b', 'c')
for x in entitiesToRemove:
    if d.has_key(x):
        del d[x]

Zastanawiałem się jednak, czy istnieje mądrzejszy sposób, aby to zrobić?

dublintech
źródło
3
Czas pobierania ze słownika wynosi prawie 0 (1) z powodu haszowania. Jeśli nie usuniesz znacznej części wpisów, nie sądzę, że poradzisz sobie znacznie lepiej.
ncmathsadist
1
Odpowiedź @mattbornski wydaje się bardziej kanoniczna, a także zwięzła.
Ioannis Filippidis,
2
StackOverflow powiedział: key in djest bardziej Pythonic niż d.has_key(key) stackoverflow.com/questions/1323410/has-key-or-in
Michael Scheper
Jeśli możesz zaoszczędzić trochę pamięci, możesz to zrobić for x in set(d) & entities_to_remove: del d[x]. Będzie to prawdopodobnie bardziej wydajne tylko wtedy, gdy entities_to_removebędzie „duże”.
DylanYoung

Odpowiedzi:

57

Dlaczego nie tak:

entries = ('a', 'b', 'c')
the_dict = {'b': 'foo'}

def entries_to_remove(entries, the_dict):
    for key in entries:
        if key in the_dict:
            del the_dict[key]

Bardziej zwarta wersja została dostarczona przez mattbornskiego za pomocą dict.pop ()

Glaslos
źródło
14
Dodanie tego dla osób pochodzących z wyszukiwarki. Jeśli klucze są znane (gdy bezpieczeństwo nie jest problemem), wiele kluczy można usunąć w jednej linii, tak jak todel dict['key1'], dict['key2'], dict['key3']
Tirtha R
W zależności od liczby usuwanych kluczy efektywniejsze może być użycie for key in set(the_dict) & entries:i obejście key in dicttestu.
DylanMłody
236
d = {'some':'data'}
entriesToRemove = ('any', 'iterable')
for k in entriesToRemove:
    d.pop(k, None)
mattbornski
źródło
38
To. To jest wybór sprytnego Pythonisty. dict.pop()eliminuje potrzebę testowania obecności kluczy. Doskonały.
Cecil Curry
4
Uważam, że .pop()jest to zła i niepytoniczna, i wolałabym zaakceptowaną odpowiedź od tej.
Arne
5
Zdumiewająca liczba osób wydaje się tym nie przejmować :) Nie przeszkadza mi dodatkowa linijka do osobistego sprawdzania istnienia i jest znacznie bardziej czytelna, chyba że znasz już pop (). Z drugiej strony, jeśli próbujesz to zrobić ze zrozumieniem lub wbudowaną lambdą, ta sztuczka może być bardzo pomocna. Powiedziałbym też, że moim zdaniem ważne jest, aby spotykać ludzi tam, gdzie są. Nie jestem pewien, czy „zły i niepytoniczny” da ludziom, którzy czytają te odpowiedzi, praktyczne wskazówki, których szukają.
mattbornski
5
Jest szczególnie dobry powód, aby to wykorzystać. Dodanie dodatkowej linii może poprawić „czytelność” lub „przejrzystość”, ale dodaje również dodatkowe wyszukiwanie do słownika. Ta metoda jest odpowiednikiem funkcji doing setdefault. Jeśli zaimplementowano poprawnie (i jestem pewien, że tak), wykonuje tylko jedno wyszukiwanie w mapie skrótów, czyli dictzamiast dwóch.
Mad Physicist,
2
Osobiście zajmowałbym się najpierw poprawnością i łatwością konserwacji, a szybkością tylko wtedy, gdy okaże się, że jest niewystarczająco szybki. Różnica prędkości między tymi operacjami będzie trywialna po pomniejszeniu do poziomu aplikacji. Może się zdarzyć, że jeden jest szybszy, ale spodziewam się, że w prawdziwym świecie nie zauważysz ani nie przejmujesz się, a jeśli zauważysz i przejmujesz się, lepiej będzie ci przepisać coś bardziej wydajnego niż Python.
mattbornski
90

Korzystanie ze zrozumiałych słów

final_dict = {key: t[key] for key in t if key not in [key1, key2]}

gdzie klucz1 i klucz2 mają zostać usunięte.

W poniższym przykładzie klucze „b” i „c” mają zostać usunięte i są przechowywane na liście kluczy.

>>> a
{'a': 1, 'c': 3, 'b': 2, 'd': 4}
>>> keys = ["b", "c"]
>>> print {key: a[key] for key in a if key not in keys}
{'a': 1, 'd': 4}
>>> 
Abhijeet Rastogi
źródło
4
nowy słownik? rozumienie listy? Odpowiedź należy dopasować do osoby zadającej pytanie;)
Glaslos
6
To rozwiązanie ma poważny spadek wydajności, gdy zmienna trzymająca wartość ma dalsze zastosowanie w programie. Innymi słowy, dyktando, z którego usunięto klucze, jest znacznie bardziej wydajne niż nowo utworzony dykt z zachowanymi elementami.
Apalala
14
ze względu na czytelność proponuję {k: v dla k, v in t.items () if k nie w [klucz1, klucz2]}
Frederic Bazin
8
Ma to również problemy z wydajnością, gdy lista kluczy jest zbyt długa, ponieważ wymagają wyszukiwania O(n). Cała operacja polega na tym O(mn), że mjest to liczba kluczy w dyktandzie i nliczba kluczy na liście. {key1, key2}Jeśli to możliwe, sugeruję użycie zestawu .
ldavid
4
Do Apalali: czy możesz mi pomóc zrozumieć, dlaczego istnieje hit wydajnościowy?
Sean
21

rozwiązaniem jest użycie mapifilter funkcjonuje

python 2

d={"a":1,"b":2,"c":3}
l=("a","b","d")
map(d.__delitem__, filter(d.__contains__,l))
print(d)

python 3

d={"a":1,"b":2,"c":3}
l=("a","b","d")
list(map(d.__delitem__, filter(d.__contains__,l)))
print(d)

dostajesz:

{'c': 3}
Jose Ricardo Bustos M.
źródło
To nie działa dla mnie z >>> d={"a":1,"b":2,"c":3} >>> l=("a","b","d") >>> map(d.__delitem__, filter(d.__contains__,l)) <map object at 0x10579b9e8> >>> print(d) {'a': 1, 'b': 2, 'c': 3}
Pythonem
@Risadinha list(map(d.__delitem__,filter(d.__contains__,l))).... w funkcji mapy Python 3.4 zwraca iterator
Jose Ricardo Bustos M.,
4
lub deque(map(...), maxlen=0)aby uniknąć tworzenia listy wartości Brak; pierwszy import zfrom collections import deque
Jason
19

Jeśli potrzebujesz również pobrać wartości usuwanych kluczy, byłby to całkiem dobry sposób:

valuesRemoved = [d.pop(k, None) for k in entitiesToRemove]

Oczywiście możesz to zrobić tylko po to, aby usunąć klucze z d, ale niepotrzebnie utworzyłbyś listę wartości za pomocą funkcji list. Trochę niejasne jest również użycie funkcji rozumienia listy tylko dla efektu ubocznego funkcji.

Andrew Clark
źródło
3
Lub jeśli chcesz zachować usunięte wpisy jako słownik: valuesRemoved = dict((k, d.pop(k, None)) for k in entitiesToRemove) i tak dalej.
kindall
Możesz zostawić przypisanie do zmiennej. W ten czy inny sposób jest to najkrótsze i najbardziej pytoniczne rozwiązanie i powinno być oznaczone jako poprawna odpowiedź IMHO.
Gerhard Hagerer
12

Znalazłem rozwiązanie za pomocą popimap

d = {'a': 'valueA', 'b': 'valueB', 'c': 'valueC', 'd': 'valueD'}
keys = ['a', 'b', 'c']
list(map(d.pop, keys))
print(d)

Wynik tego:

{'d': 'valueD'}

Odpowiedziałem na to pytanie tak późno, ponieważ myślę, że w przyszłości pomoże to, jeśli ktoś będzie szukał tego samego. I to może pomóc.

Aktualizacja

Powyższy kod spowoduje wyświetlenie błędu, jeśli klucz nie istnieje w dict.

DICTIONARY = {'a': 'valueA', 'b': 'valueB', 'c': 'valueC', 'd': 'valueD'}
keys = ['a', 'l', 'c']

def remove_keys(key):
    try:
        DICTIONARY.pop(key, None)
    except:
        pass  # or do any action

list(map(remove_key, keys))
print(DICTIONARY)

wynik:

DICTIONARY = {'b': 'valueB', 'd': 'valueD'}
Shubham Srivastava
źródło
1
Ta odpowiedź spowoduje zgłoszenie wyjątku, jeśli jakikolwiek klucz keysnie istnieje d- najpierw musiałbyś go odfiltrować.
ingofreyer
@ingofreyer zaktualizował kod obsługi wyjątków. Dziękuję za znalezienie tego problemu. Myślę, że teraz to zadziała. :)
Shubham Srivastava
Dzięki, to powinno pomóc wszystkim znaleźć tę odpowiedź :-)
ingofreyer
Tworzenie listy jako produktu ubocznego używania mapy sprawia, że ​​jest to dość wolne, właściwie lepiej jest ją zapętlić.
Charlie Clark
4

Nie mam problemu z żadną z istniejących odpowiedzi, ale zdziwiłem się, że nie znalazłem tego rozwiązania:

keys_to_remove = ['a', 'b', 'c']
my_dict = {k: v for k, v in zip("a b c d e f g".split(' '), [0, 1, 2, 3, 4, 5, 6])}

for k in keys_to_remove:
    try:
        del my_dict[k]
    except KeyError:
        pass

assert my_dict == {'d': 3, 'e': 4, 'f': 5, 'g': 6}

Uwaga: natknąłem się na to pytanie, przychodząc stąd . A moja odpowiedź jest związana z tą odpowiedzią .

Doug R.
źródło
3

Dlaczego nie:

entriestoremove = (2,5,1)
for e in entriestoremove:
    if d.has_key(e):
        del d[e]

Nie wiem, co masz na myśli mówiąc „mądrzejszy sposób”. Z pewnością istnieją inne sposoby, być może ze słownikiem:

entriestoremove = (2,5,1)
newdict = {x for x in d if x not in entriestoremove}
L3viathan
źródło
2

inline

import functools

#: not key(c) in d
d = {"a": "avalue", "b": "bvalue", "d": "dvalue"}

entitiesToREmove = ('a', 'b', 'c')

#: python2
map(lambda x: functools.partial(d.pop, x, None)(), entitiesToREmove)

#: python3

list(map(lambda x: functools.partial(d.pop, x, None)(), entitiesToREmove))

print(d)
# output: {'d': 'dvalue'}
chuang wang
źródło
2

Niektóre testy czasowe dla cpythona 3 pokazują, że prosta pętla for jest najszybszym sposobem i jest całkiem czytelna. Dodanie funkcji również nie powoduje dużego obciążenia:

wyniki timeit (10 tys. iteracji):

  • all(x.pop(v) for v in r) # 0.85
  • all(map(x.pop, r)) # 0.60
  • list(map(x.pop, r)) # 0.70
  • all(map(x.__delitem__, r)) # 0.44
  • del_all(x, r) # 0.40
  • <inline for loop>(x, r) # 0.35
def del_all(mapping, to_remove):
      """Remove list of elements from mapping."""
      for key in to_remove:
          del mapping[key]

W przypadku małych iteracji robienie tego „inline” było nieco szybsze z powodu narzutu wywołania funkcji. Ale del_alljest bezpieczny, wielokrotnego użytku i szybszy niż wszystkie konstrukcje rozumienia i mapowania Pythona.

Erik Aronesty
źródło
0

Myślę, że użycie faktu, że klucze można traktować jako zestaw, jest najmilszym sposobem, jeśli korzystasz z Pythona 3:

def remove_keys(d, keys):
    to_remove = set(keys)
    filtered_keys = d.keys() - to_remove
    filtered_values = map(d.get, filtered_keys)
    return dict(zip(filtered_keys, filtered_values))

Przykład:

>>> remove_keys({'k1': 1, 'k3': 3}, ['k1', 'k2'])
{'k3': 3}
Reut Sharabani
źródło
0

Byłoby miło mieć pełne wsparcie dla metod ustawiania dla słowników (a nie przeklętego bałaganu, który dostajemy w Pythonie 3.9), abyś mógł po prostu „usunąć” zestaw kluczy. Jeśli jednak tak nie jest i masz duży słownik z potencjalnie dużą liczbą kluczy do usunięcia, możesz chcieć wiedzieć o wydajności. Stworzyłem więc kod, który tworzy coś wystarczająco dużego do znaczących porównań: macierz 100 000 x 1000, czyli łącznie 10 000,00 elementów.

from itertools import product
from time import perf_counter

# make a complete worksheet 100000 * 1000
start = perf_counter()
prod = product(range(1, 100000), range(1, 1000))
cells = {(x,y):x for x,y in prod}
print(len(cells))

print(f"Create time {perf_counter()-start:.2f}s")
clock = perf_counter()
# remove everything above row 50,000

keys = product(range(50000, 100000), range(1, 100))

# for x,y in keys:
#     del cells[x, y]

for n in map(cells.pop, keys):
    pass

print(len(cells))
stop = perf_counter()
print(f"Removal time {stop-clock:.2f}s")

10 milionów lub więcej elementów nie jest niczym niezwykłym w niektórych sytuacjach. Porównując dwie metody na moim komputerze lokalnym, widzę niewielką poprawę podczas używania mapi popprawdopodobnie z powodu mniejszej liczby wywołań funkcji, ale obie zajmują około 2,5 sekundy na moim komputerze. Ale to blednie w porównaniu z czasem potrzebnym do utworzenia słownika w pierwszej kolejności (55 s) lub z uwzględnieniem sprawdzeń w pętli. Jeśli jest to prawdopodobne, najlepiej jest utworzyć zestaw będący przecięciem kluczy słownika i filtru:

keys = cells.keys() & keys

Podsumowując: deljest już mocno zoptymalizowany, więc nie martw się o jego używanie.

Charlie Clark
źródło
-1

Spóźniłem się na tę dyskusję, ale dla kogokolwiek innego. Rozwiązaniem może być utworzenie listy kluczy jako takiej.

k = ['a','b','c','d']

Następnie użyj pop () w składaniu listowym lub pętli for, aby iterować po klawiszach i wyskakiwać pojedynczo.

new_dictionary = [dictionary.pop(x, 'n/a') for x in k]

Wartość „n / a” oznacza, że ​​klucz nie istnieje, należy zwrócić wartość domyślną.

Terrance DeJesus
źródło
8
new_dictionarywygląda strasznie jak lista;)
DylanYoung