Может ли Python проверить принадлежность нескольких значений в списке?


Я хочу проверить, если два или более значений имеют членство в списке, но я получаю неожиданный результат:

>>> 'a','b' in ['b', 'a', 'foo', 'bar']
('a', True)

Итак, может ли Python проверить принадлежность нескольких значений сразу в списке? Что означает этот результат?

8 75

8 ответов:

это то, что вы хотите, и будет работать почти во всех случаях:

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

Другие Функции

есть и другие способы выполнения этого теста, но они не будут работать для такого количества различных видов входных данных. Как Kabie указывает, что вы можете решить эту проблему с помощью наборы...

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

наборы могут быть созданы только с элементами hashable. Но выражение генератора all(x in container for x in items) может обрабатывать практически любой тип контейнера. Единственное требование-это container быть повторной итерации (т. е. не генератор). items может быть любой iterable вообще.

>>> 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 раз в этом масштабе. Повышение скорости происходит из-за быстрого питона c-поддержал реализации set, но основной алгоритм одинаков в обоих случаях.

если items уже сохранены в списке по другим причинам, затем вам нужно будет преобразовать их в набор перед использованием подхода теста подмножества. Затем ускорение падает примерно до 2.5 x:

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

и, конечно, мы сделаем это только в случае необходимости. Если все элементы!--26--> это hashable, затем мы сделаем это вместо этого:

>>> %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 x быстрее, чем альтернатива (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 только немного медленнее, и не требует никакого дополнительного хранения. Его можно также использовать с большими генераторами деталей, и иногда обеспечивает a массовое ускорение в этом случае.

другой способ сделать это:

>>> set(['a','b']).issubset( ['b','a','foo','bar'] )
True

я почти уверен in имеет более высокий приоритет, чем , таким образом, ваше утверждение интерпретируется как 'a', ('b' в ['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

оба ответа, представленные здесь, не будут обрабатывать повторяющиеся элементы. Например, если вы проверяете, является ли [1,2,2] подсписком [1,2,3,4], оба вернут True. Что может быть то, что вы хотите сделать, но я просто хотел уточнить. Если вы хотите вернуть false для [1,2,2] в [1,2,3,4], вам нужно будет отсортировать оба списка и проверить каждый элемент с движущимся индексом в каждом списке. Просто немного сложнее для цикла.

Я бы сказал, что мы можем даже оставить эти квадратные скобки.

    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']]
[]

как можно быть данные без лямбды! .. не стоит воспринимать это всерьез .. но этот способ тоже работает:

orig_array = [ ..... ]
test_array = [ ... ]

filter(lambda x:x in test_array, orig_array) == test_array

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

filter(lambda x:x in test_array, orig_array)