Bir sözlükten birden fazla anahtarı güvenle kaldırmak


128

Sözlüğümden bir girişi, 'anahtarı' kaldırmayı biliyorum d, güvenli bir şekilde şunları yaparsınız:

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

Ancak, sözlükten birden fazla girişi güvenli bir şekilde kaldırmam gerekiyor. Bunu bir kereden fazla yapmam gerekeceği için girişleri bir demet içinde tanımlamayı düşünüyordum.

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

Ancak, bunu yapmanın daha akıllıca bir yolu olup olmadığını merak ediyordum.


3
Bir sözlükten erişim süresi, karma oluşturma nedeniyle neredeyse O (1) 'dir. Girişlerin önemli bir bölümünü kaldırmadığınız sürece, daha iyisini yapacağınızı düşünmüyorum.
ncmathsadist

1
@Mattbornski'nin cevabı daha kanonik ve aynı zamanda kısa ve öz görünüyor.
Ioannis Filippidis

2
Konuşulan StackOverflow Allâh: key in ddaha pythonic daha d.has_key(key) stackoverflow.com/questions/1323410/has-key-or-in
Michael Scheper

Biraz bellek ayırabiliyorsanız, yapabilirsiniz for x in set(d) & entities_to_remove: del d[x]. Bu muhtemelen sadece entities_to_remove"büyük" ise daha verimli olacaktır .
DylanYoung

Yanıtlar:


56

Neden böyle değil:

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 tarafından dict.pop () kullanılarak daha kompakt bir sürüm sağlandı


14
Bunu bir arama motorundan gelen insanlar için eklemek. Anahtarlar biliniyorsa (güvenlik sorun olmadığında), tek bir satırda birden çok anahtar silinebilirdel dict['key1'], dict['key2'], dict['key3']
Tirtha R

Sildiğiniz anahtarların sayısına bağlı olarak for key in set(the_dict) & entries:, key in dicttesti kullanmak ve atlamak daha verimli olabilir .
DylanYoung

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

38
Bu. Bu akıllı Pythonista'nın seçimidir. dict.pop()anahtar varlık testi ihtiyacını ortadan kaldırır. Mükemmel.
Cecil Curry

4
.pop()Değeri ne olursa olsun, kötü ve ruhani olmadığını düşünüyorum ve kabul edilen yanıtı buna tercih ederim.
Arne

5
Şaşırtıcı sayıda insan bundan rahatsız görünmüyor :) Varoluşu şahsen kontrol etmek için ekstra satıra aldırmıyorum ve pop () hakkında zaten bilginiz yoksa çok daha okunabilir. Öte yandan, bunu bir anlayışta veya satır içi lambda olarak yapmaya çalışıyorsanız, bu numara büyük bir yardımcı olabilir. Ayrıca bence, bulundukları yerde insanlarla tanışmanın önemli olduğunu da söyleyebilirim. "Kötü ve ruhsuz" un bu cevapları okuyan insanlara aradıkları pratik rehberliği sağlayacağından emin değilim.
mattbornski

5
Bunu kullanmak için özellikle iyi bir neden var. Fazladan bir satır eklemek "okunabilirliği" veya "netliği" artırabilirken, sözlüğe fazladan bir arama da ekler. Bu yöntem, yapmanın kaldırmaya eşdeğerdir setdefault. Doğru uygulanırsa (ve eminim öyle), hash haritasına dictiki yerine yalnızca bir arama yapar .
Mad Physicist

2
Şahsen ben öncelikle doğruluk ve sürdürülebilirlikle ilgilenirim ve ancak yeterince hızlı olmadığı kanıtlanırsa hızla ilgilenirim. Bu işlemler arasındaki hız farkı, uygulama düzeyine uzaklaştırıldığında önemsiz olacaktır. Birinin daha hızlı olması söz konusu olabilir, ancak gerçek dünya kullanımında fark etmeyeceğinizi veya umursamayacağınızı umuyorum ve eğer fark eder ve önemserseniz, Python'dan daha performanslı bir şekilde yeniden yazma konusunda daha iyi hizmet alırsınız.
mattbornski

90

Dikte Anlamalarını Kullanma

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

burada key1 ve key2 kaldırılacaktır.

Aşağıdaki örnekte, "b" ve "c" tuşları kaldırılacak ve bir anahtarlar listesinde tutulacaktır.

>>> 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
yeni sözlük? liste anlayışı? Cevabı soruyu soran kişiye göre ayarlamalısın;)
Glaslos

6
Bu çözüm, 'yi tutan değişken programda daha fazla kullanıldığında ciddi bir performans darbesine sahiptir. Başka bir deyişle, anahtarların silindiği bir dikt, tutulan öğelerle yeni oluşturulan bir dikteden çok daha etkilidir.
Apalala

14
okunabilirlik uğruna, öneriyorum {k: v for k, v in t.items () if k not in [key1, key2]}
Frederic Bazin

8
Bu ayrıca, aramalar sürdükçe anahtar listesi çok büyük olduğunda performans sorunlarına da neden olur O(n). Tüm operasyon olduğu O(mn)yerlerde, mdict tuşların sayısı ve olduğu nlistedeki tuşların sayısı. {key1, key2}Mümkünse bunun yerine bir set kullanmanızı öneririm .
ldavid

4
Apalala'ya: Neden bir performans vuruşu olduğunu anlamama yardım edebilir misin?
Sean

21

bir çözüm kullanıyor mapve filterişlevler

piton 2

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

piton 3

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

alırsın:

{'c': 3}

Bu benim için python 3.4 ile çalışmıyor:>>> 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 harita işlevinde bir yineleyici döndür
Jose Ricardo Bustos M.

4
veya deque(map(...), maxlen=0)Yok değerleri listesi oluşturmaktan kaçınmak için; ile ilk içe aktarmafrom collections import deque
Jason

19

Ayrıca kaldırmakta olduğunuz anahtarların değerlerini de almanız gerekiyorsa, bunu yapmanın oldukça iyi bir yolu olacaktır:

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

Elbette bunu sadece anahtarların kaldırılması için yapabilirsiniz d, ancak liste anlayışıyla gereksiz yere değerler listesi oluşturursunuz. Ayrıca, yalnızca işlevin yan etkisi için bir liste anlamanın kullanılması biraz belirsizdir.


3
Veya silinen girişleri bir sözlük olarak saklamak istiyorsanız :valuesRemoved = dict((k, d.pop(k, None)) for k in entitiesToRemove) vb.
nazik

Atamayı bir değişkene bırakabilirsiniz. Bu veya bu şekilde en kısa ve en pitonik çözümdür ve doğru cevap IMHO olarak işaretlenmelidir.
Gerhard Hagerer

12

popVe ile bir çözüm buldummap

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

Bunun çıktısı:

{'d': 'valueD'}

Bu soruyu çok geç cevapladım çünkü gelecekte biri aynı şeyi ararsa yardımcı olacağını düşünüyorum. Ve bu yardımcı olabilir.

Güncelleme

Yukarıdaki kod, diktede bir anahtar yoksa bir hata atacaktır.

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)

çıktı:

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

1
İçinde herhangi bir tuşa Bu cevap bir istisna durumu keysmevcut değildir d- ilk o Filtreye etmesi gerekir.
ingofreyer

@ingofreyer istisna işleme kodunu güncelledi. Bu sorunu bulduğunuz için teşekkürler. Şimdi işe yarayacağını düşünüyorum. :)
Shubham Srivastava

Teşekkürler, bu herkesin bu yanıtı bulmasına yardımcı olacaktır :-)
ingofreyer

Harita kullanmanın bir yan ürünü olarak bir liste oluşturmak, bunu oldukça yavaşlatır, aslında üzerinde döngü yapmak daha iyidir.
Charlie Clark

4

Mevcut cevapların hiçbiriyle sorunum yok, ancak bu çözümü bulamadığım için şaşırdım:

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}

Not: Buradan gelen bu soruyla karşılaştım . Ve cevabım bu cevapla ilgili .


3

Neden olmasın:

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

"Daha akıllı yol" ile ne demek istediğini bilmiyorum. Elbette başka yollar da vardır, belki sözlük anlamaları ile:

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

2

Çizgide

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 için bazı zamanlama testleri, basit bir for döngüsünün en hızlı yol olduğunu ve oldukça okunabilir olduğunu gösterir. Bir işlev eklemek çok fazla ek yüke neden olmaz:

timeit sonuçları (10k yineleme):

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

Küçük yinelemeler için, bunu 'satır içi' yapmak, işlev çağrısının ek yükü nedeniyle biraz daha hızlıydı. Ancak del_alltüy bırakmayan, yeniden kullanılabilir ve tüm python anlama ve haritalama yapılarından daha hızlıdır.


0

Python 3'teyseniz, anahtarların bir set olarak ele alınabileceği gerçeğini kullanmanın en güzel yolu olduğunu düşünüyorum:

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

Misal:

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

0

Sözlükler için set metotları için tam desteğe sahip olmak güzel olurdu (Python 3.9 ile karşılaştığımız kutsal karmaşa değil), böylece bir anahtar setini basitçe "kaldırabilirsiniz". Ancak, durum böyle olmadığı ve kaldırılması gereken çok sayıda anahtarın bulunduğu büyük bir sözlüğünüz olduğu sürece, performans hakkında bilgi edinmek isteyebilirsiniz. Bu yüzden, anlamlı karşılaştırmalar için yeterince büyük bir şey oluşturan bir kod yarattım: 100.000 x 1000 matris, yani toplamda 10.000,00 öğe.

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

Bazı ayarlarda 10 milyon veya daha fazla öğe alışılmadık değildir. Yerel makinemdeki iki yöntemi karşılaştırdığımda mapve popmuhtemelen daha az işlev çağrısı nedeniyle ve kullanırken küçük bir gelişme görüyorum , ancak her ikisi de makinemde yaklaşık 2,5 saniye sürüyor. Ancak bu, sözlüğü ilk etapta (55s) oluşturmak veya döngü içinde kontroller dahil etmek için gereken süreye kıyasla sönük kalır. Bu büyük olasılıkla, sözlük tuşları ile filtrenizin kesişim noktası olan bir küme oluşturmak en iyisidir:

keys = cells.keys() & keys

Özetle: delzaten yoğun bir şekilde optimize edilmiştir, bu nedenle onu kullanma konusunda endişelenmeyin.


-1

Bu tartışmaya geç kaldım ama bir başkası için. Bunun gibi bir anahtar listesi oluşturmak bir çözüm olabilir.

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

Ardından, anahtarlar üzerinde yinelemek ve her seferinde birini açmak için bir liste anlamasında veya for döngüsünde pop () kullanın.

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

'N / a', anahtarın mevcut olmaması durumunda, varsayılan bir değerin döndürülmesi gerektiğidir.


8
new_dictionarybir liste gibi çok kötü görünüyor;)
DylanYoung
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.