2014-05-11 15 views
5

Próbuję upuścić elementy ze słownika, jeśli wartość klucza jest poniżej pewnego progu. Prosty przykład na to, co mam na myśli:Usuwanie elementów ze słownika z pętlą for

my_dict = {'blue': 1, 'red': 2, 'yellow': 3, 'green': 4} 

for color in my_dict: 
    threshold_value = 3 
    if my_dict[color] < threshold_value: 
     del my_dict[color] 

print(my_dict) 

Teraz pojawia się błąd RuntimeError: dictionary changed size during iteration. Bez wielkich niespodzianek. Powodem jestem delegowania na to pytanie brzmi:

  1. Sprawdzaj, czy jest eleganckie rozwiązanie, które nie wymaga tworzenia nowego słownika (która posiada tylko klucze z wartości> = próg).

  2. Spróbuj zrozumieć rozumowanie Pythona. Sposób, w jaki go przeczytam to: "przejdź do pierwszego klawisza. Czy wartość tego klucza to < x? Jeśli tak - prześlij ten klucz: wartość i przejdź do następnego klawisza w słowniku, jeśli nie - kontynuuj następny klucz bez robienia czegokolwiek ". Innymi słowy, to, co stało się historycznie z poprzednimi kluczami, nie powinno mieć wpływu na to, dokąd pójdę dalej. Z niecierpliwością czekam na kolejne przedmioty, niezależnie od przeszłości. Wiem, że to zabawne (niektórzy mogą powiedzieć głupi, dam ci to), ale jaki jest "sposób myślenia" Pythona na temat tej pętli? Dlaczego to nie działa? W jaki sposób Python odczytałby go na głos? Po prostu staramy się uzyskać lepsze zrozumienie języka ...

Odpowiedz

10

Ze względu na fakt, że słowniki Python są implementowane jako hash tabel, nie należy na nich polegać konieczności jakiejkolwiek kolejności. Kluczowa kolejność może zmienić się nieprzewidywalnie (ale tylko po włożeniu lub usunięciu klucza). Dlatego nie można przewidzieć następnego klucza. Python powoduje, że RuntimeError jest bezpieczny i zapobiega nieoczekiwanym wynikom.

Python 2 w dict.items metoda zwraca skopiować par klucz-wartość, dzięki czemu można bezpiecznie iteracyjne nad nim i usunąć wartości nie trzeba przez klawisze, jak @wim sugerowane w komentarzach. Przykład:

for k, v in my_dict.items(): 
    if v < threshold_value: 
     del my_dict[k] 

Jednak Python 3 na dict.items zwraca view object który odzwierciedla wszystkie zmiany wprowadzone do słownika. To jest powód, dla którego powyższe rozwiązanie działa tylko w Pythonie 2. Możesz przekonwertować my_dict.items() na list (tuple itd.), Aby był kompatybilny z Python 3.

Innym sposobem podejścia do problemu jest wybór klawiszy, które chcesz usunąć i następnie je usunąć

keys = [k for k, v in my_dict.items() if v < threshold_value] 
for x in keys: 
    del my_dict[x] 

Działa to zarówno w Python 2 i Python 3.

2

słowniki są nieuporządkowane. Przez usunięcie jednego klucza nikt nie może powiedzieć, jaki jest następny klucz. Tak więc pyton w ogóle nie pozwala dodawać ani usuwać kluczy ze słownika, ponieważ jest iterowany.

Wystarczy utworzyć nowe:

my_dict = {"blue":1,"red":2,"yellow":3,"green":4} 
new_dict = {k:v for k,v in my_dict.iteritems() if v >= threshold_value} 
+0

, jak powiedziałem w PO, nie chcę tworzyć nowego dyktatu. – Optimesh

+0

Prosiłeś o elegancki sposób. Ale możesz zaprogramować, co chcesz. – Daniel

0

Chyba, że ​​modyfikację kolekcji podczas iteracji nad nim jest trudna rzecz do zrobienia, aby prawidłowo realizować. Rozważmy następujące:

>>> list = [1, 2, 3, 4, 5, 6] 
>>> for ii in range(len(list)): 
    print list[ii]; 
    if list[ii] == 3: 
    del list[ii]  
1 
2 
3 
5 
6 

Zauważ, że w tym przykładzie 4 zostało całkowicie pominięte.W słownikach jest to bardzo podobne, usunięcie lub dodanie wpisów może spowodować unieważnienie wewnętrznych struktur definiujących kolejność iteracji (na przykład usunięto wystarczającą liczbę wpisów, więc zmienił się rozmiar segmentu mapy hash).

Aby rozwiązać swoją sprawę - po prostu utwórz nowy słownik i skopiuj tam elementy. Jeśli chodzi o