Python: ¿dividir una lista en función de una condición?

272

¿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]
Desfile
fuente
55
aterrizó 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
55
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 que dividir es una palabra más precisa (o al menos menos sobrecargada en el contexto de los iterables de Python) para describir esta operación. Aterricé aquí buscando una lista equivalente a str.split(), para dividir la lista en una colección ordenada de sublistas consecutivas. Por ejemplo split([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.
Estofado
Discusión del mismo tema en python-list.
Xiong Chiamiov
IMAGE_TYPES deben 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 diferencias en la legibilidad.
ChaimG

Respuestas:

110
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?

¡Ese código es perfectamente legible y extremadamente claro!

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

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:

images, anims = [], []

for f in files:
    if f.lower() in IMAGE_TYPES:
        images.append(f)
    else:
        anims.append(f)

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 ...

if f[1] == 0:
    continue
dbr
fuente
44
¿No hay una manera de comprender la lista sin tener que recorrer la lista dos veces?
balki
35
El problema es que esto viola el principio DRY. Sería bueno si hubiera una mejor manera de hacer esto.
Antimonio
21
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 ...], lambdaes 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.
Tomasz Gandor
66
@TomaszGandor FTR, Haskell es más antiguo que Python (y de hecho influyó en su diseño). Creo que la sintaxis para la comprensión de la lista y las lambdas se mantuvo deliberadamente un poco detallada, tal vez para desalentar el uso excesivo de ellas. Lo cual es un poco arriesgado ... por mucho que me guste Haskell, puedo ver por qué muchas personas encuentran que Python es generalmente más legible.
Leftaroundabout
44
el bucle simple es la mejor manera de hacer esto ... un bucle simple, muy claro y legible
Anentropic
217
good, bad = [], []
for x in mylist:
    (bad, good)[x in goodvals].append(x)
John La Rooy
fuente
14
¡Eso es increíblemente ingenioso! Sin embargo, me llevó un tiempo entender lo que estaba sucediendo. Me gustaría saber si otros piensan que esto puede considerarse un código legible o no.
jgpaiva
171
good.append(x) if x in goodvals else bad.append(x)Es más legible.
dansalmo
21
@dansalmo Especialmente porque puedes convertirlo en una línea con el ciclo for, y si deseas agregar algo más complicado que x, puedes convertirlo en uno appendsolo:for x in mylist: (good if isgood(x) else bad).append(x)
yo
2
@MLister, en ese caso probablemente deberías incluir la búsqueda de atributos(bad.append, good.append)
John La Rooy
11
Una variación ligeramente más corta:(good if x in goodvals else bad).append(x)
Pi Delport
104

Aquí está el enfoque de iterador perezoso:

from itertools import tee

def split_on_condition(seq, condition):
    l1, l2 = tee((condition(item), item) for item in seq)
    return (i for p, i in l1 if p), (i for p, i in l2 if not p)

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:

from itertools import count, islice

def is_prime(n):
    return n > 1 and all(n % i for i in xrange(2, n))

primes, not_primes = split_on_condition(count(), is_prime)
print("First 10 primes", list(islice(primes, 10)))
print("First 10 non-primes", list(islice(not_primes, 10)))

Por lo general, aunque el enfoque de devolución de lista no perezosa es mejor:

def split_on_condition(seq, condition):
    a, b = [], []
    for item in seq:
        (a if condition(item) else b).append(item)
    return a, b

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:

DROP_VALUE = lambda _:_
def split_by_key(seq, resultmapping, keyfunc, default=DROP_VALUE):
    """Split a sequence into lists based on a key function.

        seq - input sequence
        resultmapping - a dictionary that maps from target lists to keys that go to that list
        keyfunc - function to calculate the key of an input value
        default - the target where items that don't have a corresponding key go, by default they are dropped
    """
    result_lists = dict((key, []) for key in resultmapping)
    appenders = dict((key, result_lists[target].append) for target, keys in resultmapping.items() for key in keys)

    if default is not DROP_VALUE:
        result_lists.setdefault(default, [])
        default_action = result_lists[default].append
    else:
        default_action = DROP_VALUE

    for item in seq:
        appenders.get(keyfunc(item), default_action)(item)

    return result_lists

Uso:

def file_extension(f):
    return f[2].lower()

split_files = split_by_key(files, {'images': IMAGE_TYPES}, keyfunc=file_extension, default='anims')
print split_files['images']
print split_files['anims']
Hormigas Aasma
fuente
Probablemente tengas razón en que esto viola el principio YAGNI. Se basa en el supuesto de que el número de listas diferentes en las que se pueden dividir las cosas crecerá en el futuro.
Ants Aasma
17
Puede ser mucho código, pero si [ x for x in my_list if ExpensiveOperation(x) ]lleva mucho tiempo ejecutarlo, ¡ciertamente no querrás hacerlo dos veces!
dash-tom-bang
1
+1 por ofrecer múltiples variaciones, incluidas soluciones basadas en iteradores y una solución específica "en X". El OP "en goodvals" puede ser pequeño, pero reemplazar esto con un diccionario muy grande o un predicado costoso podría ser costoso. También reduce la necesidad de escribir la comprensión de la lista dos veces donde sea ​​necesario, reduciendo así la probabilidad de introducir errores tipográficos / de usuario. Buena solución ¡Gracias!
cod3monk3y
3
Tenga en cuenta que teealmacena 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.
John La Rooy
25

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 SplitIntoTwoLists(l, f):
  a = []
  b = []
  for i in l:
    if f(i):
      a.append(i)
    else:
      b.append(i)
 return (a,b)

De esa manera, no está procesando nada dos veces y tampoco está repitiendo código.

Winden
fuente
Estoy de acuerdo. Estaba buscando una forma "elegante" (es decir, aquí, corta e incorporada / implícita) de hacer esto sin escanear la lista dos veces, pero esta parece (sin perfiles) ser el camino a seguir. Por supuesto, solo importaría de todos modos para grandes cantidades de datos.
Matthew Flaschen
En mi humilde opinión, si conoce una forma de hacerlo con menos uso de CPU (y, por lo tanto, menos consumo de energía), no hay razón para no usarlo.
winden
2
@winden ... Portar todo mi Python a C.;)
Elliot Cameron
19

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:

  • mantener el orden relativo de los elementos (por lo tanto, no hay conjuntos ni diccionarios)
  • evaluar la condición solo una vez para cada elemento (por lo tanto, no usar ( i) filtero groupby)
  • permitir el consumo diferido de cualquiera de las secuencias (si podemos permitirnos calcularlas previamente, es probable que la implementación ingenua también sea aceptable)

2. splitbiblioteca

Mi partitionfunció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:

pip install --user split

Para dividir una lista basada en la condición, use la partitionfunció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. partitionfunció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 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):

from collections import deque

SplitSeq 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() 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 la partitionfunción. Toma una función de condición y una secuencia (como mapo filter), 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.

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 mapy filter tener la función de prueba como primer argumento).

sastanin
fuente
15

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

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
Alan Isaac
fuente
Iba a tratar de elegir las declaraciones del Zen de Python que se aplican aquí, pero son demasiadas para un comentario. =) Impresionante pieza de código.
jpmc26
13

Primero vaya (pre-OP-edit): Use sets:

mylist = [1,2,3,4,5,6,7]
goodvals = [1,3,7,8,9]

myset = set(mylist)
goodset = set(goodvals)

print list(myset.intersection(goodset))  # [1, 3, 7]
print list(myset.difference(goodset))    # [2, 4, 5, 6]

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:

IMAGE_TYPES = set(['.jpg','.jpeg','.gif','.bmp','.png'])

y eso aumentará el rendimiento. De lo contrario, lo que tienes me parece bien.

RichieHindle
fuente
44
no es la mejor solución si las listas estaban en algún orden antes de dividirse y necesita que permanezcan en ese orden.
Daniyar
8
¿No eliminaría eso los duplicados?
mavnn
Crear un conjunto es O (n log n). Iterando la lista dos veces es O (n). La solución establecida puede ser más elegante (cuando es correcta en primer lugar) pero ciertamente es más lenta a medida que n aumenta.
dash-tom-bang
1
@ dash-tom-bang Iterar la lista es O (n * n). Esto se debe a que es posible que cada elemento de la lista deba compararse con cada elemento de la lista goodvals.
ChaimG
@ChaimG buen punto, aunque también debemos tener en cuenta el costo de las operaciones de intersección y diferencia (que no sé de antemano, pero estoy bastante seguro de que también son superlineales).
dash-tom-bang
10

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.

def is_good(f):
    return f[2].lower() in IMAGE_TYPES

files = [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi'), ('file3.gif', 123L, '.gif')]

for key, group in itertools.groupby(sorted(files, key=is_good), key=is_good):
    print key, list(group)

da:

False [('file2.avi', 999L, '.avi')]
True [('file1.jpg', 33L, '.jpg'), ('file3.gif', 123L, '.gif')]

Similar a las otras soluciones, la función clave se puede definir para dividirse en cualquier número de grupos que desee.

Brian
fuente
6
good.append(x) if x in goodvals else bad.append(x)

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:

good, bad = [], []
for x in my_list:
    good.append(x) if x in goodvals else bad.append(x)
John D
fuente
5

Si quieres hacerlo en estilo FP:

good, bad = [ sum(x, []) for x in zip(*(([y], []) if y in goodvals else ([], [y])
                                        for y in mylist)) ]

No es la solución más fácil de leer, pero al menos itera a través de mylist solo una vez.

michau
fuente
1
Aunque itera por la lista solo una vez, el rendimiento no es tan bueno debido a que la lista se agrega. Agregar a una lista es una operación potencialmente costosa (en comparación con deque.append, por ejemplo). En realidad, esta solución es extremadamente lenta en comparación con otras soluciones aquí (21.4s en 100000 enteros aleatorios y probando su valor).
rlat
5

Personalmente, me gusta la versión que citó, suponiendo que ya tenga una lista de goodvalscosas por ahí. Si no, algo como:

good = filter(lambda x: is_good(x), mylist)
bad = filter(lambda x: not is_good(x), mylist)

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:

good = [x for x in mylist if is_good(x)]
bad  = [x for x in mylist if not is_good(x)]

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 intersectiony differenceen los conjuntos también funcionaría bien.

BJ Homer
fuente
Por supuesto, filter(lambda x: is_good(x), mylist)se puede reducir afilter(is_good, mylist)
robru
agregar la llamada de función adicional en realidad duplica (!) el tiempo de ejecución, en comparación con las comprensiones de la lista, de lo que he visto en la creación de perfiles. Es difícil superar una lista de comprensión, la mayoría de las veces.
Corley Brigman
4

Creo que una generalización de dividir un iterable basado en N condiciones es útil

from collections import OrderedDict
def partition(iterable,*conditions):
    '''Returns a list with the elements that satisfy each of condition.
       Conditions are assumed to be exclusive'''
    d= OrderedDict((i,list())for i in range(len(conditions)))        
    for e in iterable:
        for i,condition in enumerate(conditions):
            if condition(e):
                d[i].append(e)
                break                    
    return d.values()

Por ejemplo:

ints,floats,other = partition([2, 3.14, 1, 1.69, [], None],
                              lambda x: isinstance(x, int), 
                              lambda x: isinstance(x, float),
                              lambda x: True)

print " ints: {}\n floats:{}\n other:{}".format(ints,floats,other)

 ints: [2, 1]
 floats:[3.14, 1.69]
 other:[[], None]

Si el elemento puede satisfacer múltiples condiciones, elimine la ruptura.

Geco
fuente
3
def partition(pred, iterable):
    'Use a predicate to partition entries into false entries and true entries'
    # partition(is_odd, range(10)) --> 0 2 4 6 8   and  1 3 5 7 9
    t1, t2 = tee(iterable)
    return filterfalse(pred, t1), filter(pred, t2)

Mira esto

Hanfei Sun
fuente
3

¡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):

good_list = ('.jpg','.jpeg','.gif','.bmp','.png')

import random
import string
my_origin_list = []
for i in xrange(10000):
    fname = ''.join(random.choice(string.lowercase) for i in range(random.randrange(10)))
    if random.getrandbits(1):
        fext = random.choice(good_list)
    else:
        fext = "." + ''.join(random.choice(string.lowercase) for i in range(3))

    my_origin_list.append((fname + fext, random.randrange(1000), fext))

Y aquí vamos

# Parand
def f1():
    return [e for e in my_origin_list if e[2] in good_list], [e for e in my_origin_list if not e[2] in good_list]

# dbr
def f2():
    a, b = list(), list()
    for e in my_origin_list:
        if e[2] in good_list:
            a.append(e)
        else:
            b.append(e)
    return a, b

# John La Rooy
def f3():
    a, b = list(), list()
    for e in my_origin_list:
        (b, a)[e[2] in good_list].append(e)
    return a, b

# Ants Aasma
def f4():
    l1, l2 = tee((e[2] in good_list, e) for e in my_origin_list)
    return [i for p, i in l1 if p], [i for p, i in l2 if not p]

# My personal way to do
def f5():
    a, b = zip(*[(e, None) if e[2] in good_list else (None, e) for e in my_origin_list])
    return list(filter(None, a)), list(filter(None, b))

# BJ Homer
def f6():
    return filter(lambda e: e[2] in good_list, my_origin_list), filter(lambda e: not e[2] in good_list, my_origin_list)

Usando la función cmpthese , el mejor resultado es la respuesta dbr:

f1     204/s  --    -5%   -14%   -15%   -20%   -26%
f6     215/s     6%  --    -9%   -11%   -16%   -22%
f3     237/s    16%    10%  --    -2%    -7%   -14%
f4     240/s    18%    12%     2%  --    -6%   -13%
f5     255/s    25%    18%     8%     6%  --    -8%
f2     277/s    36%    29%    17%    15%     9%  --
FunkySayu
fuente
Funciones más rápidas con puntos de referencia actualizados aquí .
ChaimG
2

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:

from collections import deque

def split(iterable, function):
    dq_true = deque()
    dq_false = deque()

    # deque - the fastest way to consume an iterator and append items
    deque((
      (dq_true if function(item) else dq_false).append(item) for item in iterable
    ), maxlen=0)

    return dq_true, dq_false

Luego, puede usar la función de la siguiente manera:

lower, higher = split([0,1,2,3,4,5,6,7,8,9], lambda x: x < 5)

selected, other = split([0,1,2,3,4,5,6,7,8,9], lambda x: x in {0,4,9})

Si no está bien con el resultado dequede objeto, puede convertir fácilmente a list, set, lo que quiera (por ejemplo list(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.

rlat
fuente
2

Por ejemplo, dividir la lista por pares e impares

arr = range(20)
even, odd = reduce(lambda res, next: res[next % 2].append(next) or res, arr, ([], []))

O en general:

def split(predicate, iterable):
    return reduce(lambda res, e: res[predicate(e)].append(e) or res, iterable, ([], []))

Ventajas:

  • El camino más corto posible
  • El predicado se aplica solo una vez para cada elemento

Desventajas

  • Requiere conocimiento del paradigma de programación funcional
Pavel Ilchenko
fuente
2

Inspirado por la excelente respuesta de @ gnibbler (¡pero conciso!) , Podemos aplicar ese enfoque para mapear en particiones múltiples:

from collections import defaultdict

def splitter(l, mapper):
    """Split an iterable into multiple partitions generated by a callable mapper."""

    results = defaultdict(list)

    for x in l:
        results[mapper(x)] += [x]

    return results

Entonces splitterse puede usar de la siguiente manera:

>>> l = [1, 2, 3, 4, 2, 3, 4, 5, 6, 4, 3, 2, 3]
>>> split = splitter(l, lambda x: x % 2 == 0)  # partition l into odds and evens
>>> split.items()
>>> [(False, [1, 3, 3, 5, 3, 3]), (True, [2, 4, 2, 4, 6, 4, 2])]

Esto funciona para más de dos particiones con un mapeo más complicado (y también en iteradores):

>>> import math
>>> l = xrange(1, 23)
>>> split = splitter(l, lambda x: int(math.log10(x) * 5))
>>> split.items()
[(0, [1]),
 (1, [2]),
 (2, [3]),
 (3, [4, 5, 6]),
 (4, [7, 8, 9]),
 (5, [10, 11, 12, 13, 14, 15]),
 (6, [16, 17, 18, 19, 20, 21, 22])]

O usando un diccionario para mapear:

>>> map = {'A': 1, 'X': 2, 'B': 3, 'Y': 1, 'C': 2, 'Z': 3}
>>> l = ['A', 'B', 'C', 'C', 'X', 'Y', 'Z', 'A', 'Z']
>>> split = splitter(l, map.get)
>>> split.items()
(1, ['A', 'Y', 'A']), (2, ['C', 'C', 'X']), (3, ['B', 'Z', 'Z'])]
Josh Bode
fuente
... acabo de notar que esto es básicamente lo mismo que @ alan-isaac ya ha respondido.
Josh Bode
2
bad = []
good = [x for x in mylist if x in goodvals or bad.append(x)]

agregar devuelve Ninguno, por lo que funciona.

Biga
fuente
1

Para el rendimiento, intente itertools.

El módulo itertools estandariza un conjunto básico de herramientas rápidas y eficientes en memoria que son útiles por sí mismas o en combinación. Juntos, forman un "álgebra iteradora" que hace posible construir herramientas especializadas de manera sucinta y eficiente en Python puro.

Ver itertools.ifilter o imap.

itertools.ifilter (predicado, iterable)

Haga un iterador que filtre elementos de iterables y devuelva solo aquellos para los que el predicado es verdadero

gimel
fuente
ifilter / imap (y los generadores en general) son bastante lentos ... en general, en mi perfil, si toma una lista de comprensión como [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.
Corley Brigman
1

A veces no necesitarás esa otra mitad de la lista. Por ejemplo:

import sys
from itertools import ifilter

trustedPeople = sys.argv[1].split(',')
newName = sys.argv[2]

myFriends = ifilter(lambda x: x.startswith('Shi'), trustedPeople)

print '%s is %smy friend.' % (newName, newName not in myFriends 'not ' or '')
Centro comercial Shikhar
fuente
1

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.

good_list_set = set(good_list)  # 45% faster than a tuple.

good, bad = [], []
for item in my_origin_list:
    if item in good_list_set:
        good.append(item)
    else:
        bad.append(item)

Un poco mas corto:

good_list_set = set(good_list)  # 45% faster than a tuple.

good, bad = [], []
for item in my_origin_list:
    out = good if item in good_list_set else bad
    out.append(item)

Resultados de referencia:

filter_BJHomer                  80/s       --   -3265%   -5312%   -5900%   -6262%   -7273%   -7363%   -8051%   -8162%   -8244%
zip_Funky                       118/s    4848%       --   -3040%   -3913%   -4450%   -5951%   -6085%   -7106%   -7271%   -7393%
two_lst_tuple_JohnLaRoy         170/s   11332%    4367%       --   -1254%   -2026%   -4182%   -4375%   -5842%   -6079%   -6254%
if_else_DBR                     195/s   14392%    6428%    1434%       --    -882%   -3348%   -3568%   -5246%   -5516%   -5717%
two_lst_compr_Parand            213/s   16750%    8016%    2540%     967%       --   -2705%   -2946%   -4786%   -5083%   -5303%
if_else_1_line_DanSalmo         292/s   26668%   14696%    7189%    5033%    3707%       --    -331%   -2853%   -3260%   -3562%
tuple_if_else                   302/s   27923%   15542%    7778%    5548%    4177%     343%       --   -2609%   -3029%   -3341%
set_1_line                      409/s   41308%   24556%   14053%   11035%    9181%    3993%    3529%       --    -569%    -991%
set_shorter                     434/s   44401%   26640%   15503%   12303%   10337%    4836%    4345%     603%       --    -448%
set_if_else                     454/s   46952%   28358%   16699%   13349%   11290%    5532%    5018%    1100%     469%       --

El código de referencia completo para Python 3.7 (modificado de FunkySayu):

good_list = ['.jpg','.jpeg','.gif','.bmp','.png']

import random
import string
my_origin_list = []
for i in range(10000):
    fname = ''.join(random.choice(string.ascii_lowercase) for i in range(random.randrange(10)))
    if random.getrandbits(1):
        fext = random.choice(list(good_list))
    else:
        fext = "." + ''.join(random.choice(string.ascii_lowercase) for i in range(3))

    my_origin_list.append((fname + fext, random.randrange(1000), fext))

# Parand
def two_lst_compr_Parand(*_):
    return [e for e in my_origin_list if e[2] in good_list], [e for e in my_origin_list if not e[2] in good_list]

# dbr
def if_else_DBR(*_):
    a, b = list(), list()
    for e in my_origin_list:
        if e[2] in good_list:
            a.append(e)
        else:
            b.append(e)
    return a, b

# John La Rooy
def two_lst_tuple_JohnLaRoy(*_):
    a, b = list(), list()
    for e in my_origin_list:
        (b, a)[e[2] in good_list].append(e)
    return a, b

# # Ants Aasma
# def f4():
#     l1, l2 = tee((e[2] in good_list, e) for e in my_origin_list)
#     return [i for p, i in l1 if p], [i for p, i in l2 if not p]

# My personal way to do
def zip_Funky(*_):
    a, b = zip(*[(e, None) if e[2] in good_list else (None, e) for e in my_origin_list])
    return list(filter(None, a)), list(filter(None, b))

# BJ Homer
def filter_BJHomer(*_):
    return list(filter(lambda e: e[2] in good_list, my_origin_list)), list(filter(lambda e: not e[2] in good_list,                                                                             my_origin_list))

# ChaimG's answer; as a list.
def if_else_1_line_DanSalmo(*_):
    good, bad = [], []
    for e in my_origin_list:
        _ = good.append(e) if e[2] in good_list else bad.append(e)
    return good, bad

# ChaimG's answer; as a set.
def set_1_line(*_):
    good_list_set = set(good_list)
    good, bad = [], []
    for e in my_origin_list:
        _ = good.append(e) if e[2] in good_list_set else bad.append(e)
    return good, bad

# ChaimG set and if else list.
def set_shorter(*_):
    good_list_set = set(good_list)
    good, bad = [], []
    for e in my_origin_list:
        out = good if e[2] in good_list_set else bad
        out.append(e)
    return good, bad

# ChaimG's best answer; if else as a set.
def set_if_else(*_):
    good_list_set = set(good_list)
    good, bad = [], []
    for e in my_origin_list:
        if e[2] in good_list_set:
            good.append(e)
        else:
            bad.append(e)
    return good, bad

# ChaimG's best answer; if else as a set.
def tuple_if_else(*_):
    good_list_tuple = tuple(good_list)
    good, bad = [], []
    for e in my_origin_list:
        if e[2] in good_list_tuple:
            good.append(e)
        else:
            bad.append(e)
    return good, bad

def cmpthese(n=0, functions=None):
    results = {}
    for func_name in functions:
        args = ['%s(range(256))' % func_name, 'from __main__ import %s' % func_name]
        t = Timer(*args)
        results[func_name] = 1 / (t.timeit(number=n) / n) # passes/sec

    functions_sorted = sorted(functions, key=results.__getitem__)
    for f in functions_sorted:
        diff = []
        for func in functions_sorted:
            if func == f:
                diff.append("--")
            else:
                diff.append(f"{results[f]/results[func]*100 - 100:5.0%}")
        diffs = " ".join(f'{x:>8s}' for x in diff)

        print(f"{f:27s} \t{results[f]:,.0f}/s {diffs}")


if __name__=='__main__':
    from timeit import Timer
cmpthese(1000, 'two_lst_compr_Parand if_else_DBR two_lst_tuple_JohnLaRoy zip_Funky filter_BJHomer if_else_1_line_DanSalmo set_1_line set_if_else tuple_if_else set_shorter'.split(" "))
ChaimG
fuente
0

Si insiste en la inteligencia, podría tomar la solución de Winden y un poco de inteligencia espuria:

def splay(l, f, d=None):
  d = d or {}
  for x in l: d.setdefault(f(x), []).append(x)
  return d
Anders Eurenius
fuente
3
El "do {}" es un poco peligroso. Si se pasa un dict vacío, no se mutará en su lugar.
Brian
Es cierto, pero se devuelve, así que ... En realidad, este es el ejemplo perfecto de por qué no desea agregar más inteligencia a su código. :-P
Anders Eurenius
0

Ya hay bastantes soluciones aquí, pero otra forma de hacerlo sería:

anims = []
images = [f for f in files if (lambda t: True if f[2].lower() in IMAGE_TYPES else anims.append(t) and False)(f)]

Itera la lista solo una vez, y se ve un poco más pitón y, por lo tanto, legible para mí.

>>> files = [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi'), ('file1.bmp', 33L, '.bmp')]
>>> IMAGE_TYPES = ('.jpg','.jpeg','.gif','.bmp','.png')
>>> anims = []
>>> images = [f for f in files if (lambda t: True if f[2].lower() in IMAGE_TYPES else anims.append(t) and False)(f)]
>>> print '\n'.join([str(anims), str(images)])
[('file2.avi', 999L, '.avi')]
[('file1.jpg', 33L, '.jpg'), ('file1.bmp', 33L, '.bmp')]
>>>
Shreyas
fuente
0

Adoptaría un enfoque de 2 pasos, separando la evaluación del predicado del filtrado de la lista:

def partition(pred, iterable):
    xs = list(zip(map(pred, iterable), iterable))
    return [x[1] for x in xs if x[0]], [x[1] for x in xs if not x[0]]

Lo bueno de esto, en cuanto al rendimiento (además de evaluar predsolo una vez en cada miembro de iterable), 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.

Jim Witschey
fuente
0

solución

from itertools import tee

def unpack_args(fn):
    return lambda t: fn(*t)

def separate(fn, lx):
    return map(
        unpack_args(
            lambda i, ly: filter(
                lambda el: bool(i) == fn(el),
                ly)),
        enumerate(tee(lx, 2)))

prueba

[even, odd] = separate(
    lambda x: bool(x % 2),
    [1, 2, 3, 4, 5])
print(list(even) == [2, 4])
print(list(odd) == [1, 3, 5])
doctorzeb8
fuente
0

Si no le importa usar una biblioteca externa, hay dos que sé que implementan de manera nativa esta operación:

>>> files = [ ('file1.jpg', 33, '.jpg'), ('file2.avi', 999, '.avi')]
>>> IMAGE_TYPES = ('.jpg','.jpeg','.gif','.bmp','.png')
  • iteration_utilities.partition:

    >>> from iteration_utilities import partition
    >>> notimages, images = partition(files, lambda x: x[2].lower() in IMAGE_TYPES)
    >>> notimages
    [('file2.avi', 999, '.avi')]
    >>> images
    [('file1.jpg', 33, '.jpg')]
  • more_itertools.partition

    >>> from more_itertools import partition
    >>> notimages, images = partition(lambda x: x[2].lower() in IMAGE_TYPES, files)
    >>> list(notimages)  # returns a generator so you need to explicitly convert to list.
    [('file2.avi', 999, '.avi')]
    >>> list(images)
    [('file1.jpg', 33, '.jpg')]
MSeifert
fuente
0

No estoy seguro si este es un buen enfoque, pero también se puede hacer de esta manera

IMAGE_TYPES = ('.jpg','.jpeg','.gif','.bmp','.png')
files = [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi')]
images, anims = reduce(lambda (i, a), f: (i + [f], a) if f[2] in IMAGE_TYPES else (i, a + [f]), files, ([], []))
Kiran
fuente
0

Si la lista está compuesta por grupos y separadores intermitentes, puede usar:

def split(items, p):
    groups = [[]]
    for i in items:
        if p(i):
            groups.append([])
        groups[-1].append(i)
    return groups

Uso:

split(range(1,11), lambda x: x % 3 == 0)
# gives [[1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]
David
fuente
0
images = [f for f in files if f[2].lower() in IMAGE_TYPES]
anims  = [f for f in files if f not in images]

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.

Chrisjan
fuente
0

Otra respuesta, corta pero "malvada" (para efectos secundarios de comprensión de la lista).

digits = list(range(10))
odd = [x.pop(i) for i, x in enumerate(digits) if x % 2]

>>> odd
[1, 3, 5, 7, 9]

>>> digits
[0, 2, 4, 6, 8]
Shay
fuente