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


121

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

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

Отже, чи може Python перевірити приналежність кількох значень одночасно до списку? Що це означає?

Відповіді:


197

Це робить те, що ви хочете, і буде працювати майже у всіх випадках:

>>> 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 разів за цією шкалою. Збільшення швидкості пояснюється швидкою cреалізацією Python set, але основний алгоритм в обох випадках однаковий.

Якщо ви itemsвже збережені в списку з інших причин, вам доведеться перетворити їх у набір, перш ніж використовувати підхід підмножини тесту. Потім швидкість падає приблизно до 2,5 разів:

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


62

Ще один спосіб зробити це:

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

21
Веселий факт: set(['a', 'b']) <= set(['b','a','foo','bar'])це ще один спосіб написання того ж самого, і виглядає «матьє».
Кірк Штраузер

8
Станом на Python 2.7 ви можете використовувати{'a', 'b'} <= {'b','a','foo','bar'}
Viktor Stískala

11

Я впевнений, що inмає більший пріоритет, ніж ,тому, що ваше твердження трактується як 'a', ('b' in ['b' ...]), яке потім оцінюється, 'a', Trueоскільки 'b'є в масиві.

Дивіться попередню відповідь, як робити те, що ви хочете.


7

Якщо ви хочете перевірити всі ваші відповідність ,

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

якщо ви хочете перевірити хоча б одну відповідність ,

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

3

Аналізатор 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

2
[x for x in ['a','b'] if x in ['b', 'a', 'foo', 'bar']]

Причиною, на яку я вважаю, що це краще, ніж обрана відповідь, є те, що вам дійсно не потрібно викликати функцію 'all ()'. Порожній список оцінюється на Неправильно в операторах IF, непустий список оцінюється на Істинне.

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

Я б сказав, що ми можемо навіть залишити ці квадратні дужки.

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

0

Обидва відповіді, представлені тут, не оброблять повторювані елементи. Наприклад, якщо ви тестуєте, чи [1,2,2] є підспілом [1,2,3,4], обидва повернуть True. Це може бути те, що ви хочете зробити, але я просто хотів уточнити. Якщо ви хочете повернути помилкове значення для [1,2,2] в [1,2,3,4], вам потрібно буде сортувати обидва списки і перевірити кожен елемент з переміщувальним індексом у кожному списку. Просто трохи складніше для циклу.


1
"обидва"? Є більше двох відповідей. Ви мали на увазі, що всі відповіді страждають від цієї проблеми, або лише дві відповіді (і якщо так, то які)?
Wipqozn

-1

як ти можеш бути пітоніком без лямбдів! .. не сприймати серйозно .. але і цей спосіб працює:

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

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

залиште кінцеву частину, якщо ви хочете перевірити, чи є якесь значення у масиві:

filter(lambda x:x in test_array, orig_array)

1
Просто підкресліть, що це не буде працювати так, як задумано в Python 3, де filterє генератором. Вам потрібно буде обернути його, listякщо ви хочете насправді отримати результат, який ви можете протестувати з ==булевим контекстом або в ньому (щоб побачити, чи він порожній). Використання розуміння списку або виразу генератора в anyабо allє кращим.
Blckknght

-1

Ось як я це зробив:

A = ['a','b','c']
B = ['c']
logic = [(x in B) for x in A]
if True in logic:
    do something
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.