Может ли Python проверить наличие нескольких значений в списке?
Я хочу проверить, есть ли в списке два или более значений, но я получаю неожиданный результат:
>>> 'a','b' in ['b', 'a', 'foo', 'bar']
('a', True)
Итак, может ли Python проверить наличие нескольких значений одновременно в списке? Что означает этот результат?
12 ответов
Это делает то, что вы хотите, и будет работать почти во всех случаях:
>>> all(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])
True
Выражение 'a','b' in ['b', 'a', 'foo', 'bar']
не работает должным образом, потому что Python интерпретирует его как кортеж:
>>> 'a', 'b'
('a', 'b')
>>> 'a', 5 + 2
('a', 7)
>>> 'a', 'x' in 'xerxes'
('a', True)
Другие опции
Существуют и другие способы выполнить этот тест, но они не будут работать для стольких различных типов входных данных. Как указывает Каби, вы можете решить эту проблему, используя наборы...
>>> set(['a', 'b']).issubset(set(['a', 'b', 'foo', 'bar']))
True
>>> {'a', 'b'} <= {'a', 'b', 'foo', 'bar'}
True
...иногда:
>>> {'a', ['b']} <= {'a', ['b'], 'foo', 'bar'}
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
Наборы могут быть созданы только с помощью хэшируемых элементов. Но генератор выражений all(x in container for x in items)
может обрабатывать практически любой тип контейнера. Единственное требование заключается в том, что container
быть повторяемым (т.е. не генератором). items
может быть любым итеративным.
>>> container = [['b'], 'a', 'foo', 'bar']
>>> items = (i for i in ('a', ['b']))
>>> all(x in [['b'], 'a', 'foo', 'bar'] for x in items)
True
Тесты скорости
Во многих случаях тест подмножества будет быстрее, чем all
, но разница не шокирует - за исключением случаев, когда вопрос не имеет значения, потому что наборы не вариант. Преобразование списков в наборы только для целей такого теста не всегда будет стоить хлопот. А преобразование генераторов в наборы иногда может быть невероятно расточительным, замедляя программы на много порядков.
Вот несколько тестов для иллюстрации. Самая большая разница возникает, когда оба container
а также items
относительно малы В этом случае подход с использованием подмножества примерно на порядок быстрее:
>>> smallset = set(range(10))
>>> smallsubset = set(range(5))
>>> %timeit smallset >= smallsubset
110 ns ± 0.702 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
>>> %timeit all(x in smallset for x in smallsubset)
951 ns ± 11.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Это выглядит как большая разница. Но пока container
это набор, all
все еще отлично подходит для использования в гораздо больших масштабах:
>>> bigset = set(range(100000))
>>> bigsubset = set(range(50000))
>>> %timeit bigset >= bigsubset
1.14 ms ± 13.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit all(x in bigset for x in bigsubset)
5.96 ms ± 37 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Использование тестирования подмножеств все еще быстрее, но только в 5 раз в этом масштабе. Повышение скорости происходит из-за быстрого Python c
реализация set
, но основной алгоритм одинаков в обоих случаях.
Если твой items
По другим причинам они уже сохранены в списке, поэтому вам придется преобразовать их в набор перед использованием метода тестирования подмножеств. Тогда ускорение падает примерно до 2,5x:
>>> %timeit bigset >= set(bigsubseq)
2.1 ms ± 49.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
И если ваш container
является последовательностью, и ее нужно сначала преобразовать, затем ускорение еще меньше:
>>> %timeit set(bigseq) >= set(bigsubseq)
4.36 ms ± 31.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Единственный раз, когда мы получаем катастрофически медленные результаты, это когда мы уходим container
как последовательность:
>>> %timeit all(x in bigseq for x in bigsubseq)
184 ms ± 994 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
И, конечно, мы сделаем это, только если должны. Если все предметы в bigseq
являются хэшируемыми, тогда мы сделаем это вместо этого:
>>> %timeit bigset = set(bigseq); all(x in bigset for x in bigsubseq)
7.24 ms ± 78 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Это просто в 1,66 раза быстрее, чем альтернатива (set(bigseq) >= set(bigsubseq)
, приуроченный выше к 4.36).
Таким образом, тестирование подмножества, как правило, быстрее, но не с невероятным запасом С другой стороны, давайте посмотрим, когда all
быстрее. Что, если items
имеет длину в десять миллионов значений и, вероятно, будет иметь значения, которые не находятся в container
?
>>> %timeit hugeiter = (x * 10 for bss in [bigsubseq] * 2000 for x in bss); set(bigset) >= set(hugeiter)
13.1 s ± 167 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
>>> %timeit hugeiter = (x * 10 for bss in [bigsubseq] * 2000 for x in bss); all(x in bigset for x in hugeiter)
2.33 ms ± 65.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
В этом случае преобразование генератора в набор оказывается невероятно расточительным. set
Конструктор должен потреблять весь генератор. Но короткое замыкание all
гарантирует, что потребляется только небольшая часть генератора, так что это быстрее, чем тест подмножества на четыре порядка.
Это крайний пример, правда. Но, как видно, вы не можете предполагать, что тот или иной подход будет быстрее во всех случаях.
Апшот
Большую часть времени, конвертируя container
к набору того стоит, по крайней мере, если все его элементы являются хэшируемыми. Это потому что in
для наборов O(1), в то время как in
для последовательностей O(n).
С другой стороны, использование подмножественного тестирования, вероятно, стоит только иногда. Обязательно сделайте это, если ваши тестовые задания уже сохранены в наборе. Иначе, all
только немного медленнее, и не требует дополнительного хранилища. Он также может использоваться с большими генераторами предметов, и в этом случае иногда обеспечивает значительное ускорение.
Еще один способ сделать это:
>>> set(['a','b']).issubset( ['b','a','foo','bar'] )
True
Если вы хотите проверить все ваши входные совпадения,
>>> all(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])
если вы хотите проверить хотя бы одно совпадение,
>>> any(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])
Я точно уверен in
имеет более высокий приоритет, чем ,
таким образом, ваше утверждение интерпретируется как "a", ("b" в ["b"...]), которое затем оценивается как "a",True, поскольку "b" находится в массиве.
Смотрите предыдущий ответ о том, как делать то, что вы хотите.
Парсер Python оценил этот оператор как кортеж, где первое значение было 'a'
и второе значение является выражением 'b' in ['b', 'a', 'foo', 'bar']
(который оценивает True
).
Вы можете написать простую функцию, которая делает то, что вы хотите, хотя:
def all_in(candidates, sequence):
for element in candidates:
if element not in sequence:
return False
return True
И назовите это как:
>>> all_in(('a', 'b'), ['b', 'a', 'foo', 'bar'])
True
Я бы сказал, что мы можем даже опустить эти квадратные скобки.
array = ['b', 'a', 'foo', 'bar']
all([i in array for i in 'a', 'b'])
PS: Я бы добавил это в качестве комментария, но мне не хватает представителя для этого.
[x for x in ['a','b'] if x in ['b', 'a', 'foo', 'bar']]
Причина, по которой я думаю, что это лучше, чем выбранный ответ, заключается в том, что вам действительно не нужно вызывать функцию all(). Пустой список оценивается как False в операторах IF, непустой список оценивается как True.
if [x for x in ['a','b'] if x in ['b', 'a', 'foo', 'bar']]:
...Do something...
Пример:
>>> [x for x in ['a','b'] if x in ['b', 'a', 'foo', 'bar']]
['a', 'b']
>>> [x for x in ['G','F'] if x in ['b', 'a', 'foo', 'bar']]
[]
Оба представленных здесь ответа не будут обрабатывать повторяющиеся элементы. Например, если вы проверяете, является ли [1,2,2] подсписком [1,2,3,4], оба вернут True. Это может быть то, что вы хотите сделать, но я просто хотел уточнить. Если вы хотите вернуть false для [1,2,2] в [1,2,3,4], вам необходимо отсортировать оба списка и проверить каждый элемент с движущимся индексом в каждом списке. Просто немного сложнее для цикла.
Любой
В Python3 вы можете использовать пересечение множеств какany
:
>>> {'a','b'} & set(['b', 'a', 'foo', 'bar'])
{'a', 'b'}
>>> {'a','b'} & set(['b', 1, 'foo', 'bar'])
{'b'}
конечно, вы можете обернуть результат в bool дляTrue
/False
ценности:
>>> bool({'a','b'} & set(['b', 1, 'foo', 'bar']))
True
>>> bool({'c'} & set(['b', 1, 'foo', 'bar']))
False
Все
Использование подмножества `is:
>>> {'a','b'}.issubset(set(['b', 'a', 'foo', 'bar']))
True
>>> {'a','b'}.issubset(set(['b', 1, 'foo', 'bar']))
False
Примечания
-
bool()
превращается в логическое значение -
issubset()
ищет множество, которое является полным подмножеством другого множества -
&
может использоваться для пересечения (любых) множеств
Примеры можно очистить с помощью переменных:
test = {'a','b'}
values = set(['b', 'a', 'foo', 'bar'])
# Any
test & values # {'a', 'b'}
bool(test & values) # True
# All
test.issubset(values) # True
Вот как я это сделал:
A = ['a','b','c']
B = ['c']
logic = [(x in B) for x in A]
if True in logic:
do something
Как ты можешь быть питоном без лямбд! .. не принимать всерьез.. но этот способ тоже работает
orig_array = [ ..... ]
test_array = [ ... ]
filter(lambda x:x in test_array, orig_array) == test_array
пропустите конечную часть, если вы хотите проверить, есть ли какие-либо значения в массиве:
filter(lambda x:x in test_array, orig_array)
# This is to extract all count of all combinations inside list of
# list
import itertools
l = [[1,2,3],[6,5,4,3,7,2],[4,3,2,9],[6,7],[5,1,0],[6,3,2,7]]
els = list(set(b for a in l for b in a))
sol = {}
def valid(p):
for s in l:
if set(p).issubset(set(s)):
if p in sol.keys():
sol[p] += 1
else:
sol[p] = 1
for c in itertools.combinations(els, 2):
valid(c)
# {(0, 1): 1,
# (0, 5): 1,
# (1, 2): 1,
# (1, 3): 1,
# (1, 5): 1,
# (2, 3): 4,
# (2, 4): 2,
# (2, 5): 1,
# (2, 6): 2,
# (2, 7): 2,
# (2, 9): 1,
# (3, 4): 2,
# (3, 5): 1,
# (3, 6): 2,
# (3, 7): 2,
# (3, 9): 1,
# (4, 5): 1,
# (4, 6): 1,
# (4, 7): 1,
# (4, 9): 1,
# (5, 6): 1,
# (5, 7): 1,
# (6, 7): 3}