2010-10-16 20 views
8
# I have 3 lists: 
L1 = [1, 2, 3, 4, 5, 6, 7, 8, 9] 
L2 = [4, 7, 8] 
L3 = [5, 2, 9] 
# I want to create another that is L1 minus L2's memebers and L3's memebers, so: 
L4 = (L1 - L2) - L3 # Of course this isn't going to work 

Me pregunto, ¿cuál es la forma "correcta" de hacerlo? Puedo hacerlo de muchas formas diferentes, pero la guía de estilo de Python dice que debe haber solo 1 forma correcta de hacer cada cosa. Nunca supe lo que era esto.Python - eliminación de elementos de las listas

+3

No hay una forma correcta de hacerlo hasta que decida si le importa o si no le importan los duplicados y el pedido. Probablemente algún tipo de lista de comprensión o de trabajo en función de lo que te interesa. – istruble

+1

Además, ¿está bien suponer que todos los elementos en las listas serán manejables todo el tiempo? Si no, o a veces no, eso sería muy significativo. – martineau

+1

¿Por qué no usa conjuntos para empezar? Entonces tu "aritmética" funcionaría. – poke

Respuesta

10

Éstos son algunos intentos:

L4 = [ n for n in L1 if (n not in L2) and (n not in L3) ] # parens for clarity 

tmpset = set(L2 + L3) 
L4 = [ n for n in L1 if n not in tmpset ] 

Ahora que he tenido un momento para pensar, Me doy cuenta de que la cosa L2 + L3 crea una lista temporal que inmediatamente se descarta. Por lo tanto una mejor manera es:

tmpset = set(L2) 
tmpset.update(L3) 
L4 = [ n for n in L1 if n not in tmpset ] 

Actualización: veo algunas afirmaciones extravagantes que son lanzados alrededor por el rendimiento, y quiero afirmar que mi solución era ya tan rápido como sea posible. Crear resultados intermedios, ya sean listas intermedias o iteradores intermedios a los que se deba llamar repetidamente, será más lento, siempre, que simplemente dar L2 y L3 para que el conjunto itere directamente como lo he hecho aquí.

$ python -m timeit \ 
    -s 'L1=range(300);L2=range(30,70,2);L3=range(120,220,2)' \ 
    'ts = set(L2); ts.update(L3); L4 = [ n for n in L1 if n not in ts ]' 
10000 loops, best of 3: 39.7 usec per loop 

Todas las demás alternativas (que se me ocurren) serán necesariamente más lentas que esta. Haciendo los bucles de nosotros mismos, por ejemplo, en lugar de dejar que el constructor set() hacerlas, añade gastos:

$ python -m timeit \ 
    -s 'L1=range(300);L2=range(30,70,2);L3=range(120,220,2)' \ 
    'unwanted = frozenset(item for lst in (L2, L3) for item in lst); L4 = [ n for n in L1 if n not in unwanted ]' 
10000 loops, best of 3: 46.4 usec per loop 

Utilización de iteradores, habrá todas las devoluciones de llamada y de ahorro estatales que implican, obviamente habrá aún más caro:

$ python -m timeit \ 
    -s 'L1=range(300);L2=range(30,70,2);L3=range(120,220,2);from itertools import ifilterfalse, chain' \ 
    'L4 = list(ifilterfalse(frozenset(chain(L2, L3)).__contains__, L1))' 
10000 loops, best of 3: 47.1 usec per loop 

por lo que creo que la respuesta que dio la noche anterior todavía está lejos y lejos (para valores de "lejos" mayor que alrededor de 5μsec, obviamente) la mejor, a menos que el interrogador tendrá duplicados en L1 y quiere se eliminan una vez cada vez que aparece el duplicado en una de las otras listas .

+0

Podría ser posible obtener un mayor rendimiento construyendo un conjunto congelado a partir de una cadena de dos iteradores de lista. – intuited

+0

No, los juegos congelados tienen exactamente la misma velocidad que los normales, pero normalmente requieren más gasto para crear porque tienes que crear un resultado intermedio o un bucle si, como aquí, los estás construyendo a partir de varios elementos de entrada. –

0

Suponiendo sus listas individuales no contienen duplicados .... Use Set y Difference

L1 = [1, 2, 3, 4, 5, 6, 7, 8, 9] 
L2 = [4, 7, 8] 
L3 = [5, 2, 9] 
print(list(set(L1) - set(L2) - set(L3))) 
+2

Esto perdería el orden. –

+1

Sí, la diferencia clave entre una lista y un conjunto ... – mepcotterell

+1

Si la orden/duplicados NO son un problema, esta es la opción más limpia, IMO –

0

Hacer tales operaciones en las listas puede dificultar el rendimiento de su programa muy pronto. Lo que ocurre es que con cada eliminación, las operaciones de lista hacen que un nuevo Malloc & mueva los elementos alrededor. Esto puede ser costoso si tiene una lista muy grande o no. Así que sugeriría esto:

Supongo que su lista tiene elementos únicos. De lo contrario, debe mantener una lista en su dict con valores duplicados. De todos modos para los datos de su provistos, aquí lo es-

MÉTODO 1

d = dict() 
for x in L1: d[x] = True 

# Check if L2 data is in 'd' 
for x in L2: 
    if x in d: 
     d[x] = False 

for x in L3: 
    if x in d: 
     d[x] = False 

# Finally retrieve all keys with value as True. 
final_list = [x for x in d if d[x]] 

MÉTODO 2 Si todo lo que se parece demasiado código. Entonces podrías intentar usar set. Pero de esta manera su lista perderá todos los elementos duplicados.

final_set = set.difference(set(L1),set(L2),set(L3)) 
final_list = list(final_set) 
+0

La lista de comprensión no elimina operaciones que son costosas. – aaronasterling

+0

#aaron sí, lo sé. Me refería a la solución publicada por Santiago. –

+1

Oye, básicamente estás usando un diccionario como un conjunto. Tienen otro tipo de datos para eso: http://docs.python.org/library/stdtypes.html#types-set – intuited

0

Esto puede ser menos Pythonesque que la respuesta lista-comprensión, pero tiene un aspecto más simple a la misma:

l1 = [ ... ] 
l2 = [ ... ] 

diff = list(l1) # this copies the list 
for element in l2: 
    diff.remove(element) 

La ventaja aquí es que preservar el orden de la lista, y si hay elementos duplicados, eliminamos solo uno por cada vez que aparece en l2.

+1

Eso es increíblemente caro y, por el contrario, es más complicado de ver que una simple comprensión. – aaronasterling

+0

Un problema de sabor, parece. Me gustan mucho las listas de comprensión, en realidad tiendo a abusar de ellas, pero no creo que "n for n in L if n not in ..." sea agradable a la vista. De una manera u otra, es, lo admito, computacionalmente costosa. – slezica

6

actualización ::: publicación contiene una referencia a acusaciones falsas de rendimiento inferior de conjuntos en comparación con conjuntos congelados. Sigo diciendo que todavía es sensato usar un juego frozenset en este caso, incluso si no hay necesidad de ajustar el conjunto en sí mismo, simplemente porque es más correcto semánticamente. Aunque, en la práctica, es posible que no me moleste en escribir los 6 caracteres adicionales. No me siento motivado para revisar y editar la publicación, así que tenga en cuenta que el vínculo de "acusaciones" se vincula con algunas pruebas ejecutadas incorrectamente. Los detalles sangrientos se resumen en los comentarios. ::: actualización

El segundo trozo de código posted por Brandon Craig Rodas es bastante bueno, pero como él no respondió a mi sugerencia sobre el uso de un frozenset (bueno, no cuando empecé a escribir esto, de todos modos) , Voy a seguir adelante y publicarlo yo mismo.

Toda la base de la empresa en cuestión es comprobar si cada una de una serie de valores (L1) están en otro conjunto de valores; ese conjunto de valores es el contenido de L2 y L3. El uso de la palabra "ajuste" en esa frase está diciendo: a pesar de que L2 y L3 son list s, que realmente no se preocupan por sus propiedades de lista como, como el orden en que sus valores están en o cuántos de cada uno se Contiene. Nos importa el conjunto (existe otra vez) de los valores que colectivamente contienen.

Si ese conjunto de valores se almacena como una lista, usted tiene que pasar por la lista de elementos, uno por uno, comprobando cada uno. Es relativamente lento, y es una semántica mala: una vez más, es un "conjunto" de valores, no una lista. Por lo tanto, Python tiene estos tipos de conjunto ordenado que contienen un conjunto de valores únicos y puede indicarle rápidamente si hay algún valor en ellos o no. Esto funciona de la misma manera que los tipos dict de python funcionan cuando buscas una clave.

La diferencia entre los conjuntos y frozensets es que los conjuntos son mutables, es decir que pueden ser modificados después de la creación. La documentación en ambos tipos es here.

Dado que el conjunto que necesitamos crear, la unión de los valores almacenados en L2 y L3, no se va a modificar una vez creado, es semánticamente apropiado utilizar un tipo de datos inmutables. Esto también tiene algunos beneficios de rendimiento. Bueno, tiene sentido que tenga alguna ventaja; de lo contrario, ¿por qué Python tendría frozenset como un builtin?

actualización ...

Brandon ha respondido a esta pregunta: ¿la verdadera ventaja de los conjuntos de congelados es que su inmutabilidad hace que sea posible para que sean hashable, que les permite ser claves de diccionario o miembros de otros conjuntos .

Realicé algunas pruebas de temporización informales que comparaban la velocidad de creación y búsqueda en conjuntos relativamente grandes (3000 elementos) congelados y cambiables; no hubo mucha diferencia. Esto entra en conflicto con el enlace anterior, pero respalda lo que dice Brandon sobre que son idénticos, pero por el aspecto de la mutabilidad.

... actualización

Ahora, debido frozensets son inmutables, que no tienen un método de actualización. Brandon usó el método set.update para evitar crear y luego descartar una lista temporal en el camino para establecer la creación; Voy a tomar un enfoque diferente.

items = (item for lst in (L2, L3) for item in lst) 

Esto hace generator expressionitems un iterador sobre, consecutivamente, el contenido de L2 y L3. No solo eso, sino que lo hace sin crear una lista completa, llena de objetos intermedios. Usar expresiones anidadas for en generadores es un poco confuso, pero logro mantenerlo ordenado al recordar que anidan en el mismo orden en que lo harían si escribiera bucles for reales, p.

def get_items(lists): 
    for lst in lists: 
     for item in lst: 
      yield item 

Eso generator function es equivalente a la expresión generador que asignamos a items. Bueno, excepto que es una definición de función parametrizada en lugar de una asignación directa a una variable.

De todos modos, suficiente digresión. El gran problema con los generadores es que en realidad no hacen nada. Bueno, al menos no de inmediato: simplemente configuran el trabajo que se realizará más tarde, cuando la expresión del generador es iterado. Esto se conoce formalmente como perezosa. Vamos a hacer eso (bueno, lo estoy, de todos modos) al pasar items a la función frozenset, que itera sobre él y devuelve un frozenset frio y frío.

unwanted = frozenset(items) 

En realidad se podría combinar las dos últimas líneas, poniendo la expresión generador de la derecha dentro de la llamada a frozenset:

unwanted = frozenset(item for lst in (L2, L3) for item in lst) 

Este truco sintáctica ordenada funciona siempre y cuando el iterator creado por la expresión generador es el único parámetro para la función que está llamando. De lo contrario, tiene que escribirlo en su conjunto de paréntesis habitual, como si estuviera pasando una tupla como argumento para la función.

Ahora podemos construir una nueva lista de la misma manera que Brandon, con un list comprehension. Estos utilizan la misma sintaxis que las expresiones de generador, y básicamente hacen lo mismo, excepto que son con ganas en lugar de (de nuevo, estos son términos técnicos reales), por lo que se ponen a trabajar iterando sobre los elementos y creando una lista de ellos.

L4 = [item for item in L1 if item not in unwanted] 

Esto es equivalente a pasar una expresión generador para list, por ejemplo

L4 = list(item for item in L1 if item not in unwanted) 

pero más idiomático.

Así que esto va a crear la lista L4, que contiene los elementos de L1 que no estaban en cualquiera L2 o L3, manteniendo el orden en que fueron originalmente en el número de ellos que había.


Si lo que desea saber cuales valores están en L1 pero no en L2 o L3, es mucho más fácil: sólo tiene que crear ese conjunto:

L1_unique_values = set(L1) - unwanted 

Usted puede hacer una lista cabo de él, as does st0le, pero eso podría no ser realmente lo que quieres. Si realmente desea que el establecer de valores que sólo se encuentran en L1, puede que tenga una muy buena razón para mantener esa conjunto como set, o incluso un frozenset:

L1_unique_values = frozenset(L1) - unwanted 

... Annnnd, ahora algo completamente diferente:

from itertools import ifilterfalse, chain 
L4 = list(ifilterfalse(frozenset(chain(L2, L3)).__contains__, L1)) 
+0

+1 Muy informativo. La adición más reciente (con itertools) es muy buena. Diría que has obtenido tu doctorado en listas de filtrado basadas en la inclusión en un conjunto de listas. – aaronasterling

+0

@aaron: Tomó años de estudio, pero valió la pena. – intuited

+0

¿Me falta algo o su expresión de generador es simplemente 'itertools.chain'? Si es así, solo use eso (puede guardar la explicación de generadores y expresiones genéricas, la gente necesita aprender sobre ellos). – delnan

0

Creo que la respuesta de intuited es demasiado larga para un problema tan simple, y Python ya tiene una función integrada para encadenar dos listas como generador.

El procedimiento es el siguiente:

  1. Uso itertools.chain a la cadena L2 y L3 sin crear una copia de la memoria que consume
  2. Crear un conjunto de que (en este caso, un frozenset hará porque don no lo cambie después de la creación)
  3. Use la lista de comprensión para filtrar los elementos que están en L1 y también en L2 o L3. Como la búsqueda set/frozenset (x in someset) es O (1), esto será muy rápido.

Y ahora el código:

L1 = [1, 2, 3, 4, 5, 6, 7, 8, 9] 
L2 = [4, 7, 8] 
L3 = [5, 2, 9] 

from itertools import chain 
tmp = frozenset(chain(L2, L3)) 
L4 = [x for x in L1 if x not in tmp] # [1, 3, 6] 

Este debe ser uno de la solución más rápida, más simple y menos memoria que consume.

+0

No es el más rápido; verifica las pruebas en mi publicación. Poner un iterador entre el conjunto y las listas ya iterables solo ralentiza las cosas. –

+0

@Brandon Craig Rhodes: Ok, digamos "una de las soluciones más rápidas". Gracias por publicar sus resultados de referencia. – AndiDog

+0

De hecho, sus soluciones son definitivamente una de las soluciones O (* n * log * m *) más rápidas y ciertamente de la clase que este problema merece. Solo quería asegurarme de que los programadores se den cuenta de que los iteradores no son polvo de hadas que de alguna manera son más rápidos que el bucle sobre un contenedor en sí mismo; cada elemento devuelto por un iterador requiere que se reactive su alcance y se reinicie su código, para que sus beneficios no sean gratuitos. –

Cuestiones relacionadas