Безпечне видалення декількох клавіш зі словника


127

Я знаю, щоб видалити запис "ключ" зі мого словника d, безпечно, ви робите:

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

Однак мені потрібно безпечно видалити кілька записів зі словника. Я думав визначити записи в кортежі, оскільки мені це потрібно буде робити не один раз.

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

Однак мені було цікаво, чи є розумніший спосіб це зробити?


3
Час пошуку зі словника майже O (1) через хешування. Якщо ви не видалите значну частину записів, я не думаю, що ви зробите набагато краще.
ncmathsadist

1
Відповідь @mattbornski здається більш канонічною, а також лаконічною.
Іоанніс Філіппідіс

2
StackOverflow висловили: key in dбільш віщий , ніж d.has_key(key) stackoverflow.com/questions/1323410/has-key-or-in
Майкл Шепер

Якщо ви можете зекономити трохи пам'яті, ви можете зробити for x in set(d) & entities_to_remove: del d[x]. Це, ймовірно, буде більш ефективним лише тоді, коли entities_to_removeвін "великий".
DylanYoung

Відповіді:


56

Чому б не так:

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]

Більш компактну версію надав mattbornski за допомогою dict.pop ()


14
Додайте це для людей, які надходять із пошукової системи. Якщо ключі відомі (коли безпеку не викликає), можна видалити кілька клавіш в одному рядку, як цеdel dict['key1'], dict['key2'], dict['key3']
Tirtha R

Залежно від кількості клавіш, яку ви видаляєте, тест може бути більш ефективним у використанні for key in set(the_dict) & entries:та обходженні key in dict.
DylanYoung

236
d = {'some':'data'}
entriesToRemove = ('any', 'iterable')
for k in entriesToRemove:
    d.pop(k, None)

37
Це. Це вибір розумного Пітоніста. dict.pop()виключає необхідність тестування ключових існувань. Відмінно.
Сесіль Карі

4
Що я того вартий, я думаю, що .pop()це погано і непітонічно, і я вважаю за краще прийняту відповідь над цією.
Арн

5
Зрозуміло, що це непохитне число людей не турбується :) Я не проти додаткової лінії для перевірки існування особисто, і вона значно зручніше читати, якщо ви вже не знаєте про pop (). З іншого боку, якщо ви намагалися зробити це в розумінні або вбудованій лямбда, цей трюк може стати великою підмогою. Я б також сказав, що, на мою думку, важливо зустріти людей там, де вони є. Я не впевнений, що "поганий і непітонічний" збирається дати людям, які читають ці відповіді, практичні вказівки, які вони шукають.
mattbornski

5
Є особливо вагомий привід використовувати це. Якщо додавання додаткового рядка може покращити "читабельність" чи "чіткість", воно також додасть додатковий пошук до словника. Цей метод є еквівалентом видалення дій setdefault. Якщо він реалізований правильно (і я впевнений, що це так), він здійснює лише один пошук у хеш-карті, яка є dict, замість двох.
Божевільний фізик

2
Особисто я б переймався правильністю та обслуговуваністю спочатку та швидкістю, лише якщо це доведеться недостатньо швидко. Різниця в швидкості між цими операціями буде тривіальною, коли її зменшити до рівня програми. Можливо, так швидше, але я очікую, що в реальному використанні ви не помітите і не потурбуєтесь, і якщо ви помітите і подбаєте, вам буде краще подати переписування в чомусь більш виконавському, ніж Python.
mattbornski

89

Використання розумінь Dict

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

де key1 і key2 повинні бути видалені.

У наведеному нижче прикладі потрібно видалити ключі "b" і "c", і вони зберігаються у списку ключів.

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

4
новий словник? осмислення списку? Ви повинні скоригувати відповідь особі, яка задає питання;)
Glaslos

6
Це рішення спричинило серйозне враження від продуктивності, коли змінна, що містить, має додаткове використання в програмі. Іншими словами, диктант, з якого були видалені ключі, набагато ефективніший, ніж новостворений дікт із збереженими елементами.
Апалала

14
заради читабельності пропоную {k: v for k, v in t.items (), якщо k не в [key1, key2]}
Фредерік Базін,

8
Це також має проблеми з продуктивністю, коли список клавіш занадто великий, як і пошук O(n). Вся операція полягає в тому O(mn), де mкількість клавіш у дикті та nкількість клавіш у списку. Я пропоную {key1, key2}замість цього використовувати набір , якщо це можливо.
ldavid

4
До Апалала: чи можете ви допомогти мені зрозуміти, чому є хіт вистави?
Шон

21

рішення використовує mapі filterфункціонує

пітон 2

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

пітон 3

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

Ви отримуєте:

{'c': 3}

Це не працює для мене з 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))).... в python 3.4 функція карти повернути ітератор
Хосе Рікардо Бустос М.

4
або deque(map(...), maxlen=0)уникати складання списку значень None; перший імпорт зfrom collections import deque
Джейсон

19

Якщо вам також потрібно було отримати значення для ключів, які ви виймаєте, це буде досить непоганим способом:

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

Звичайно, ви все одно можете це зробити лише для видалення ключів d, але ви будете зайво створювати список значень із розумінням списку. Також трохи незрозуміло використовувати розуміння списку лише для побічного ефекту функції.


3
Або якщо ви хотіли зберегти видалені записи як словник: valuesRemoved = dict((k, d.pop(k, None)) for k in entitiesToRemove) тощо.
kindall

Ви можете залишити завдання змінній. У той чи інший спосіб це найкоротше і найпітонічніше рішення, і його слід позначати як основну відповідь ІМХО.
Герхард Хагерер

12

Знайшов рішення з popіmap

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

Вихід цього:

{'d': 'valueD'}

Я відповів на це питання так пізно лише тому, що думаю, що це допоможе у майбутньому, якщо хтось шукатиме те саме. І це може допомогти.

Оновлення

Вищевказаний код призведе до помилки, якщо ключ не існує в диктаті.

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)

вихід:

DICTIONARY = {'b': 'valueB', 'd': 'valueD'}

1
Ця відповідь призведе до виключення, якщо жодного ключа keysне існує в d- вам доведеться відфільтрувати його першим.
ingofreyer

@ingofreyer оновив код для обробки винятків. Дякуємо, що знайшли цю проблему. Я думаю, зараз це спрацює. :)
Shubham Srivastava

Дякую, це має допомогти всім знайти цю відповідь :-)
ingofreyer

Створення списку як побічного продукту використання карти робить це досить повільно, насправді краще перетворити його на цикл.
Чарлі Кларк

4

Я не маю проблем з жодною із існуючих відповідей, але я був здивований, що не знайшов цього рішення:

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}

Примітка. Я натрапив на це питання, що йде звідси . І моя відповідь пов’язана з цією відповіддю .


3

Чому ні:

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

Я не знаю, що ви маєте на увазі під "розумнішим способом". Звичайно, є й інші способи, можливо, із розумінням словника:

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

2

в лінію

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'}

2

Деякі тести на терміни для cpython 3 показують, що простий цикл - це найшвидший спосіб, і він досить читабельний. Додавання функції також не спричиняє значних витрат:

Результат timeit (10k ітерацій):

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

Для невеликих ітерацій зробити це "вбудованим" було трохи швидше, через накладні витрати виклику функції. Але del_allце захищене від ворсів, багаторазове використання та швидше, ніж усі конструкції з розуміння та відображення пітона.


0

Я думаю, що використання того факту, що ключі можна розглядати як набір - це найприємніший спосіб, якщо ти на python 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))

Приклад:

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

0

Було б непогано мати повну підтримку встановлених методів для словників (а не нечестивий безлад, який ми отримуємо з Python 3.9), щоб ви могли просто «видалити» набір ключів. Однак, поки це не так, і у вас є великий словник з потенційно великою кількістю клавіш, який потрібно видалити, ви, можливо, захочете дізнатися про продуктивність. Отже, я створив якийсь код, який створює щось досить велике для змістовного зіставлення: матриця 100 000 х 1000, тобто загалом 10 000,00 елементів.

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 мільйонів предметів і більше не є незвичним для деяких налаштувань. Порівнюючи два способи на моїй локальній машині, я бачу незначне поліпшення при використанні mapі pop, імовірно, через меншу кількість викликів функцій, але обидва займають близько 2,5 секунд на моїй машині. Але це бледнее порівняно з часом, необхідним для створення словника в першу чергу (55s), або включаючи перевірки всередині циклу. Якщо це можливо, тоді найкраще створити набір, який є перетином клавіш словника та вашого фільтра:

keys = cells.keys() & keys

Підсумовуючи це: delвже сильно оптимізовано, тому не хвилюйтеся використовувати його.


-1

Я запізнююсь на цю дискусію, але для когось іншого. Рішенням може бути створення списку ключів як такого.

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

Потім використовуйте pop () у розумінні списку або для циклу, щоб перебирати клавіші та вводити їх по черзі як такі.

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

'N / a' - у випадку, якщо ключа не існує, потрібно повернути значення за замовчуванням.


8
new_dictionaryвиглядає жахливо, як список;)
DylanYoung
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.