2012-04-27 17 views
7

Ho bisogno di filtrare un elenco di grandi dimensioni più volte, ma mi preoccupo sia della semplicità del codice che dell'efficienza di esecuzione. Per fare un esempio:Ottimizza gli elenchi di filtri in Python 2.7

all_things # huge collection of all things 

# inefficient but clean code 
def get_clothes(): 
    return filter(lambda t: t.garment, allThings) 

def get_hats(): 
    return filter(lambda t: t.headgear, get_clothes()) 

mi riguarda che sto iterazione oltre l'elenco vestiti quando in realtà è già stata iterata sopra. Voglio anche tenere separate le due operazioni di filtro, poiché appartengono a due classi diverse, e non voglio duplicare la prima funzione lambda nella classe dei cappelli.

# efficient but duplication of code 
def get_clothes(): 
    return filter(lambda t: t.garment, allThings) 

def get_hats(): 
    return filter(lambda t: t.headgear and t.garment, allThings) 

ho indagato funzioni del generatore, come sembravano come la strada da percorrere, ma non ho ancora capire come.

+0

Se sei preoccupato per le prestazioni, hai ** verificato ** le prestazioni? –

+0

Lo farei se pensassi che non fosse ovvio. – cammil

+2

"ovvio" è una parola pericolosa quando si tratta di prestazioni. –

risposta

23

Prima di tutto usando la combinazione filter/lambda sarà deprecato. Lo stile di programmazione funzionale attuale è descritto in Python Functional Programming HOWTO.

In secondo luogo, se si è interessati all'efficienza, piuttosto che creare elenchi, è necessario restituire generators. In questo caso sono abbastanza semplici da usare generator expressions.

def get_clothes(): 
    return (t for t in allThings if t.garment) 

def get_hats(): 
    return (t for t in get_clothes() if t.headgear) 

O se si preferisce, veri generatori (presumibilmente più divinatorio):

def get_clothes(): 
    for t in allThings: 
     if t.garment: 
      yield t 

def get_hats(): 
    for t in get_clothes(): 
     if t.headgear: 
      yield t 

Se per qualche motivo, a volte è necessario list piuttosto che iterator, è possibile costruire la lista per semplice casting:

hats_list = list(get_hats()) 

nota, che al di sopra sarà non lista costrutto di vestiti, così l'efficienza è vicino al vostro DUPLICAT e versione del codice.

+0

Dannazione, vartec. Prendi i miei voti. –

+0

@ Li-aungYip: sorry dude ;-) – vartec

+6

1) La combinazione '' filter/lambda'' non è deprecata. 2) PEP 8 sconsiglia di restituire un'espressione di generatore - quelli dovrebbero essere consumati nello stesso campo di applicazione se sono stati creati - usato invece un generatore regolare. 3). Se una lista è ciò che è necessario, l'OP dovrebbe usare una comprensione di lista piuttosto che una * lista * avvolta attorno a un genexp. –

4

di farlo in una sola passata (pseudocodice):

clothes = list() 
hats = list() 
for thing in things: 
    if thing is a garment: 
     clothes.append(thing) 
     if thing is a hat: 
      hats.append(thing) 

Per farlo in un grande passaggio e un passaggio più piccolo (list comprehension):

clothes = [ x for x in things if x is garment ] 
hats = [ x for x in clothes if x is hat ] 

Se si desidera creare L'intero elenco non ha senso usare un'espressione di generatore per la valutazione pigra, perché non sarai pigro.

Se si desidera gestire solo alcune cose alla volta o se si dispone di vincoli di memoria, utilizzare la soluzione di generatore di @ vartec.

+1

potresti voler risolvere l'uso di 'thing in things' – okm

+0

@okm: non vederlo, mi dispiace - puoi elaborare? –

+0

Intendo '[cosa in vestiti se la cosa è cappello]' non è sintatticamente corretto, non è vero? – okm

3

Cercavo un filtro simile delle liste ma volevo un formato leggermente diverso da quello presentato qui.

La chiamata sopra get_hats() è buona ma limitata nel suo riutilizzo. Stavo cercando qualcosa di più come get_hats(get_clothes(all_things)), dove è possibile specificare una fonte (all_things) e quindi come pochi o tanti livelli di filtri get_hats(), get_clothes() come si desidera.

Ho trovato un modo per farlo con generatori:

def get_clothes(in_list): 
    for item in in_list: 
     if item.garment: 
      yield item 

def get_hats(in_list): 
    for item in in_list: 
     if item.headgear: 
      yield item 

Questo può quindi essere chiamato da:

get_hats(get_clothes(all_things)) 

Ho provato le soluzioni originali, la soluzione di Vartec e questa soluzione ulteriore per vedere la efficienza, ed è stato in qualche modo sorpreso dai risultati. Codice come segue:

installazione:

class Thing: 
    def __init__(self): 
     self.garment = False 
     self.headgear = False 

all_things = [Thing() for i in range(1000000)] 

for i, thing in enumerate(all_things): 
    if i % 2 == 0: 
     thing.garment = True 
    if i % 4 == 0: 
     thing.headgear = True 

soluzioni originali:

def get_clothes(): 
    return filter(lambda t: t.garment, all_things) 

def get_hats(): 
    return filter(lambda t: t.headgear, get_clothes()) 

def get_clothes2(): 
    return filter(lambda t: t.garment, all_things) 

def get_hats2(): 
    return filter(lambda t: t.headgear and t.garment, all_things) 

La mia soluzione: soluzione

def get_clothes3(in_list): 
    for item in in_list: 
     if item.garment: 
      yield item 

def get_hats3(in_list): 
    for item in in_list: 
     if item.headgear: 
      yield item 

di Vartec:

def get_clothes4(): 
    for t in all_things: 
     if t.garment: 
      yield t 

def get_hats4(): 
    for t in get_clothes4(): 
     if t.headgear: 
      yield t 
Codice

Timing:

import timeit 

print 'get_hats()' 
print timeit.timeit('get_hats()', 'from __main__ import get_hats', number=1000) 

print 'get_hats2()' 
print timeit.timeit('get_hats2()', 'from __main__ import get_hats2', number=1000) 

print '[x for x in get_hats3(get_clothes3(all_things))]' 
print timeit.timeit('[x for x in get_hats3(get_clothes3(all_things))]', 
        'from __main__ import get_hats3, get_clothes3, all_things', 
        number=1000) 

print '[x for x in get_hats4()]' 
print timeit.timeit('[x for x in get_hats4()]', 
        'from __main__ import get_hats4', number=1000) 

Risultati:

get_hats() 
379.334653854 
get_hats2() 
232.768362999 
[x for x in get_hats3(get_clothes3(all_things))] 
214.376812935 
[x for x in get_hats4()] 
218.250688076 

Il generatore sembrano espressioni per essere leggermente più veloce, la differenza di tempo tra le soluzioni mia e di Vartec sono probabilmente solo rumore. Ma preferisco la flessibilità di poter applicare qualsiasi filtro richiesto in qualsiasi ordine.