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

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цикле, и это занимает много времени! Есть ли у вас какие-либо предложения?

alwbtc
источник
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
SIslam
источник
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]
mulllhausen
источник
Не могли бы вы просто сказать 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. Но мне нравится краткость вашей идеи, поэтому я
обновлю