Filtrować słownik, aby zawierał tylko niektóre klucze?


496

Mam taki, dictktóry ma całą masę wpisów. Interesuje mnie tylko kilka wybranych. Czy istnieje prosty sposób na przycięcie wszystkich pozostałych?


Pomocne jest określenie, jaki typ kluczy (liczby całkowite? Ciągi? Daty? Dowolne obiekty?), A zatem czy istnieje prosty test (ciąg, wyrażenie regularne, członkostwo na liście lub nierówność numeryczna), aby sprawdzić, które klucze są na wejściu, a które na zewnątrz. Czy też musimy wywołać dowolną funkcję (funkcje), aby to ustalić.
smci

@smci Klucze ciągowe. Nie myśl, że nawet przyszło mi do głowy, że mogę użyć czegokolwiek innego; Tak długo
pisałem

Odpowiedzi:


655

Konstruowanie nowego dykta:

dict_you_want = { your_key: old_dict[your_key] for your_key in your_keys }

Korzysta ze słownika.

Jeśli używasz wersji, która ich nie ma (tj. Python 2.6 i wcześniejsze), zrób to dict((your_key, old_dict[your_key]) for ...). To jest to samo, choć brzydsze.

Zauważ, że w przeciwieństwie do wersji jnnnnn, ma stabilną wydajność (zależy tylko od liczby twoich_kluczy) dla old_dicts dowolnego rozmiaru. Zarówno pod względem szybkości, jak i pamięci. Ponieważ jest to wyrażenie generatora, przetwarza jeden element na raz i nie przegląda wszystkich elementów old_dict.

Usuwanie wszystkiego na miejscu:

unwanted = set(keys) - set(your_dict)
for unwanted_key in unwanted: del your_dict[unwanted_key]

8
„Korzysta ze słownika, jeśli używasz wersji, która ich nie ma” == wersja <= 2.6
getekha,

8
Zgłasza błąd klucza, jeśli jeden z kluczy filtru nie występuje w old_dict. Sugerowałbym {k: d [k] dla k w filtrze, jeśli k in d}
Peter Gibson

1
@PeterGibson Tak, jeśli jest to część wymagań, musisz coś z tym zrobić . To, czy dyskretnie upuszczasz klucze, dodajesz wartość domyślną, czy coś innego, zależy od tego, co robisz; istnieje wiele przypadków użycia, w których twoje podejście jest błędne. Istnieje również wiele przypadków, w których brak klucza old_dictwskazuje na błąd w innym miejscu, i w takim przypadku zdecydowanie wolę błąd niż cicho błędne wyniki.

@ delnan, również dodatek „if k in d” spowalnia cię, jeśli d jest duże, pomyślałem, że warto o tym wspomnieć
Peter Gibson

7
@PeterGibson Nie, wyszukiwanie słownika to O (1).

130

Nieco bardziej eleganckie rozumienie nagrań:

foodict = {k: v for k, v in mydict.items() if k.startswith('foo')}

Pozytywne. Myślałem o dodaniu podobnej odpowiedzi. Jednak z ciekawości, dlaczego {k: v dla k, v w dict.items () ...} zamiast {k: dict [k] dla k w dict ...} Czy istnieje różnica w wydajności?
Hart Simha

4
Odpowiedziałem na własne pytanie. {K: dict [k] dla k w dict ...} jest około 20-25% szybszy, przynajmniej w Pythonie 2.7.6, ze słownikiem 26 elementów (timeit (..., setup = "d = {chr (x + 97): x + 1 dla xw zakresie (26)} ")), w zależności od liczby elementów, które są filtrowane (odfiltrowanie kluczy spółgłoskowych jest szybsze niż odfiltrowanie kluczy samogłoskowych, ponieważ patrzysz w górę mniej przedmiotów). Różnica w wydajności może bardzo zmniejszyć się wraz ze wzrostem rozmiaru słownika.
Hart Simha

5
Prawdopodobnie byłby taki sam, gdybyś użył mydict.iteritems()zamiast tego. .items()tworzy kolejną listę.
Pat

64

Oto przykład w Pythonie 2.6:

>>> a = {1:1, 2:2, 3:3}
>>> dict((key,value) for key, value in a.iteritems() if key == 1)
{1: 1}

Część filtrująca to ifinstrukcja.

Ta metoda jest wolniejsza niż odpowiedź delnana, jeśli chcesz wybrać tylko kilka bardzo wielu kluczy.


11
chyba że prawdopodobnie skorzystałbym if key in ('x','y','z').
mpen

jeśli już wiesz, które klucze chcesz, użyj odpowiedzi delnana. Jeśli chcesz przetestować każdy klucz za pomocą instrukcji if, skorzystaj z odpowiedzi ransford.
jnnnnn

1
To rozwiązanie ma jeszcze jedną zaletę. Jeśli słownik zostanie zwrócony z drogiego wywołania funkcji (tj. / Old_dict to wywołanie funkcji), to rozwiązanie wywołuje funkcję tylko raz. W imperatywnym środowisku przechowującym słownik zwracany przez funkcję w zmiennej nie jest wielka sprawa, ale w środowisku funkcjonalnym (np. W lambda) jest to kluczowa obserwacja.
gae123


20

Kod 1:

dict = { key: key * 10 for key in range(0, 100) }
d1 = {}
for key, value in dict.items():
    if key % 2 == 0:
        d1[key] = value

Kod 2:

dict = { key: key * 10 for key in range(0, 100) }
d2 = {key: value for key, value in dict.items() if key % 2 == 0}

Kod 3:

dict = { key: key * 10 for key in range(0, 100) }
d3 = { key: dict[key] for key in dict.keys() if key % 2 == 0}

Wszystkie składowe wydajności kodu są mierzone za pomocą timeit przy użyciu liczby = 1000 i gromadzone 1000 razy dla każdego fragmentu kodu.

wprowadź opis zdjęcia tutaj

W Pythonie 3.6 wydajność trzech sposobów filtrowania kluczy dyktowania jest prawie taka sama. Dla Pythona 2.7 kod 3 jest nieco szybszy.


tylko ciekawe, czy stworzyłeś tę fabułę z Pythona?
user5359531,

1
ggplot2 in R - część tidyverse
keithpjolley

18

Ta liner lambda powinna działać:

dictfilt = lambda x, y: dict([ (i,x[i]) for i in x if i in set(y) ])

Oto przykład:

my_dict = {"a":1,"b":2,"c":3,"d":4}
wanted_keys = ("c","d")

# run it
In [10]: dictfilt(my_dict, wanted_keys)
Out[10]: {'c': 3, 'd': 4}

Jest to podstawowe rozumienie listy, iterujące po klawiszach dict (i in x) i wyświetla listę par krotek (klucz, wartość), jeśli klucz znajduje się na żądanej liście kluczy (y). Dict () otacza całość, by wyprowadzić ją jako obiekt dict.


Należy użyć setdo wanted_keys, ale poza tym wygląda dobrze.
mpen

To daje mi pusty słownik, jeśli mój oryginalny słownik zawiera listy zamiast wartości. Jakieś obejścia?
FaCoffee

@Francesco, czy możesz podać przykład? Jeśli uruchomię dictfilt({'x':['wefwef',52],'y':['iuefiuef','efefij'],'z':['oiejf','iejf']}, ('x','z')):, zwraca {'x': ['wefwef', 52], 'z': ['oiejf', 'iejf']}zgodnie z przeznaczeniem.
Jim

Próbowałem tego z: dict={'0':[1,3], '1':[0,2,4], '2':[1,4]}i wynik był taki {}, że zakładałem, że jest to puste zdanie.
FaCoffee

Jedno, „dykt” jest słowem zastrzeżonym, więc nie powinieneś używać go do nazywania dykt. Jakie klucze próbowałeś wyciągnąć? Jeśli uruchomię foo = {'0':[1,3], '1':[0,2,4], '2':[1,4]}; dictfilt(foo,('0','2')):, dostanę: {'0': [1, 3], '2': [1, 4]}co jest zamierzonym rezultatem
Jim

14

Biorąc pod uwagę oryginalny słownik origi zestaw pozycji, którymi jesteś zainteresowany keys:

filtered = dict(zip(keys, [orig[k] for k in keys]))

co nie jest tak miłe jak odpowiedź Delnana, ale powinno działać w każdej interesującej wersji Pythona. Jest jednak delikatny dla każdego elementu keysistniejącego w oryginalnym słowniku.


Cóż, jest to w zasadzie chętna wersja „wersji generatora krotek” mojego rozumienia dykta. Rzeczywiście bardzo kompatybilny, chociaż wyrażenia generatora zostały wprowadzone wiosną 2005 roku w wersji 2.4 - poważnie, czy ktoś jeszcze tego używa?

1
Nie zgadzam się; 2.3 naprawdę nie powinno już istnieć. Jednak jako przestarzała ankieta dotycząca użycia 2.3: moinmo.in/PollAboutRequiringPython24 Wersja skrócona: RHEL4, SLES9, dostarczana z systemem OS X 10.4
Kai

7

Na podstawie zaakceptowanej odpowiedzi delnan.

Co zrobić, jeśli jednego z twoich poszukiwanych kluczy nie ma w old_dict? Rozwiązanie Delnan zgłosi wyjątek KeyError, który można przechwycić. Jeśli nie tego potrzebujesz, może chcesz:

  1. uwzględniaj tylko klucze, które istnieją zarówno w old_dict, jak i w zestawie „Want_Key”.

    old_dict = {'name':"Foobar", 'baz':42}
    wanted_keys = ['name', 'age']
    new_dict = {k: old_dict[k] for k in set(wanted_keys) & set(old_dict.keys())}
    
    >>> new_dict
    {'name': 'Foobar'}
  2. mają wartość domyślną dla kluczy, która nie jest ustawiona w old_dict.

    default = None
    new_dict = {k: old_dict[k] if k in old_dict else default for k in wanted_keys}
    
    >>> new_dict
    {'age': None, 'name': 'Foobar'}

Możesz również zrobić{k: old_dict.get(k, default) for k in ...}
Moberg

6

Ta funkcja załatwi sprawę:

def include_keys(dictionary, keys):
    """Filters a dict by only including certain keys."""
    key_set = set(keys) & set(dictionary.keys())
    return {key: dictionary[key] for key in key_set}

Podobnie jak wersja delnan, ta wykorzystuje rozumienie słownika i ma stabilną wydajność dla dużych słowników (zależnie tylko od liczby dozwolonych kluczy, a nie od całkowitej liczby kluczy w słowniku).

I podobnie jak wersja MyGGan, ta pozwala na dołączenie do listy kluczy kluczy, które mogą nie istnieć w słowniku.

Jako bonus, oto odwrotność, w której można utworzyć słownik, wykluczając niektóre klucze w oryginale:

def exclude_keys(dictionary, keys):
    """Filters a dict by excluding certain keys."""
    key_set = set(dictionary.keys()) - set(keys)
    return {key: dictionary[key] for key in key_set}

Zauważ, że w przeciwieństwie do wersji delnana, operacja nie jest wykonywana na miejscu, więc wydajność zależy od liczby kluczy w słowniku. Zaletą tego jest jednak to, że funkcja nie modyfikuje podanego słownika.

Edycja: Dodano osobną funkcję wykluczania niektórych kluczy ze słownika.


Powinieneś zezwalać keysna dowolny rodzaj iteracji, jak na przykład zestaw akceptuje.
mpen

Ach, dobra rozmowa, dziękuję za zwrócenie na to uwagi. Dokonam tej aktualizacji.
Ryan,

Zastanawiam się, czy lepiej ci z dwiema funkcjami. Jeśli zapytasz 10 osób „czy to invertoznacza, że keysargument jest podtrzymywany, czy keysargument jest odrzucany?”, Ilu z nich się zgodzi?
skatenerd

Zaktualizowano Powiedz mi co myślisz.
Ryan

Wygląda na to, że nie działa, jeśli wejściowy słownik ma listy zamiast wartości. W takim przypadku otrzymasz nieważny dyktand. Jakieś obejścia?
FaCoffee

4

Jeśli chcemy stworzyć nowy słownik z usuniętymi wybranymi kluczami, możemy skorzystać ze słownika
Na przykład:

d = {
'a' : 1,
'b' : 2,
'c' : 3
}
x = {key:d[key] for key in d.keys() - {'c', 'e'}} # Python 3
y = {key:d[key] for key in set(d.keys()) - {'c', 'e'}} # Python 2.*
# x is {'a': 1, 'b': 2}
# y is {'a': 1, 'b': 2}

Schludny. Działa tylko w Pythonie 3. Python 2 mówi „TypeError: nieobsługiwane typy operandów dla -:„ list ”i„ set ””
mpen

Dodano zestaw (d.keys ()) dla Pythona 2. Działa to podczas uruchamiania.
Srivastava

2

Inna opcja:

content = dict(k1='foo', k2='nope', k3='bar')
selection = ['k1', 'k3']
filtered = filter(lambda i: i[0] in selection, content.items())

Ale otrzymujesz list(Python 2) lub iterator (Python 3) filter(), a nie dict.


Wrap filteredw dicti wrócisz do słownika!
CMCDragonkai

1

Skrócona forma:

[s.pop(k) for k in list(s.keys()) if k not in keep]

Ponieważ większość odpowiedzi sugerują, w celu zachowania zwięzłości musimy utworzyć duplikat obiektu czy to listlub dict. Ten tworzy wyrzucenie, listale usuwa klucze w oryginale dict.


0

Oto kolejna prosta metoda wykorzystująca delw jednym wkładce:

for key in e_keys: del your_dict[key]

e_keysto lista kluczy do wykluczenia. Zaktualizuje twój dykt, a nie da ci nowy.

Jeśli chcesz nowego nagrania wyjściowego, wykonaj jego kopię przed usunięciem:

new_dict = your_dict.copy()           #Making copy of dict

for key in e_keys: del new_dict[key]

0

Możesz użyć python-benedict, to podklasa dict.

Instalacja: pip install python-benedict

from benedict import benedict

dict_you_want = benedict(your_dict).subset(keys=['firstname', 'lastname', 'email'])

Jest open-source na GitHub: https://github.com/fabiocaccamo/python-benedict


Oświadczenie: Jestem autorem tej biblioteki.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.