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?

Odpowiedzi:


363

Cóż, możesz to zrobić:

>>> if all (k in foo for k in ("foo","bar")):
...     print "They're there!"
...
They're there!

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

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

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.


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

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

8
w rzeczywistości nie tylko niepotrzebne, pozytywnie szkodliwe, ponieważ utrudniają normalne zachowanie zwarciowe all.
Alex Martelli,


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.


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.


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

1

A może skorzystasz z lambda?

 if reduce( (lambda x, y: x and foo.has_key(y) ), [ True, "foo", "bar"] ): # do stuff

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

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

1
>>> if 'foo' in foo and 'bar' in foo:
...     print 'yes'
... 
yes

Jason, () nie są potrzebne w Pythonie.


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

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

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

-4
>>> ok
{'five': '5', 'two': '2', 'one': '1'}

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

To wydaje się działać


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