2012-04-27 17 views
7

Necesito filtrar una gran lista varias veces, pero me preocupan tanto la simplicidad del código como la eficacia de la ejecución. Para dar un ejemplo:Optimizar las listas de filtrado en 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()) 

Me preocupa que estoy interactuando sobre la lista de ropa cuando, de hecho, ya se ha reiterado de nuevo. También quiero mantener las dos operaciones de filtro separadas, ya que pertenecen a dos clases diferentes, y no quiero duplicar la primera función lambda en la clase de sombreros.

# 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) 

he estado investigando las funciones del generador, ya que parecía como el camino a seguir, pero no he todavía encontrar la manera.

+0

Si le preocupa el rendimiento, ¿** evaluó ** el rendimiento? –

+0

Lo habría hecho si hubiera pensado que no era obvio. – cammil

+2

"obvio" es una palabra peligrosa cuando se trata de rendimiento. –

Respuesta

23

En primer lugar, la combinación de filter/lambda está en desuso. El estilo de programación funcional actual se describe en Python Functional Programming HOWTO.

En segundo lugar, si le preocupa la eficiencia, en lugar de listas de construcción, debe devolver generators. En este caso, son lo suficientemente simples como para usar 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 si lo prefiere, generadores verdaderos (supuestamente más Pythonic):

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 

Si por alguna razón, a veces se necesita más que listiterator, puede construir la lista por simple colada:

hats_list = list(get_hats()) 

Nota, que anteriormente se no lista de constructo de la ropa, por lo que la eficiencia está cerca de su DUPLICAT Versión del código e

+0

Damnation, vartec. Toma mis votos ganadores –

+0

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

+6

1) La combinación '' filter/lambda'' no está en desuso. 2) PEP 8 aconseja no devolver una expresión del generador, ya que deben consumirse en el mismo ámbito, ya sean creadas, sino un generador regular. 3). Si una lista es lo que se necesita, el OP debe usar una lista de comprensión en lugar de * list * en torno a un genexp. –

4

hacerlo en una sola pasada (pseudocódigo):

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

Para hacerlo en una sola pasada grande y un paso más pequeño (listas por comprensión):

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

Si desea crear la lista completa no tiene sentido usar una expresión de generador para la evaluación perezosa, porque no vas a ser flojo.

Si solo quiere tratar algunas cosas a la vez, o si tiene limitaciones de memoria, use la solución de generador de @ vartec.

+1

es posible que desee corregir el uso de 'thing in things' – okm

+0

@okm: No lo veo, lo siento, ¿puede dar más detalles? –

+0

Quiero decir '[cosa en la ropa si la cosa es un sombrero] 'no es sintácticamente correcto, ¿verdad? – okm

3

Estaba buscando un filtrado similar de listas, pero quería tener un formato ligeramente diferente al presentado aquí.

La llamada anterior get_hats() es buena pero limitada en su reutilización. Estaba buscando algo más como get_hats(get_clothes(all_things)), donde puede especificar una fuente (all_things), y luego el número de filtros get_hats(), get_clothes() que desee.

he encontrado una manera de hacer eso con generadores:

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 

Esto entonces se puede llamar:

get_hats(get_clothes(all_things)) 

Probé las soluciones originales, la solución de VarTec y esta solución adicional para ver la eficiencia, y estaba algo sorprendido por los resultados. Código de la siguiente manera:

instalación:

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 

soluciones originales:

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) 

Mi solución: solución

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 

de 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 
código

Tiempo:

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) 

Resultados:

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 

El generador de expresiones parecen ser ligeramente más rápido, la diferencia en el tiempo entre mi y soluciones de VarTec son probablemente sólo ruido. Pero prefiero la flexibilidad de poder aplicar los filtros requeridos en cualquier orden.