Как проверить, все ли элементы списка соответствуют условию?


209

У меня есть список, состоящий из примерно 20000 списков. Я использую третий элемент каждого списка в качестве флага. Я хочу сделать некоторые операции в этом списке, если хотя бы один из элементов флага равен 0, это похоже на:

my_list = [["a", "b", 0], ["c", "d", 0], ["e", "f", 0], .....]

В начале все флаги равны 0. Я использую цикл while, чтобы проверить, равен ли хотя бы один элемент 0:

def check(list_):
    for item in list_:
        if item[2] == 0:
            return True
    return False

Если check(my_list)возвращается True, то я продолжаю работать над своим списком:

while check(my_list):
    for item in my_list:
        if condition:
            item[2] = 1
        else:
            do_sth()

На самом деле, я хотел удалить элемент в my_list при его итерации по нему, но мне не разрешено удалять элементы при его итерации по нему.

Оригинальный my_list не имел флагов:

my_list = [["a", "b"], ["c", "d"], ["e", "f"], .....]

Поскольку я не мог удалить элементы, пока я перебирал их, я изобрел эти флаги. Но он my_listсодержит много элементов, и whileцикл читает все из них в каждом forцикле, и это занимает много времени! Есть ли у вас какие-либо предложения?


3
Похоже, ваша структура данных не идеально подходит для вашей проблемы. Если вы объясните контекст немного больше, возможно, мы могли бы предложить что-то более подходящее.
— uselpa

Может быть, вы могли бы заменить элементы на Noneили []когда вы перебираете список вместо того, чтобы удалять их. Проверка всего списка с помощью 'check () `итерации по всем элементам перед каждым проходом во внутреннем цикле - очень медленный подход.
— Мартино

Ответы:


404

Лучший ответ здесь - это использование all(), встроенное в эту ситуацию. Мы объединяем это с выражением генератора, чтобы получить результат, который вы хотите, чисто и эффективно. Например:

>>> items = [[1, 2, 0], [1, 2, 0], [1, 2, 0]]
>>> all(flag == 0 for (_, _, flag) in items)
True
>>> items = [[1, 2, 0], [1, 2, 1], [1, 2, 0]]
>>> all(flag == 0 for (_, _, flag) in items)
False

Обратите внимание, что all(flag == 0 for (_, _, flag) in items)это прямо эквивалентно all(item[2] == 0 for item in items), в данном случае читать немного приятнее.

И, для примера фильтра, понимание списка (конечно, вы можете использовать выражение генератора, где это уместно):

>>> [x for x in items if x[2] == 0]
[[1, 2, 0], [1, 2, 0]]

Если вы хотите проверить, что хотя бы один элемент равен 0, лучше использовать any()более читаемый:

>>> any(flag == 0 for (_, _, flag) in items)
True

Я виноват в использовании лямбды, все Python не принимает функцию в качестве первого аргумента, как Haskell et. Я также изменил свой ответ на понимание списка. :)
— Hampus Nilsson

3
@HampusNilsson Понимание списка не совпадает с выражением генератора. Как all()и any()короткое замыкание, если, например, первое значение на моем будет оценено как False, all()потерпит неудачу и не проверит больше значений, возвращая False. Ваш пример будет делать то же самое, за исключением того, что сначала будет сгенерирован весь список сравнений, что означает большую обработку для ничего.
— Гарет Латти

14

Если вы хотите проверить, нарушает ли какой-либо элемент в списке условие, используйте all:

if all([x[2] == 0 for x in lista]):
    # Will run if all elements in the list has x[2] = 0 (use not to invert if necessary)

Чтобы удалить все элементы, не соответствующие, используйте filter

# Will remove all elements where x[2] is 0
listb = filter(lambda x: x[2] != 0, listb)

2
Вы можете удалить [...]в , all(...)так как это может создать генератор вместо списка, который не только экономит вам два символа , но и экономит память и время. При использовании генераторов будет рассчитываться только один элемент за раз (прежние результаты будут отбрасываться, так как больше не используются), и, если какой-либо из них окажется False, генератор прекратит вычислять остальные.
— InQβ

7

Вы можете использовать команду itertools следующим образом: она остановится, как только будет выполнено условие, которое не соответствует вашему утверждению. Противоположный метод будет в то же время

for x in itertools.takewhile(lambda x: x[2] == 0, list)
    print x

0

Еще один способ использования itertools.ifilter. Это проверяет правдивость и процесс (используя lambda)

Образец-

for x in itertools.ifilter(lambda x: x[2] == 0, my_list):
    print x

0

этот способ немного более гибкий, чем использование all():

my_list = [[1, 2, 0], [1, 2, 0], [1, 2, 0]]
all_zeros = False if False in [x[2] == 0 for x in my_list] else True
any_zeros = True if True in [x[2] == 0 for x in my_list] else False

или более кратко:

all_zeros = not False in [x[2] == 0 for x in my_list]
any_zeros = 0 in [x[2] for x in my_list]

Не могли бы вы просто сказать all_zeros = False in [x[2] == 0 for x in my_list]или даже 0 in [x[2] for x in my_list]и соответственно для any_zeros? Я не вижу заметного улучшения по сравнению с этим all().
— tripleee

нет, ваша версия - all_zeros = False in [x[2] == 0 for x in my_list]оценивает False, а моя - True. Если вы измените его на all_zeros = not (False in [x[2] == 0 for x in my_list])то, что эквивалентно моему. И 0 in [x[2] for x in my_list], очевидно, только собирается работать any_zeros. Но мне нравится краткость вашей идеи, поэтому я
— обновлю
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.