filtruj elementy w słowniku Pythona, w którym klucze zawierają określony ciąg

97

Jestem programistą C, który tworzy coś w Pythonie. Wiem, jak wykonać następujące czynności w C (a zatem w logice podobnej do C zastosowanej w Pythonie), ale zastanawiam się, jaki jest sposób robienia tego w „Pythonie”.

Mam słownik d i chciałbym operować na podzbiorze elementów, tylko te, dla których klucz (ciąg) zawiera określony podciąg.

czyli logika C wyglądałaby następująco:

for key in d:
    if filter_string in key:
        # do something
    else
        # do nothing, continue

Wyobrażam sobie, że wersja Pythona byłaby podobna

filtered_dict = crazy_python_syntax(d, substring)
for key,value in filtered_dict.iteritems():
    # do something

Znalazłem tutaj wiele postów dotyczących filtrowania słowników, ale nie mogłem znaleźć takiego, który dotyczyłby dokładnie tego.

Mój słownik nie jest zagnieżdżony i używam Pythona 2.7

notatka
źródło
stackoverflow.com/questions/2844516/python-filter-a-dictionary
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Odpowiedzi:

187

A co ze zrozumieniem dyktowania :

filtered_dict = {k:v for k,v in d.iteritems() if filter_string in k}

Jeśli to zobaczysz, powinno to być oczywiste, ponieważ całkiem dobrze brzmi jak angielski.

Ta składnia wymaga Pythona 2.7 lub nowszego.

W Pythonie 3 jest tylko dict.items(), nie iteritems()więc używałbyś:

filtered_dict = {k:v for (k,v) in d.items() if filter_string in k}
Jonathon Reinhart
źródło
1
Dlaczego nie filtered_dict = {k:d[k] for k in d if filter_string in k}?
thefourtheye
5
@thefourtheye Zgaduję, że mój jest szybszy, ponieważ nie powoduje d[k]wyszukiwania.
Jonathon Reinhart
Również, mówi # do somethingw komentarzach, ale tutaj upuszczamy kilka kluczy.
thefourtheye
Czy mamy iteritemsw Pythonie 3? Nie sądzę. Więc moja wersja byłaby kompatybilna, nie?
thefourtheye
1
W Pythonie 3 należy zastąpić iteritemsz items, która jest taka sama jak w Pythonie 2.7-tych iteritems.
Jonathon Reinhart
18

Wybierz wszystko, co jest najbardziej czytelne i łatwe w utrzymaniu. To, że możesz to zapisać w jednej linii, nie oznacza, że ​​powinieneś. Twoje istniejące rozwiązanie jest bliskie temu, czego użyłbym inaczej niż użyłbym iteritemów, aby pominąć wyszukiwanie wartości, i nienawidzę zagnieżdżonych ifs, jeśli mogę ich uniknąć:

for key, val in d.iteritems():
    if filter_string not in key:
        continue
    # do something

Jednak jeśli naprawdę chcesz, aby coś pozwoliło ci iterować przez filtrowany dykt, nie wykonałbym dwuetapowego procesu budowania przefiltrowanego dyktu, a następnie iterowania przez niego, ale zamiast tego użyj generatora, ponieważ co jest bardziej pythonowe (i niesamowite) niż generator?

Najpierw tworzymy nasz generator, a dobry projekt wymaga, aby był na tyle abstrakcyjny, aby można go było ponownie użyć:

# The implementation of my generator may look vaguely familiar, no?
def filter_dict(d, filter_string):
    for key, val in d.iteritems():
        if filter_string not in key:
            continue
        yield key, val

A potem możemy użyć generatora, aby rozwiązać twój problem ładnie i czysto za pomocą prostego, zrozumiałego kodu:

for key, val in filter_dict(d, some_string):
    # do something

Krótko mówiąc: generatory są niesamowite.

Brendan F
źródło
11

Możesz użyć wbudowanej funkcji filtru do filtrowania słowników, list itp. Na podstawie określonych warunków.

filtered_dict = dict(filter(lambda item: filter_str in item[0], d.items()))

Zaletą jest to, że można go używać do różnych struktur danych.

Pulkit
źródło
Zauważ, że items:powinno to być item:w definicji lambda.
bkribbs
Dziękuję @bkribbs za wskazanie błędu. Teraz to naprawiłem.
Pulkit
8
input = {"A":"a", "B":"b", "C":"c"}
output = {k:v for (k,v) in input.items() if key_satifies_condition(k)}
jspurim
źródło
3
Moja metoda iteritems()będzie bardziej wydajna niż items().
Jonathon Reinhart
@Jonathin Reinhart Nie wiedziałem o tym. Dzięki.
jspurim
2
Tylko w Pythonie 2.7. W Pythonie 3 jest tylko items() , który działa jak Python 2.7 iteritems.
Jonathon Reinhart
1
Pytanie dotyczy wyraźnie Pythona 2.7
Brendan F
7

Jonathon podał ci podejście, używając w swojej odpowiedzi dyktowania ze zrozumieniem . Oto podejście, które dotyczy części robienia czegoś .

Jeśli chcesz coś zrobić z wartościami słownika, nie potrzebujesz w ogóle rozumienia ze słownika:

Używam iteritems(), ponieważ oznaczyłeś swoje pytanie tagiem

results = map(some_function, [(k,v) for k,v in a_dict.iteritems() if 'foo' in k])

Teraz wynik będzie na liście z some_functionzastosowaną do każdej pary klucz / wartość w słowniku, która ma foow swoim kluczu.

Jeśli chcesz tylko zająć się wartościami i zignorować klucze, po prostu zmień rozumienie listy:

results = map(some_function, [v for k,v in a_dict.iteritems() if 'foo' in k])

some_function może być wywoływalne, więc lambda również będzie działać:

results = map(lambda x: x*2, [v for k,v in a_dict.iteritems() if 'foo' in k])

Lista wewnętrzna w rzeczywistości nie jest wymagana, ponieważ można również przekazać wyrażenie generatora do mapowania:

>>> map(lambda a: a[0]*a[1], ((k,v) for k,v in {2:2, 3:2}.iteritems() if k == 2))
[4]
Burhan Khalid
źródło
ciekawy. jak można zdefiniować some_function? w pierwszym przypadku (k, v), czy przyjmuje tylko dwa parametry? pierwszy klucz, potem wartość?
notatka
Tak, tylko do wywołania. Więc map(lambda a: a[0]*a[1], ((k,v) for k,v in {2:2, 3:2}.iteritems() if k == 2))- to ci da [4].
Burhan Khalid
To jest poprawne, ale bardziej pythonowe niż używanie mapjest rozumienie listy. [f(v) for k, v in d.iteritems() if substring in k]Myślę, że jest dużo bardziej czytelny i wydajniejszy.
Davidmh
@memo Nie wymagałoby to dwóch parametrów, tylko jeden parametr z dwoma elementami. Istnieje również mapa gwiazd, która rozpakuje się na dwa argumenty, jednak jest to leniwy iterator (musi być iterowany przed wykonaniem, tj. results = list(starmap(...))Lub for result in starmap(...): ...).
nmclean