Jak sprawdzić, czy wiele kluczy znajduje się w nagraniu w jednym przejściu?

218

Chcę zrobić coś takiego:

foo = {'foo':1,'zip':2,'zam':3,'bar':4}

if ("foo","bar") in foo:
    #do stuff

Jak sprawdzić, czy zarówno „foo”, jak i „bar” są w dict foo?

Jean-François Corbett
źródło

Odpowiedzi:

363

Cóż, możesz to zrobić:

>>> if all (k in foo for k in ("foo","bar")):
...     print "They're there!"
...
They're there!
Hughdbrown
źródło
10
+1, podoba mi się to lepiej niż odpowiedź Grega, ponieważ jest bardziej zwięzłe ORAZ szybsze (brak budowania niepotrzebnej listy tymczasowej ORAZ pełne wykorzystanie zwarcia).
Alex Martelli,
4
Kocham wszystkie () i dowolne (). Sprawiają, że wiele algorytmów jest o wiele czystszych.
hughdbrown
Ostatecznie skorzystałem z tego rozwiązania. Wydawało się to najlepsze w przypadku większych zbiorów danych. Podczas sprawdzania, powiedzmy 25 lub 30 kluczy.
4
Jest to dobre rozwiązanie dzięki zwarciu, szczególnie jeśli test kończy się niepowodzeniem częściej niż nie; chyba że możesz utworzyć zestaw interesujących kluczy tylko raz i sprawdzić go wiele razy, w którym setto przypadku jest lepszy. Jak zwykle ... zmierzyć to! -)
Alex Martelli,
Używam tego, ilekroć wygląda to ładniej niż w „normalny” sposób, ze wszystkimi znakami „i” lub „lub”… jest również fajny, ponieważ możesz użyć „wszystkich” lub „dowolnych”… a dodatkowo możesz mieć „ k in foo ”lub„ k not in foo ”w zależności od testu, który próbujesz wykonać
Terence Honles
123
if {"foo", "bar"} <= myDict.keys(): ...

Jeśli nadal korzystasz z Python 2, możesz to zrobić

if {"foo", "bar"} <= myDict.viewkeys(): ...

Jeśli nadal używasz naprawdę starego Pythona <= 2.6, możesz wywołać setdyktę, ale będzie on powtarzał się przez cały dykt, aby zbudować zestaw, a to wolno:

if set(("foo", "bar")) <= set(myDict): ...
Alex Martelli
źródło
wygląda dobrze! Jedyne, co mi się nie podoba, to to, że musisz tworzyć zestawy tymczasowe, ale jest bardzo kompaktowy. Więc muszę powiedzieć ... fajne użycie zestawów!
Terence Honles,
17
W Pythonie 3 możesz powiedzieć, set(("foo","bar")) <= myDict.keys()który unika zestawu tymczasowego, więc jest znacznie szybszy. W moich testach jest to mniej więcej taka sama prędkość, jak w przypadku użycia wszystkich, gdy zapytanie zawierało 10 elementów. Jednak staje się wolniejszy, gdy zapytanie staje się większe.
John La Rooy,
1
W odpowiedzi opublikowałem niektóre z moich testów. stackoverflow.com/questions/1285911/…
John La Rooy,
30
if {'foo', 'bar'} <= set(myDict): ...
Boris Raicheff,
11
Dla każdego, kto zastanawia się, dlaczego to działa: operator <= jest taki sam jak metoda use .set issubset (): docs.python.org/3/library/stdtypes.html#set-types-set-frozenset
edepe
41

Prosty zestaw do testów porównawczych dla 3 alternatyw.

Wprowadź własne wartości dla D i Q


>>> from timeit import Timer
>>> setup='''from random import randint as R;d=dict((str(R(0,1000000)),R(0,1000000)) for i in range(D));q=dict((str(R(0,1000000)),R(0,1000000)) for i in range(Q));print("looking for %s items in %s"%(len(q),len(d)))'''

>>> Timer('set(q) <= set(d)','D=1000000;Q=100;'+setup).timeit(1)
looking for 100 items in 632499
0.28672504425048828

#This one only works for Python3
>>> Timer('set(q) <= d.keys()','D=1000000;Q=100;'+setup).timeit(1)
looking for 100 items in 632084
2.5987625122070312e-05

>>> Timer('all(k in d for k in q)','D=1000000;Q=100;'+setup).timeit(1)
looking for 100 items in 632219
1.1920928955078125e-05
John La Rooy
źródło
4
Python 2.7 musi d.viewkeys()zrobić set(q) <= d.viewkeys().
Martijn Pieters
Python 2.7.5ma d.keys()też metodę.
Ivan Kharlamov
3
@IvanKharlamov, ale w Python2 nie zwraca obiektu zgodnego zset(q) <= ...
John La Rooy,
1
Mój zły, jesteś absolutnie na miejscu: powraca TypeError: can only compare to a set. Przepraszam! :))
Ivan Kharlamov
1
Dla Pythona 2 przełączyć kolejność: d.viewkeys() >= set(q). Przybyłem tutaj, aby dowiedzieć się, dlaczego zamówienie ma znaczenie!
Veedrac
34

Nie musisz owijać lewej strony w zestaw. Możesz po prostu to zrobić:

if {'foo', 'bar'} <= set(some_dict):
    pass

Działa to również lepiej niż all(k in d...)rozwiązanie.

claytonk
źródło
2
Działa to również lepiej niż rozwiązanie all (k in d ...). Zasugerowałem to jako edycję, ale zostało odrzucone ze względu na to, że lepiej było dodać komentarz . Więc oto ja to robię
miraculixx
@miraculixx Nie jest lepiej, aby dodać komentarz. Lepiej jest edytować odpowiednie informacje w odpowiedzi i usuwać komentarze.
endolith,
1
@endolith Zgadzam się, niektórzy ludzie oczywiście tego nie robią, jak widać w odrzuconej edycji, którą zrobiłem w pierwszej kolejności. W każdym razie to dyskusja na temat meta nie tutaj.
miraculixx
Czy ktoś może to wytłumaczyć? Zrozumiałem, że {} tworzy zestaw, ale w jaki sposób działa tutaj operator mniejszy lub równy?
Locane
1
@Locane Operator <= sprawdza, czy pierwszy zestaw jest podzbiorem drugiego zestawu. Możesz także zrobić {„foo”, „bar”}. Issubset (somedict). Dokumentację dotyczącą metodologii zestawów można znaleźć tutaj: docs.python.org/2/library/sets.html
Meow
24

Korzystanie z zestawów :

if set(("foo", "bar")).issubset(foo):
    #do stuff

Alternatywnie:

if set(("foo", "bar")) <= set(foo):
    #do stuff
Karl Voigtland
źródło
2
set (d), tak jak użyłem w mojej odpowiedzi, jest jak set (d.keys ()), ale jest szybszy, krótszy i powiedziałbym, że stylistycznie jest lepszy.
Alex Martelli,
set(d)jest taki sam jak set(d.keys())(bez d.keys()konstruowanej listy pośredniej )
Jochen Ritzel
11

Co powiesz na to:

if all([key in foo for key in ["foo","bar"]]):
    # do stuff
    pass
Greg
źródło
8
w rzeczywistości nie tylko niepotrzebne, pozytywnie szkodliwe, ponieważ utrudniają normalne zachowanie zwarciowe all.
Alex Martelli,
10

Myślę, że jest to najmądrzejszy i pithonic.

{'key1','key2'} <= my_dict.keys()
Shota Tamura
źródło
9

Chociaż podoba mi się odpowiedź Alexa Martellego, nie wydaje mi się ona pytoniczna. To znaczy, myślałem, że ważną częścią bycia Pythonem jest łatwość zrozumienia. W tym celu<= nie jest łatwo zrozumieć.

Chociaż jest więcej postaci, używanie issubset()zgodnie z sugestią Karla Voigtlanda jest bardziej zrozumiałe. Ponieważ ta metoda może wykorzystywać słownik jako argument, krótkie, zrozumiałe rozwiązanie to:

foo = {'foo': 1, 'zip': 2, 'zam': 3, 'bar': 4}

if set(('foo', 'bar')).issubset(foo):
    #do stuff

Chciałbym użyć {'foo', 'bar'}zamiast set(('foo', 'bar')), ponieważ jest krótszy. Nie jest to jednak tak zrozumiałe i myślę, że nawiasy klamrowe są zbyt łatwo mylone jako słownik.

LS
źródło
2
Myślę, że to zrozumiałe, kiedy zrozumiesz, co to znaczy.
Bobort
Jest w dokumentacji jako synonim .issubset(). Myślę, że obecność w dokumentacji Pythona domyślnie sprawia, że ​​jest to język Python.
ingyhere
4

Rozwiązanie Alexa Martellego set(queries) <= set(my_dict) jest najkrótszym kodem, ale może nie być najszybszym. Załóżmy, że Q = len (zapytania) i D = len (mój_dykt).

To zajmuje O (Q) + O (D), aby utworzyć dwa zestawy, a następnie (jeden ma nadzieję!) Tylko O ​​(min (Q, D)), aby wykonać test podzestawu - zakładając oczywiście, że wyszukiwanie zestawu Python to O (1) - jest to najgorszy przypadek (gdy odpowiedź brzmi Prawda).

Generatorowym rozwiązaniem hughdbrown (i in.?) all(k in my_dict for k in queries)Jest najgorszy przypadek O (Q).

Czynniki komplikujące:
(1) wszystkie pętle w gadżecie opartym na zestawie są wykonywane z prędkością C, podczas gdy gadżet oparty na dowolnym zestawie zapętla się po kodzie bajtowym.
(2) Osoba wywołująca dowolny gadżet może być w stanie wykorzystać dowolną wiedzę o prawdopodobieństwie niezamawiania odpowiednio elementów zapytania, natomiast gadżet oparty na zestawie nie pozwala na taką kontrolę.

Jak zawsze, jeśli prędkość jest ważna, dobrym pomysłem jest przeprowadzenie testów porównawczych w warunkach operacyjnych.

John Machin
źródło
1
Generator był szybszy dla wszystkich przypadków, które próbowałem. stackoverflow.com/questions/1285911/…
John La Rooy
2

Można użyć .issubset () , jak również

>>> {"key1", "key2"}.issubset({"key1":1, "key2":2, "key3": 3})
True
>>> {"key4", "key2"}.issubset({"key1":1, "key2":2, "key3": 3})
False
>>>
Sinan Çetinkaya
źródło
1

A może skorzystasz z lambda?

 if reduce( (lambda x, y: x and foo.has_key(y) ), [ True, "foo", "bar"] ): # do stuff
Jinuk Kim
źródło
2
Ta odpowiedź jest jedyną poprawną funkcjonalnie, która będzie działała na Pythonie 1.5 z prostą zmianą (s / True / 1 /) ... ale nie ma nic innego. ORAZ Prawda byłaby lepsza jako opcjonalny arg inicjalizujący niż wciskany na przód sekwencji arg.
John Machin,
1

Jeśli chcesz:

  • również uzyskać wartości dla kluczy
  • sprawdź więcej niż jeden dyktafon

następnie:

from operator import itemgetter
foo = {'foo':1,'zip':2,'zam':3,'bar':4}
keys = ("foo","bar") 
getter = itemgetter(*keys) # returns all values
try:
    values = getter(foo)
except KeyError:
    # not both keys exist
    pass
Jochen Ritzel
źródło
1

Nie sugerując, że nie jest to coś, o czym nie pomyślałeś, ale uważam, że najprostsza rzecz jest zwykle najlepsza:

if ("foo" in foo) and ("bar" in foo):
    # do stuff
Jason Baker
źródło
1
>>> if 'foo' in foo and 'bar' in foo:
...     print 'yes'
... 
yes

Jason, () nie są potrzebne w Pythonie.

Juanjo Conti
źródło
3
Wciąż mogą być w dobrym stylu ... bez nich, mój mózg w języku C ++ zawsze zastanawia się, czy to będzie interpretowane jako „jeśli” foo w (foo i „bar”) w foo: ”
Jeremy Friesner,
1
Rozumiem, że nie są konieczne. Po prostu czuję, że dodają jasności w tym przypadku.
Jason Baker
0

Po prostu moje zdanie na ten temat, istnieją dwie metody, które są łatwe do zrozumienia dla wszystkich podanych opcji. Więc moje główne kryteria to bardzo czytelny kod, a nie wyjątkowo szybki kod. Aby kod był zrozumiały, wolę dawać możliwości:

  • var <= var2.keys ()
  • var.issubset (var2)

Fakt, że „var <= var2.keys ()” działa szybciej w moich testach poniżej, wolę ten.

import timeit

timeit.timeit('var <= var2.keys()', setup='var={"managed_ip", "hostname", "fqdn"}; var2= {"zone": "test-domain1.var23.com", "hostname": "bakje", "api_client_ip": "127.0.0.1", "request_data": "", "request_method": "GET", "request_url": "hvar2p://127.0.0.1:5000/test-domain1.var23.com/bakje", "utc_datetime": "04-Apr-2019 07:01:10", "fqdn": "bakje.test-domain1.var23.com"}; var={"managed_ip", "hostname", "fqdn"}')
0.1745898080000643

timeit.timeit('var.issubset(var2)', setup='var={"managed_ip", "hostname", "fqdn"}; var2= {"zone": "test-domain1.var23.com", "hostname": "bakje", "api_client_ip": "127.0.0.1", "request_data": "", "request_method": "GET", "request_url": "hvar2p://127.0.0.1:5000/test-domain1.var23.com/bakje", "utc_datetime": "04-Apr-2019 07:01:10", "fqdn": "bakje.test-domain1.var23.com"}; var={"managed_ip", "hostname", "fqdn"};')
0.2644960229999924
PietjePuk
źródło
0

W przypadku ustalenia, czy tylko niektóre klucze pasują, działa to:

any_keys_i_seek = ["key1", "key2", "key3"]

if set(my_dict).intersection(any_keys_i_seek):
    # code_here
    pass

Jeszcze jedna opcja, aby sprawdzić, czy pasują tylko niektóre klucze:

any_keys_i_seek = ["key1", "key2", "key3"]

if any_keys_i_seek & my_dict.keys():
    # code_here
    pass
ingyhere
źródło
0

Inna opcja wykrywania, czy wszystkie klucze są w nagraniu:

dict_to_test = { ... }  # dict
keys_sought = { "key_sought_1", "key_sought_2", "key_sought_3" }  # set

if keys_sought & dict_to_test.keys() == keys_sought: 
    # yes -- dict_to_test contains all keys in keys_sought
    # code_here
    pass
ingyhere
źródło
-4
>>> ok
{'five': '5', 'two': '2', 'one': '1'}

>>> if ('two' and 'one' and 'five') in ok:
...   print "cool"
... 
cool

To wydaje się działać

Prashanth Gowda
źródło
To sprytne i byłem przekonany, że to nie zadziałało, dopóki sam tego nie spróbowałem. Podejrzewałem, ()że zostanie najpierw oceniony i wynik True, który następnie sprawdzi, czy True in ok. Jak to właściwie działa ?!
durden2.0
7
(„dwa”, „jeden” i „pięć”) zwraca „pięć”, więc faktycznie sprawdza tylko, czy „pięć” jest
podyktowane