Ответ 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
работает только немного медленнее и не требует дополнительного хранилища. Он также может использоваться с большими генераторами предметов, а иногда и в этом случае обеспечивает значительное ускорение.