Parand
Tengo un código como:
good = [x for x in mylist if x in goodvals]
bad = [x for x in mylist if x not in goodvals]
El objetivo es dividir el contenido de mylist
en otras dos listas, en función de si cumplen o no una condición.
¿Cómo puedo hacer esto con más elegancia? ¿Puedo evitar hacer dos iteraciones separadas sobre mylist
? ¿Puedo mejorar el rendimiento al hacerlo?
dbr
good = [x for x in mylist if x in goodvals] bad = [x for x in mylist if x not in goodvals]
¿Cómo puedo hacer esto con más elegancia?
Ese código ya es perfectamente elegante.
Puede haber ligeras mejoras de rendimiento al usar set
s, pero la diferencia es trivial. set
los enfoques basados también descartarán los duplicados y no preservarán el orden de los elementos. También encuentro que la lista de comprensión es mucho más fácil de leer.
De hecho, podríamos incluso más simplemente usar un for
bucle:
good, bad = [], []
for x in mylist:
if x in goodvals:
good.append(f)
else:
bad.append(f)
Este enfoque facilita la adición de lógica adicional. Por ejemplo, el código se modifica fácilmente para descartar None
valores:
good, bad = [], []
for x in mylist:
if x is None:
continue
if x in goodvals:
good.append(f)
else:
bad.append(f)
-
¿No hay una forma de comprensión de la lista sin tener que recorrer la lista dos veces?
– balki
21 de julio de 2012 a las 15:42
-
El problema es que esto viola el principio DRY. Sería bueno si hubiera una mejor manera de hacer esto.
– Antimonio
9 mayo 2013 a las 18:03
-
Una vez que aumenta el apetito por la programación funcional (Haskell) o el estilo funcional (LINQ), comenzamos a oler Python para su edad:
[x for x in blah if ...]
– detallado,lambda
es torpe y limitado… Se siente como conducir el mejor auto de 1995 en la actualidad. No es lo mismo que entonces.– Tomasz Gandor
24 mayo 2015 a las 13:52
-
@TomaszGandor FTR, Haskell es más viejo que Python (y de hecho influyó en su diseño). Creo que la sintaxis para la comprensión de listas y las lambdas se mantuvo deliberadamente un poco detallada, tal vez para desalentar su uso excesivo. Lo cual es un poco arriesgado… por mucho que me guste Haskell, puedo ver por qué mucha gente encuentra Python generalmente más legible.
– a la izquierda
30/09/2015 a las 20:04
-
el bucle for simple es la mejor manera de hacer esto… un solo bucle, muy claro y legible
– Anentrópico
21 de abril de 2016 a las 10:04
-
Probablemente tengas razón en que esto viola el principio YAGNI. Se basa en la suposición de que la cantidad de listas diferentes en las que se pueden dividir las cosas crecerá en el futuro.
– Hormigas Aasma
4 de junio de 2009 a las 15:33
-
Puede ser mucho código, pero si
[ x for x in my_list if ExpensiveOperation(x) ]
lleva mucho tiempo ejecutarse, ¡ciertamente no querrás hacerlo dos veces!– dash-tom-bang
10 de enero de 2013 a las 22:29
-
+1 por ofrecer múltiples variaciones, incluidas las basadas en iteradores y una solución específica “en X”. El OP “in goodvals” puede ser pequeño, pero reemplazarlo con un diccionario muy grande o un predicado costoso podría ser costoso. También reduce la necesidad de escribir la lista de comprensión dos veces. en todos lados es necesario, lo que reduce la probabilidad de introducir errores tipográficos o de usuario. Buena solución. ¡Gracias!
– cod3monk3y
16 de noviembre de 2013 a las 21:08
-
Tenga en cuenta que
tee
almacena todos los valores entre los iteradores que devuelve, por lo que realmente no ahorrará memoria si recorre un generador completo y luego el otro.– John LaRooy
8 de agosto de 2014 a las 4:51
viento
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:
def split_into_two_lists(lst, f):
a = []
b = []
for elem in lst:
if f(elem):
a.append(elem)
else:
b.append(elem)
return a, b
De esa manera, no está procesando nada dos veces y tampoco está repitiendo el código.
-
Estoy de acuerdo. Estaba buscando una forma “elegante” (es decir, aquí significa corta e integrada/implícita) de hacer esto sin escanear la lista dos veces, pero parece (sin perfiles) ser el camino a seguir. Por supuesto, solo importaría de todos modos para grandes cantidades de datos.
– Mateo Flaschen
4 de junio de 2009 a las 8:32
-
En mi humilde opinión, si conoce una manera de hacerlo con menos uso de la CPU (y, por lo tanto, menos consumo de energía), no hay razón para no usarlo.
– viento
4 de junio de 2009 a las 19:46
-
@winden … Transferir todo mi Python a C. 😉
– Elliot Cameron
27/04/2016 a las 21:52
Tomás
Mi opinión al respecto. Propongo un perezoso, de un solo paso, partition
función, que conserva el orden relativo en las subsecuencias de salida.
1. Requisitos
Supongo que los requisitos son:
- mantener el orden relativo de los elementos (por lo tanto, sin conjuntos ni diccionarios)
- evaluar la condición solo una vez para cada elemento (por lo tanto, no usar (
i
)filter
ogroupby
) - permitir el consumo perezoso de cualquiera de las secuencias (si podemos permitirnos calcularlas previamente, es probable que la implementación ingenua también sea aceptable)
2. split
biblioteca
Mi partition
función (presentada a continuación) y otras funciones similares se han convertido en una pequeña biblioteca:
Es instalable normalmente a través de PyPI:
pip install --user split
Para dividir una lista basada en condiciones, use partition
función:
>>> from split import partition
>>> files = [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi') ]
>>> image_types = ('.jpg','.jpeg','.gif','.bmp','.png')
>>> images, other = partition(lambda f: f[-1] in image_types, files)
>>> list(images)
[('file1.jpg', 33L, '.jpg')]
>>> list(other)
[('file2.avi', 999L, '.avi')]
3. partition
función explicada
Internamente, necesitamos construir dos subsecuencias a la vez, por lo que consumir solo una secuencia de salida obligará a calcular la otra también. Y necesitamos mantener el estado entre las solicitudes de los usuarios (almacenar elementos procesados pero aún no solicitados). Para mantener el estado, uso dos colas de dos extremos (deques
):
from collections import deque
SplitSeq
la clase se encarga de la limpieza:
class SplitSeq:
def __init__(self, condition, sequence):
self.cond = condition
self.goods = deque([])
self.bads = deque([])
self.seq = iter(sequence)
La magia sucede en su .getNext()
método. es casi como .next()
de 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:
def getNext(self, getGood=True):
if getGood:
these, those, cond = self.goods, self.bads, self.cond
else:
these, those, cond = self.bads, self.goods, lambda x: not self.cond(x)
if these:
return these.popleft()
else:
while 1: # exit on StopIteration
n = self.seq.next()
if cond(n):
return n
else:
those.append(n)
Se supone que el usuario final debe usar partition
función. Se necesita una función de condición y una secuencia (al igual que map
o filter
), y devuelve dos generadores. El primer generador construye una subsecuencia de elementos para los que se cumple la condición, el segundo construye la subsecuencia complementaria. Los iteradores y generadores permiten la división diferida incluso de secuencias largas o infinitas.
def partition(condition, sequence):
cond = condition if condition else bool # evaluate as bool if condition == None
ss = SplitSeq(cond, sequence)
def goods():
while 1:
yield ss.getNext(getGood=True)
def bads():
while 1:
yield ss.getNext(getGood=False)
return goods(), bads()
Elegí la función de prueba como el primer argumento para facilitar la aplicación parcial en el futuro (similar a cómo map
y filter
tener la función de prueba como primer argumento).
-
Estoy de acuerdo. Estaba buscando una forma “elegante” (es decir, aquí significa corta e integrada/implícita) de hacer esto sin escanear la lista dos veces, pero parece (sin perfiles) ser el camino a seguir. Por supuesto, solo importaría de todos modos para grandes cantidades de datos.
– Mateo Flaschen
4 de junio de 2009 a las 8:32
-
En mi humilde opinión, si conoce una manera de hacerlo con menos uso de la CPU (y, por lo tanto, menos consumo de energía), no hay razón para no usarlo.
– viento
4 de junio de 2009 a las 19:46
-
@winden … Transferir todo mi Python a C. 😉
– Elliot Cameron
27/04/2016 a las 21:52
alan isaac
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 dictado predeterminado (se supone que es importado).
def categorize(func, seq):
"""Return mapping from categories to lists
of categorized items.
"""
d = defaultdict(list)
for item in seq:
d[func(item)].append(item)
return d
-
Iba a tratar de seleccionar las declaraciones de zen de pitón que se aplican aquí, pero son demasiados para un comentario. =) Impresionante pieza de código.
– jpmc26
24/10/2014 a las 18:49
Aterricé aquí buscando una manera de tener una condición en la declaración del generador de conjuntos, su pregunta respondió a mi pregunta 🙂
– Anuvrat Parashar
21 de junio de 2012 a las 13:27
dividir es una descripción desafortunada de esta operación, ya que ya tiene un significado específico con respecto a las cadenas de Python. Creo dividir es una palabra más precisa (o al menos menos sobrecargada en el contexto de iterables de Python) para describir esta operación. Aterricé aquí buscando una lista equivalente a
str.split()
a dividir la lista en una colección ordenada de sublistas consecutivas. P.ejsplit([1,2,3,4,5,3,6], 3) -> ([1,2],[4,5],[6])
Opuesto a divisor elementos de una lista por categoría.– Guiso
17 dic 2015 a las 16:24
Discusión del mismo tema en python-list.
– Xiong Chiamiov
10/10/2016 a las 19:13
IMAGE_TYPES debe ser un conjunto en lugar de una tupla:
IMAGE_TYPES = set('.jpg','.jpeg','.gif','.bmp','.png')
. n(1) en lugar de n(o/2), prácticamente sin diferencia en legibilidad.– ChaimG
5 de marzo de 2017 a las 17:58