Як перевірити, чи всі елементи списку відповідають умові?


208

У мене є список, який складається приблизно з 20000 списків. Я використовую 3-й елемент кожного списку як прапор. Я хочу виконати деякі операції в цьому списку до тих пір, поки щонайменше прапор одного елемента дорівнює 0, це так:

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

На початку всі прапори дорівнюють 0. Я використовую цикл час, щоб перевірити, чи хоча б прапор одного елемента дорівнює 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 = [["a", "b"], ["c", "d"], ["e", "f"], .....]

Оскільки я не міг видалити елементи під час повторення, я вигадав ці прапори. Але він my_listмістить багато елементів, і whileцикл читає їх у кожному forциклі, і це забирає багато часу! Чи є у вас якісь пропозиції?


3
Схоже, ваша структура даних не ідеальна для вашої проблеми. Якщо ви трохи більше пояснили контекст, можливо, ми могли б запропонувати щось більш відповідне.
uselpa

Можливо, ви могли б замінити елементи на Noneабо []під час перегляду списку замість того, щоб видаляти їх. Перевірка всього списку за допомогою повторення "check ()" над усіма елементами перед кожним проходом у внутрішній цикл є дуже повільним підходом.
мартіно

Відповіді:


402

Найкращою відповіддю тут є використання 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. ін., я змінив свою відповідь і на розуміння списку. :)
Гампус Нільссон

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.