Может ли 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}   
Другие вопросы по тегам