Как проверить, что несколько ключей находятся в dict за один проход?

218

Я хочу сделать что-то вроде:

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

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

Как проверить, находятся ли оба слова 'foo' и 'bar' в dict foo?

Жан-Франсуа Корбетт
источник

Ответы:

363

Ну, вы могли бы сделать это:

>>> if all (k in foo for k in ("foo","bar")):
...     print "They're there!"
...
They're there!
hughdbrown
источник
10
+1, мне нравится это лучше, чем ответ Грега, потому что он более лаконичен и быстр (без создания ненужного временного списка и полной эксплуатации короткого замыкания).
Алекс Мартелли
4
Я люблю все () и любые (). Они делают так много алгоритмов намного чище.
hughdbrown
В конечном итоге я использовал это решение. Это казалось лучшим для больших наборов данных. При проверке скажем 25 или 30 ключей.
4
Это хорошее решение благодаря короткому замыканию, особенно если тест не проходит чаще; если вы не можете создать интересующий набор ключей только один раз и проверять его много раз, в этом случае setлучше. Как обычно ... измерить! -)
Алекс Мартелли
Я использую это всякий раз, когда это выглядит лучше, чем "нормальный" способ, со всеми "и" или "или" ... это также хорошо, потому что вы можете использовать либо "все", либо "любое" ... кроме того, вы можете иметь " k in foo "или" k not in foo "в зависимости от теста, который вы пытаетесь выполнить
Теренс Хонлес
123
if {"foo", "bar"} <= myDict.keys(): ...

Если вы все еще на Python 2, вы можете сделать

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

Если вы все еще на самом старом Python <= 2.6, вы можете вызвать setdict, но он будет перебирать весь dict для создания набора, и это медленно:

if set(("foo", "bar")) <= set(myDict): ...
Алекс Мартелли
источник
выглядит хорошо! Единственное, что мне не нравится, это то, что вы должны создавать временные наборы, но это очень компактно. Так что я должен сказать ... хорошее использование наборов!
Теренс Хонлес
17
В Python 3 вы можете сказать, set(("foo","bar")) <= myDict.keys()что избегает временного набора, поэтому гораздо быстрее. Для моего тестирования это примерно та же скорость, что и при использовании всего, когда запрос был 10 элементов. Это становится медленнее, когда запрос становится больше, хотя.
Джон Ла Рой
1
Я опубликовал некоторые из моих тестов в качестве ответа. stackoverflow.com/questions/1285911/…
Джон Ла Рой
30
if {'foo', 'bar'} <= set(myDict): ...
Борис Райхев
11
Для всех, кто интересуется, почему это работает: оператор <= аналогичен методу use .set issubset (): docs.python.org/3/library/stdtypes.html#set-types-set-frozenset
edepe
41

Простая тестовая установка для 3 альтернатив.

Введите свои собственные значения для D и 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 должен d.viewkeys()сделать set(q) <= d.viewkeys().
Мартин Питерс
Python 2.7.5есть d.keys()метод тоже.
Иван Харламов
3
@IvanKharlamov, но в Python2 он не возвращает объект, который совместим сset(q) <= ...
John La Rooy
1
Мое плохое, вы абсолютно на месте: оно возвращается TypeError: can only compare to a set. Сожалею! :))
Иван Харламов
1
Для Python 2 изменить порядок: d.viewkeys() >= set(q). Я пришел сюда, чтобы выяснить, почему порядок имеет значение!
Veedrac
34

Вам не нужно оборачивать левую сторону в наборе. Вы можете просто сделать это:

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

Это также работает лучше, чем all(k in d...)решение.

claytonk
источник
2
Это также работает лучше, чем решение all (k in d ...). Я предложил это как редактирование, но оно было отклонено на том основании, что было бы лучше добавить комментарий . Так вот, я делаю именно это
miraculixx
@miraculixx Не лучше добавлять комментарии. Лучше отредактировать соответствующую информацию в ответ и удалить комментарии.
эндолит
1
@endolith Я согласен, некоторые люди, очевидно, не так, как вы можете видеть в отклоненном редактировании, которое я сделал в первую очередь. Во всяком случае, это обсуждение для мета не здесь.
miraculixx
Может кто-нибудь объяснить это, пожалуйста? Я понял, что {} создает множество, но как здесь работает оператор «меньше или равно»?
Locane
1
@Locane Оператор <= проверяет, является ли первый набор подмножеством второго набора. Вы также можете сделать {'foo', 'bar'}. Issubset (somedict). Документацию по методологии набора можно найти здесь: docs.python.org/2/library/sets.html
Мяу
24

Используя наборы :

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

В качестве альтернативы:

if set(("foo", "bar")) <= set(foo):
    #do stuff
Карл Войтланд
источник
2
set (d), как я использовал в своем ответе, аналогично set (d.keys ()), но быстрее, короче, и я бы сказал, стилистически предпочтительнее.
Алекс Мартелли
set(d)такой же, как set(d.keys())(без промежуточного списка, который d.keys()
создает
11

Как насчет этого:

if all([key in foo for key in ["foo","bar"]]):
    # do stuff
    pass
Greg
источник
8
действительно, не только ненужно, но и вредно, поскольку они препятствуют нормальному короткому замыканию all.
Алекс Мартелли
10

Я думаю, что это самый умный и содержательный.

{'key1','key2'} <= my_dict.keys()
Шота Тамура
источник
9

Хотя мне нравится ответ Алекса Мартелли, он не кажется мне пифоническим. То есть я думал, что важная часть того, чтобы быть Pythonic, должна быть легко понятной. С этой целью <=не легко понять.

Хотя это больше символов, использование, issubset()как предложено ответом Карла Фойгтланда, более понятно. Поскольку этот метод может использовать словарь в качестве аргумента, короткое, понятное решение:

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

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

Я хотел бы использовать {'foo', 'bar'}вместо set(('foo', 'bar')), потому что это короче. Тем не менее, это не так понятно, и я думаю, что скобки слишком легко спутать как словарь.

LS
источник
2
Я думаю, что это понятно, если вы понимаете, что это значит.
Боборт
Это в документации как синоним для .issubset(). Я думаю, что в документации по Python по умолчанию это Pythonic.
ingyhere
4

Решение Алекса Мартелли set(queries) <= set(my_dict) - самый короткий код, но, возможно, не самый быстрый. Предположим, Q = len (запросы) и D = len (my_dict).

Требуется O (Q) + O (D), чтобы сделать два набора, а затем (можно надеяться!) Только O (min (Q, D)), чтобы выполнить тест подмножества - при условии, конечно, что поиск набора Python O (1) - это наихудший случай (когда ответ True).

Генераторное решение hughdbrown (et al?) all(k in my_dict for k in queries)Является наихудшим вариантом O (Q).

Осложняющие факторы:
(1) все циклы в основанном на множестве гаджете все выполняются на C-скорости, тогда как любое основанное на гаджете зацикливается на байт-коде.
(2) Вызывающий объект любого гаджета на основе может иметь возможность использовать любое знание вероятности сбоя, чтобы упорядочить элементы запроса соответствующим образом, тогда как гаджет на основе набора не допускает такого контроля.

Как всегда, если важна скорость, рекомендуется провести сравнительный анализ в условиях эксплуатации.

Джон Мачин
источник
1
Генератор был быстрее для всех случаев, которые я пробовал. stackoverflow.com/questions/1285911/…
Джон Ла Рой
2

Вы можете использовать .issubset () , а также

>>> {"key1", "key2"}.issubset({"key1":1, "key2":2, "key3": 3})
True
>>> {"key4", "key2"}.issubset({"key1":1, "key2":2, "key3": 3})
False
>>>
Синан Четинкая
источник
1

Как насчет использования лямбда?

 if reduce( (lambda x, y: x and foo.has_key(y) ), [ True, "foo", "bar"] ): # do stuff
Джинук Ким
источник
2
Этот ответ является единственным функционально-правильным, который будет работать на Python 1.5 с простым изменением (s / True / 1 /) ... но больше ничего не получится. И Истинная вещь была бы лучше в качестве необязательного инициализатора arg, чем втиснута в начало аргумента arg.
Джон Мачин
1

Если вы хотите:

  • также получить значения для ключей
  • проверить более одного диктофона

затем:

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

Не хочу предполагать, что это не то, о чем вы не подумали, но я считаю, что самая простая вещь, как правило, самая лучшая:

if ("foo" in foo) and ("bar" in foo):
    # do stuff
Джейсон Бейкер
источник
1
>>> if 'foo' in foo and 'bar' in foo:
...     print 'yes'
... 
yes

Джейсон, () не нужны в Python.

Хуанхо Конти
источник
3
Тем не менее, они могли бы быть хорошим стилем ... без них мой мозг на языке C ++ всегда задается вопросом, будет ли он интерпретироваться как "if 'foo in (foo и' bar ') в foo:"
Джереми Фризнер,
1
Я понимаю, что они не нужны. Я просто чувствую, что они добавляют ясности в этом случае.
Джейсон Бейкер
0

На мой взгляд, есть два метода, которые легко понять из всех приведенных вариантов. Так что мои главные критерии - это очень читаемый код, а не исключительно быстрый код. Чтобы код был понятен, я предпочитаю использовать следующие возможности:

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

Тот факт, что "var <= var2.keys ()" выполняется быстрее в моем тестировании ниже, я предпочитаю этот.

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
источник
0

В случае определения соответствия только некоторых ключей это работает:

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

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

Еще один вариант найти, если совпадают только некоторые ключи:

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

if any_keys_i_seek & my_dict.keys():
    # code_here
    pass
ingyhere
источник
0

Еще одна опция для определения, все ли ключи находятся в dict:

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
источник
-4
>>> ok
{'five': '5', 'two': '2', 'one': '1'}

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

Это похоже на работу

Прашант Говда
источник
Это умно, и я был уверен, что это не сработало, пока я сам не попробовал. Я подозревал, ()что сначала будет оцениваться результат True, а затем проверяться, если True in ok. Как это на самом деле работает ?!
durden2.0
7
('two' и 'one' and 'five') возвращает значение '
Five