python: pobierz liczbę elementów z listy (sekwencji) z określonym warunkiem

85

Zakładając, że mam listę z ogromną liczbą pozycji.

l = [ 1, 4, 6, 30, 2, ... ]

Chcę uzyskać liczbę pozycji z tej listy, dla których pozycja powinna spełniać określony warunek. Moją pierwszą myślą było:

count = len([i for i in l if my_condition(l)])

Ale jeśli lista filtrowana my_condition () ma również dużą liczbę elementów, myślę, że tworzenie nowej listy dla przefiltrowanych wyników jest po prostu stratą pamięci. Dla wydajności, IMHO, powyższe wezwanie nie może być lepsze niż:

count = 0
for i in l:
    if my_condition(l):
        count += 1

Czy jest jakiś funkcjonalny sposób na uzyskanie liczby elementów spełniających określony warunek bez generowania listy tymczasowej?

Z góry dziękuję.

cinsk
źródło
3
Wybór między generatorami i listami to wybór między czasem wykonania a zużyciem pamięci. Zdziwiłbyś się, jak często wyniki są sprzeczne z intuicją, jeśli profilujesz kod. Przedwczesna optymalizacja jest źródłem wszelkiego zła.
Paulo Scardine

Odpowiedzi:

103

Możesz użyć wyrażenia generatora :

>>> l = [1, 3, 7, 2, 6, 8, 10]
>>> sum(1 for i in l if i % 4 == 3)
2

lub nawet

>>> sum(i % 4 == 3 for i in l)
2

który wykorzystuje fakt, że int(True) == 1.

Alternatywnie możesz użyć itertools.imap(python 2) lub po prostu map(python 3):

>>> def my_condition(x):
...     return x % 4 == 3
... 
>>> sum(map(my_condition, l))
2
DSM
źródło
1
@mgilson: Nie sądzę, żeby kiedykolwiek dokonywał tych obliczeń - startdomyślnie 0, więc pierwszy dodatek brzmi True + 0, nie?
DSM
4
Tak. Może powinienem wyrazić się bardziej jasno ... Nie ma znaczenia, co int(True)jest. int("1") == 1ale to nie znaczy, że możesz to zrobić "1" + 0. Liczy się sposób, w jaki Python ocenia integer + Truelub integer + False.
mgilson
2
@mgilson: hmm, ok, przekonałeś mnie.
DSM
4
Chodzi o to, że booljest to podklasa int, więc możesz łatwo dodawać boole i ints ( Trueo wartości 1 i False0).
mgilson
Cóż, właśnie do tego doszedłem int(True) == 1, wspominając , ale twój punkt widzenia int("1") == 1dowodzi, że skrócenie tego w ten sposób może oznaczać rzeczy, które nie są prawdą.
DSM
21

Chcesz raczej zrozumienia generatora niż listy tutaj.

Na przykład,

l = [1, 4, 6, 7, 30, 2]

def my_condition(x):
    return x > 5 and x < 20

print sum(1 for x in l if my_condition(x))
# -> 2
print sum(1 for x in range(1000000) if my_condition(x))
# -> 14

Lub użyj itertools.imap(chociaż wydaje mi się, że jawna lista i wyrażenia generatora wyglądają nieco bardziej w Pythonie).

Zauważ, że chociaż nie jest to oczywiste z sumprzykładu, możesz ładnie komponować wyrażenia generatora. Na przykład,

inputs = xrange(1000000)      # In Python 3 and above, use range instead of xrange
odds = (x for x in inputs if x % 2)  # Pick odd numbers
sq_inc = (x**2 + 1 for x in odds)    # Square and add one
print sum(x/2 for x in sq_inc)       # Actually evaluate each one
# -> 83333333333500000

Fajną rzeczą w tej technice jest to, że można określić koncepcyjnie oddzielne kroki w kodzie bez wymuszania oceny i przechowywania w pamięci do czasu oceny końcowego wyniku.

JohnJ
źródło
10

Można to również zrobić za pomocą, reducejeśli wolisz programowanie funkcjonalne

reduce(lambda count, i: count + my_condition(i), l, 0)

W ten sposób wykonujesz tylko 1 przebieg i nie jest generowana lista pośrednia.

Mały Uczeń Fermata
źródło
7

możesz zrobić coś takiego:

l = [1,2,3,4,5,..]
count = sum(1 for i in l if my_condition(i))

co po prostu dodaje 1 do każdego elementu spełniającego warunek.

Jsdodgers
źródło
2
from itertools import imap
sum(imap(my_condition, l))
kkonrad
źródło
2
imapnie jest dostępny w aktualnym Pythonie.
Torsten Bronger