Подтвердить что ты не робот

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

Upshot

В большинстве случаев преобразование container в набор стоит того, по крайней мере, если все его элементы хешируются. Это потому, что in для множеств - O (1), а in для последовательностей - O (n).

С другой стороны, использование тестирования подмножества, вероятно, стоит того, что нужно. Определенно сделайте это, если ваши тестовые элементы уже сохранены в наборе. В противном случае all работает только немного медленнее и не требует дополнительного хранилища. Он также может использоваться с большими генераторами предметов, а иногда и в этом случае обеспечивает значительное ускорение.

Ответ 2

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

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

Ответ 3

Я уверен, что in имеет более высокий приоритет, чем ,, поэтому ваш оператор интерпретируется как 'a', ('b' в ['b'...]), который затем вычисляет значение ' a ', True, так как' b 'находится в массиве.

См. предыдущий ответ о том, как сделать то, что вы хотите.

Ответ 4

Парсер 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

Ответ 5

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

Ответ 6

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

    array = ['b', 'a', 'foo', 'bar']
    all([i in array for i in 'a', 'b'])

ps: Я бы добавил это в качестве комментария, но для этого мне не хватает репутации.

Ответ 7

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

Ответ 8

если вы хотите проверить все ваши входные совпадения,

>>> all(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])

если вы хотите проверить хотя бы одно совпадение,

>>> any(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])

Ответ 9

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

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

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

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

filter(lambda x:x in test_array, orig_array)