¿Cuál es la mejor manera, tanto desde el punto de vista estético como del rendimiento, de dividir una lista de elementos en varias listas basadas en un condicional? El equivalente de:
good = [x for x in mylist if x in goodvals]
bad = [x for x in mylist if x not in goodvals]
¿Hay alguna manera más elegante de hacer esto?
Actualización: aquí está el caso de uso real, para explicar mejor lo que estoy tratando de hacer:
# files looks like: [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi'), ... ]
IMAGE_TYPES = ('.jpg','.jpeg','.gif','.bmp','.png')
images = [f for f in files if f[2].lower() in IMAGE_TYPES]
anims = [f for f in files if f[2].lower() not in IMAGE_TYPES]
str.split()
, para dividir la lista en una colección ordenada de sublistas consecutivas. Por ejemplosplit([1,2,3,4,5,3,6], 3) -> ([1,2],[4,5],[6])
, en lugar de dividir los elementos de una lista por categoría.IMAGE_TYPES = set('.jpg','.jpeg','.gif','.bmp','.png')
. n (1) en lugar de n (o / 2), prácticamente sin diferencias en la legibilidad.Respuestas:
¡Ese código es perfectamente legible y extremadamente claro!
De nuevo, esto está bien!
Puede haber ligeras mejoras en el rendimiento al usar conjuntos, pero es una diferencia trivial, y encuentro que la comprensión de la lista es mucho más fácil de leer, y no tiene que preocuparse por el desorden del orden, la eliminación de duplicados, etc.
De hecho, puedo dar otro paso "hacia atrás" y simplemente usar un bucle for simple:
Una lista de comprensión o uso
set()
está bien hasta que necesite agregar alguna otra verificación u otro poco de lógica; digamos que desea eliminar todos los archivos JPEG de 0 bytes, simplemente agregue algo como ...fuente
[x for x in blah if ...]
,lambda
es decir , es torpe y limitado ... Se siente como conducir el auto más genial de 1995 hoy. No es lo mismo que en aquel entonces.fuente
good.append(x) if x in goodvals else bad.append(x)
Es más legible.x
, puedes convertirlo en unoappend
solo:for x in mylist: (good if isgood(x) else bad).append(x)
(bad.append, good.append)
(good if x in goodvals else bad).append(x)
Aquí está el enfoque de iterador perezoso:
Evalúa la condición una vez por elemento y devuelve dos generadores, primero arrojando valores de la secuencia donde la condición es verdadera, y la otra donde es falsa.
Debido a que es vago, puede usarlo en cualquier iterador, incluso uno infinito:
Por lo general, aunque el enfoque de devolución de lista no perezosa es mejor:
Editar: para su caso de uso más específico de dividir elementos en diferentes listas por alguna tecla, aquí hay una función genérica que hace eso:
Uso:
fuente
[ x for x in my_list if ExpensiveOperation(x) ]
lleva mucho tiempo ejecutarlo, ¡ciertamente no querrás hacerlo dos veces!tee
almacena todos los valores entre los iteradores que devuelve, por lo que realmente no ahorrará memoria si realiza un bucle sobre un generador completo y luego el otro.El problema con todas las soluciones propuestas es que escaneará y aplicará la función de filtrado dos veces. Haría una pequeña función simple como esta:
De esa manera, no está procesando nada dos veces y tampoco está repitiendo código.
fuente
Mi opinión sobre eso. Propongo una función perezosa de un solo paso
partition
, que conserva el orden relativo en las subsecuencias de salida.1. Requisitos
Supongo que los requisitos son:
i
)filter
ogroupby
)2.
split
bibliotecaMi
partition
función (presentada a continuación) y otras funciones similares lo han convertido en una pequeña biblioteca:Se puede instalar normalmente a través de PyPI:
Para dividir una lista basada en la condición, use la
partition
función:3.
partition
función explicadaInternamente, necesitamos construir dos subsecuencias a la vez, por lo que consumir solo una secuencia de salida obligará a calcular la otra también. Y debemos mantener el estado entre las solicitudes de los usuarios (elementos procesados en la tienda pero aún no solicitados). Para mantener el estado, utilizo dos colas de doble extremo (
deques
):SplitSeq
clase se encarga de la limpieza:La magia sucede en su
.getNext()
método. Es casi como.next()
los iteradores, pero permite especificar qué tipo de elemento queremos esta vez. Detrás de escena no descarta los elementos rechazados, sino que los coloca en una de las dos colas:Se supone que el usuario final debe usar la
partition
función. Toma una función de condición y una secuencia (comomap
ofilter
), y devuelve dos generadores. El primer generador construye una subsecuencia de elementos para los cuales se cumple la condición, el segundo genera la subsecuencia complementaria. Los iteradores y generadores permiten la división perezosa de secuencias incluso largas o infinitas.Elegí la función de prueba como el primer argumento para facilitar la aplicación parcial en el futuro (similar a cómo
map
yfilter
tener la función de prueba como primer argumento).fuente
Básicamente me gusta el enfoque de Anders, ya que es muy general. Aquí hay una versión que pone el categorizador primero (para que coincida con la sintaxis del filtro) y usa un defaultdict (supuesto importado).
fuente
Primero vaya (pre-OP-edit): Use sets:
Eso es bueno tanto para la legibilidad (IMHO) como para el rendimiento.
Segundo paso (post-OP-edit):
Cree su lista de buenas extensiones como un conjunto:
y eso aumentará el rendimiento. De lo contrario, lo que tienes me parece bien.
fuente
goodvals
.itertools.groupby casi hace lo que quiere, excepto que requiere que los elementos se ordenen para garantizar que obtenga un rango contiguo único, por lo que primero debe ordenar por su clave (de lo contrario, obtendrá múltiples grupos intercalados para cada tipo). p.ej.
da:
Similar a las otras soluciones, la función clave se puede definir para dividirse en cualquier número de grupos que desee.
fuente
Esta respuesta elegante y concisa de @dansalmo apareció oculta en los comentarios, así que solo la vuelvo a publicar aquí como respuesta para que pueda obtener la prominencia que merece, especialmente para los nuevos lectores.
Ejemplo completo:
fuente
Si quieres hacerlo en estilo FP:
No es la solución más fácil de leer, pero al menos itera a través de mylist solo una vez.
fuente
Personalmente, me gusta la versión que citó, suponiendo que ya tenga una lista de
goodvals
cosas por ahí. Si no, algo como:Por supuesto, eso es muy similar a usar una comprensión de lista como lo hizo originalmente, pero con una función en lugar de una búsqueda:
En general, encuentro que la estética de las comprensiones de listas es muy agradable. Por supuesto, si realmente no necesita preservar el pedido y no necesita duplicados, el uso de los métodos
intersection
ydifference
en los conjuntos también funcionaría bien.fuente
filter(lambda x: is_good(x), mylist)
se puede reducir afilter(is_good, mylist)
Creo que una generalización de dividir un iterable basado en N condiciones es útil
Por ejemplo:
Si el elemento puede satisfacer múltiples condiciones, elimine la ruptura.
fuente
Mira esto
fuente
¡A veces, parece que la comprensión de la lista no es lo mejor para usar!
Hice una pequeña prueba basada en la respuesta que la gente dio a este tema, probé en una lista generada al azar. Aquí está la generación de la lista (probablemente haya una mejor manera de hacerlo, pero no es el punto):
Y aquí vamos
Usando la función cmpthese , el mejor resultado es la respuesta dbr:
fuente
Otra solución más a este problema. Necesitaba una solución lo más rápida posible. Eso significa solo una iteración sobre la lista y preferiblemente O (1) para agregar datos a una de las listas resultantes. Esto es muy similar a la solución provista por sastanin , excepto que es mucho más corta:
Luego, puede usar la función de la siguiente manera:
Si no está bien con el resultado
deque
de objeto, puede convertir fácilmente alist
,set
, lo que quiera (por ejemplolist(lower)
). La conversión es mucho más rápida, esa construcción de las listas directamente.Este método mantiene el orden de los elementos, así como cualquier duplicado.
fuente
Por ejemplo, dividir la lista por pares e impares
O en general:
Ventajas:
Desventajas
fuente
Inspirado por la excelente respuesta de @ gnibbler (¡pero conciso!) , Podemos aplicar ese enfoque para mapear en particiones múltiples:
Entonces
splitter
se puede usar de la siguiente manera:Esto funciona para más de dos particiones con un mapeo más complicado (y también en iteradores):
O usando un diccionario para mapear:
fuente
agregar devuelve Ninguno, por lo que funciona.
fuente
Para el rendimiento, intente
itertools
.Ver itertools.ifilter o imap.
fuente
[x for x in a if x > 50000]
en una simple matriz de 100000 enteros (a través de random.shuffle),filter(lambda x: x> 50000, a)
tomará 2 veces más,ifilter(lambda x: x> 50000, a); list(result)
toma aproximadamente 2.3 veces más largo. Extraño pero cierto.A veces no necesitarás esa otra mitad de la lista. Por ejemplo:
fuente
Esta es la forma más rápida.
Utiliza
if else
, (como la respuesta de dbr) pero crea primero un conjunto. Un conjunto reduce el número de operaciones de O (m * n) a O (log m) + O (n), lo que resulta en un aumento del 45% en la velocidad.Un poco mas corto:
Resultados de referencia:
El código de referencia completo para Python 3.7 (modificado de FunkySayu):
fuente
Si insiste en la inteligencia, podría tomar la solución de Winden y un poco de inteligencia espuria:
fuente
Ya hay bastantes soluciones aquí, pero otra forma de hacerlo sería:
Itera la lista solo una vez, y se ve un poco más pitón y, por lo tanto, legible para mí.
fuente
Adoptaría un enfoque de 2 pasos, separando la evaluación del predicado del filtrado de la lista:
Lo bueno de esto, en cuanto al rendimiento (además de evaluar
pred
solo una vez en cada miembro deiterable
), es que saca mucha lógica del intérprete y lo lleva a un código de mapeo e iteración altamente optimizado. Esto puede acelerar la iteración sobre iterables largos, como se describe en esta respuesta .En cuanto a la expresividad, aprovecha los modismos expresivos como la comprensión y el mapeo.
fuente
solución
prueba
fuente
Si no le importa usar una biblioteca externa, hay dos que sé que implementan de manera nativa esta operación:
iteration_utilities.partition
:more_itertools.partition
fuente
No estoy seguro si este es un buen enfoque, pero también se puede hacer de esta manera
fuente
Si la lista está compuesta por grupos y separadores intermitentes, puede usar:
Uso:
fuente
Agradable cuando la condición es más larga, como en su ejemplo. El lector no tiene que descubrir la condición negativa y si captura todos los demás casos.
fuente
Otra respuesta, corta pero "malvada" (para efectos secundarios de comprensión de la lista).
fuente