Mehrere Schlüssel sicher aus einem Wörterbuch entfernen

127

Ich weiß, dass ich einen Eintrag, 'Schlüssel' d, sicher aus meinem Wörterbuch entfernen muss.

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

Ich muss jedoch mehrere Einträge sicher aus dem Wörterbuch entfernen. Ich habe darüber nachgedacht, die Einträge in einem Tupel zu definieren, da ich dies mehr als einmal tun muss.

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

Ich habe mich jedoch gefragt, ob es einen intelligenteren Weg gibt, dies zu tun.

dublintech
quelle
3
Die Abrufzeit aus einem Wörterbuch beträgt aufgrund von Hashing fast O (1). Wenn Sie nicht einen erheblichen Teil der Einträge entfernen, werden Sie es wahrscheinlich nicht viel besser machen.
ncmathsadist
1
Die Antwort von @mattbornski erscheint kanonischer und prägnanter.
Ioannis Filippidis
2
StackOverflow hat gesprochen: key in dist mehr pythonisch als d.has_key(key) stackoverflow.com/questions/1323410/has-key-or-in
Michael Scheper
Wenn Sie etwas Speicherplatz sparen können, können Sie dies tun for x in set(d) & entities_to_remove: del d[x]. Dies wird wahrscheinlich nur dann effizienter sein, wenn entities_to_removees "groß" ist.
DylanYoung

Antworten:

56

Warum nicht so:

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]

Eine kompaktere Version wurde von mattbornski mit dict.pop () bereitgestellt

Glaslos
quelle
14
Hinzufügen für Personen, die von einer Suchmaschine kommen. Wenn Schlüssel bekannt sind (wenn Sicherheit kein Problem ist), können mehrere Schlüssel in einer Zeile wie folgt gelöscht werdendel dict['key1'], dict['key2'], dict['key3']
Tirtha R
Abhängig von der Anzahl der zu löschenden Schlüssel ist es möglicherweise effizienter, for key in set(the_dict) & entries:den key in dictTest zu verwenden und zu umgehen .
DylanYoung
234
d = {'some':'data'}
entriesToRemove = ('any', 'iterable')
for k in entriesToRemove:
    d.pop(k, None)
Mattbornski
quelle
37
Dies. Dies ist die Wahl des cleveren Pythonista. dict.pop()macht das Testen der Schlüsselexistenz überflüssig. Ausgezeichnet.
Cecil Curry
4
Für das, was es wert ist, halte ich es für .pop()schlecht und unpythonisch und würde die akzeptierte Antwort dieser vorziehen.
Arne
5
Eine erstaunliche Anzahl von Menschen scheint davon nicht betroffen zu sein :) Ich habe nichts gegen die zusätzliche Zeile für die persönliche Überprüfung der Existenz, und sie ist wesentlich besser lesbar, es sei denn, Sie wissen bereits über pop () Bescheid. Auf der anderen Seite könnte dieser Trick eine große Hilfe sein, wenn Sie versuchen, dies in einem Verständnis oder Inline-Lambda zu tun. Ich würde auch sagen, dass es meiner Meinung nach wichtig ist, Menschen dort zu treffen, wo sie sind. Ich bin nicht sicher, ob "schlecht und unpythonisch" den Leuten, die diese Antworten lesen, die praktische Anleitung geben wird, nach der sie suchen.
Mattbornski
5
Es gibt einen besonders guten Grund, dies zu nutzen. Während das Hinzufügen einer zusätzlichen Zeile die "Lesbarkeit" oder "Klarheit" verbessern kann, wird dem Wörterbuch auch eine zusätzliche Suche hinzugefügt. Diese Methode entspricht dem Entfernen setdefault. Bei korrekter Implementierung (und ich bin mir sicher, dass dies der Fall ist) wird nur eine Suche in der Hash-Map durchgeführt, dh die dictanstelle von zwei.
Verrückter Physiker
2
Persönlich würde ich mich zuerst mit Korrektheit und Wartbarkeit befassen und nur dann mit Geschwindigkeit, wenn sich herausstellt, dass sie nicht ausreichend schnell ist. Der Geschwindigkeitsunterschied zwischen diesen Vorgängen wird trivial sein, wenn auf die Anwendungsebene herausgezoomt wird. Es mag sein, dass man schneller ist, aber ich gehe davon aus, dass Sie im realen Gebrauch weder etwas bemerken noch sich darum kümmern werden, und wenn Sie es bemerken und sich darum kümmern, ist es besser, wenn Sie in etwas Performanterem als Python umschreiben.
Mattbornski
89

Verwenden von Diktatverständnissen

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

wo Schlüssel1 und Schlüssel2 entfernt werden sollen.

Im folgenden Beispiel müssen die Schlüssel "b" und "c" entfernt und in einer Schlüsselliste gespeichert werden.

>>> 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
quelle
4
neues Wörterbuch? Listenverständnis? Sie sollten die Antwort an die Person anpassen, die die Frage stellt;)
Glaslos
6
Diese Lösung hat einen schwerwiegenden Leistungseinbruch, wenn die Variable, die die enthält, im Programm weiter verwendet wird. Mit anderen Worten, ein Diktat, aus dem Schlüssel gelöscht wurden, ist viel effizienter als ein neu erstelltes Diktat mit den beibehaltenen Elementen.
Apalala
14
Aus Gründen der Lesbarkeit schlage ich {k: v für k, v in t.items () vor, wenn k nicht in [key1, key2]}
Frederic Bazin
8
Dies hat auch Leistungsprobleme, wenn die Liste der Schlüssel zu groß ist, wie bei Suchvorgängen erforderlich O(n). Die gesamte Operation besteht darin O(mn), wo msich die Anzahl der Schlüssel im Diktat und ndie Anzahl der Schlüssel in der Liste befinden. Ich schlage vor {key1, key2}, wenn möglich stattdessen ein Set zu verwenden.
ldavid
4
Zu Apalala: Können Sie mir helfen zu verstehen, warum es einen Performance-Hit gibt?
Sean
21

Eine Lösung verwendet mapundfilter funktioniert

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)

du erhältst:

{'c': 3}
Jose Ricardo Bustos M.
quelle
Dies funktioniert nicht für mich mit Python 3.4:>>> 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}
Risadinha
@ Risadinha list(map(d.__delitem__,filter(d.__contains__,l))).... in Python 3.4 Map-Funktion einen Iterator zurückgeben
Jose Ricardo Bustos M.
4
oder um deque(map(...), maxlen=0)zu vermeiden, dass eine Liste mit None-Werten erstellt wird; erster Import mitfrom collections import deque
Jason
19

Wenn Sie auch die Werte für die Schlüssel abrufen müssen, die Sie entfernen, ist dies ein guter Weg, dies zu tun:

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

Sie könnten dies natürlich immer noch nur zum Entfernen der Schlüssel aus tun d, aber Sie würden unnötigerweise die Werteliste mit dem Listenverständnis erstellen. Es ist auch ein wenig unklar, ein Listenverständnis nur für die Nebenwirkung der Funktion zu verwenden.

Andrew Clark
quelle
3
Oder wenn Sie die gelöschten Einträge als Wörterbuch behalten möchten:valuesRemoved = dict((k, d.pop(k, None)) for k in entitiesToRemove) und so weiter.
Kindall
Sie können die Zuordnung zu einer Variablen weglassen. Auf diese oder jene Weise ist es die kürzeste und pythonischste Lösung und sollte meiner Meinung nach als die richtige Antwort markiert werden.
Gerhard Hagerer
12

Mit popund eine Lösung gefundenmap

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

Die Ausgabe davon:

{'d': 'valueD'}

Ich habe diese Frage so spät beantwortet, nur weil ich denke, dass es in Zukunft helfen wird, wenn jemand dasselbe sucht. Und das könnte helfen.

Aktualisieren

Der obige Code löst einen Fehler aus, wenn im Diktat kein Schlüssel vorhanden ist.

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)

Ausgabe:

DICTIONARY = {'b': 'valueB', 'd': 'valueD'}
Shubham Srivastava
quelle
1
Diese Antwort keyslöst eine Ausnahme aus, wenn kein Schlüssel in vorhanden ist d- Sie müssten diesen zuerst filtern.
Ingofreyer
@ingofreyer hat den Code für die Ausnahmebehandlung aktualisiert. Vielen Dank, dass Sie dieses Problem gefunden haben. Ich denke jetzt wird es funktionieren. :)
Shubham Srivastava
Danke, das sollte allen helfen, diese Antwort zu finden :-)
ingofreyer
Das Erstellen einer Liste als Nebenprodukt der Verwendung von Map macht dies ziemlich langsam. Es ist eigentlich besser, eine Schleife darüber zu erstellen.
Charlie Clark
4

Ich habe kein Problem mit einer der vorhandenen Antworten, aber ich war überrascht, diese Lösung nicht zu finden:

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}

Hinweis: Ich bin auf diese Frage gestoßen, die von hier kommt . Und meine Antwort bezieht sich auf diese Antwort .

Doug R.
quelle
3

Warum nicht:

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

Ich weiß nicht, was du mit "klügerer Weg" meinst. Sicher gibt es andere Möglichkeiten, vielleicht mit Wörterbuchverständnis:

entriestoremove = (2,5,1)
newdict = {x for x in d if x not in entriestoremove}
L3viathan
quelle
2

in der Reihe

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
quelle
2

Einige Timing-Tests für cpython 3 zeigen, dass eine einfache for-Schleife der schnellste Weg ist und gut lesbar ist. Das Hinzufügen einer Funktion verursacht auch nicht viel Overhead:

Timeit-Ergebnisse (10.000 Iterationen):

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

Bei kleinen Iterationen war dies aufgrund des Overheads des Funktionsaufrufs etwas schneller. Aber del_allist fusselsicher, wiederverwendbar und schneller als alle Python-Verständnis- und Mapping-Konstrukte.

Erik Aronesty
quelle
0

Ich denke, die Tatsache, dass die Schlüssel als Set behandelt werden können, ist der schönste Weg, wenn Sie mit Python 3 arbeiten:

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

Beispiel:

>>> remove_keys({'k1': 1, 'k3': 3}, ['k1', 'k2'])
{'k3': 3}
Reut Sharabani
quelle
0

Es wäre schön, volle Unterstützung für festgelegte Methoden für Wörterbücher zu haben (und nicht für das unheilige Durcheinander, das wir mit Python 3.9 bekommen), damit Sie einfach einen Satz von Schlüsseln "entfernen" können. Solange dies jedoch nicht der Fall ist und Sie ein großes Wörterbuch mit möglicherweise einer großen Anzahl von Schlüsseln zum Entfernen haben, möchten Sie möglicherweise Informationen zur Leistung erhalten. Also habe ich einen Code erstellt, der etwas erstellt, das groß genug für aussagekräftige Vergleiche ist: eine 100.000 x 1000-Matrix, also insgesamt 10.000,00 Elemente.

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 Millionen Artikel oder mehr sind in einigen Einstellungen nicht ungewöhnlich. Beim Vergleich der beiden Methoden auf meinem lokalen Computer sehe ich eine leichte Verbesserung bei der Verwendung mapund popvermutlich aufgrund weniger Funktionsaufrufe, aber beide dauern auf meinem Computer ungefähr 2,5 Sekunden. Dies verblasst jedoch im Vergleich zu der Zeit, die erforderlich ist, um das Wörterbuch überhaupt zu erstellen (55 Sekunden) oder Überprüfungen in die Schleife einzubeziehen. Wenn dies wahrscheinlich ist, erstellen Sie am besten eine Menge, die eine Schnittstelle zwischen den Wörterbuchschlüsseln und Ihrem Filter darstellt:

keys = cells.keys() & keys

Zusammenfassend: delist bereits stark optimiert, machen Sie sich also keine Sorgen über die Verwendung.

Charlie Clark
quelle
-1

Ich bin zu spät zu dieser Diskussion, aber für alle anderen. Eine Lösung kann darin bestehen, eine Liste von Schlüsseln als solche zu erstellen.

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

Verwenden Sie dann pop () in einem Listenverständnis oder eine for-Schleife, um die Tasten zu durchlaufen und einzeln als solche zu popen.

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

Das 'n / a' ist, falls der Schlüssel nicht existiert, muss ein Standardwert zurückgegeben werden.

Terrance DeJesus
quelle
8
new_dictionarysieht sehr nach einer Liste aus;)
DylanYoung