Eliminar múltiples elementos de una lista

160

¿Es posible eliminar varios elementos de una lista al mismo tiempo? Si quiero eliminar elementos en el índice 0 y 2, e intentar algo como del somelist[0], seguido de del somelist[2], la segunda declaración se eliminará realmente somelist[3].

Supongo que siempre podría eliminar primero los elementos con un número más alto, pero espero que haya una mejor manera.

Løiten
fuente

Respuestas:

110

Puede usar enumeratey eliminar los valores cuyo índice coincide con los índices que desea eliminar:

indices = 0, 2
somelist = [i for j, i in enumerate(somelist) if j not in indices]
SilentGhost
fuente
2
Casi, solo si elimina la lista completa. será len (índices) * len (somelist). También crea una copia, que puede o no ser deseada
Richard Levasseur
si está buscando un valor en una lista, lo es. el operador 'in' funciona con los valores de una lista, mientras que funciona con las teclas de un dict. Si estoy equivocado, por favor que me señale el pep / referencia
Richard Levasseur
55
la razón por la que elegí tupla para índices fue solo la simplicidad del registro. sería un trabajo perfecto para set () dando O (n)
SilentGhost
18
Esto no es eliminar elementos de la lista, sino crear una lista nueva. Si algo contiene una referencia a la lista original, todavía tendrá todos los elementos en ella.
Tom Future
2
@SilentGhost No es necesario hacer una enumeración. ¿Qué tal esto somelist = [ lst[i] for i in xrange(len(lst)) if i not in set(indices) ]?
ToolmakerSteve
183

Por alguna razón, no me gusta ninguna de las respuestas aquí. Sí, funcionan, pero estrictamente hablando, la mayoría de ellos no están eliminando elementos de una lista, ¿verdad? (Pero haciendo una copia y luego reemplazando la original con la copia editada).

¿Por qué no simplemente eliminar primero el índice más alto?

¿Hay alguna razón para esto? Yo solo haría:

for i in sorted(indices, reverse=True):
    del somelist[i]

Si realmente no desea eliminar elementos al revés, entonces supongo que debería reducir los valores de los índices que son mayores que el último índice eliminado (realmente no puede usar el mismo índice ya que tiene una lista diferente) o usar una copia de la lista (que no estaría 'borrando' sino reemplazando el original con una copia editada).

¿Me falta algo aquí, alguna razón para NO eliminar en el orden inverso?

tglaria
fuente
1
¡No sé por qué esto no ha sido elegido como la respuesta aceptada! Gracias por esto.
swathis
44
Hay dos razones (a) Para una lista, la complejidad del tiempo sería mayor que el método de "hacer una copia" (usando un conjunto de índices) en promedio (suponiendo índices aleatorios) porque algunos elementos deben desplazarse hacia adelante varias veces. (b) Al menos para mí, es difícil de leer, porque hay una función de clasificación que no corresponde a ninguna lógica de programa real y existe únicamente por razones técnicas. Aunque a estas alturas ya entiendo la lógica a fondo, todavía siento que sería difícil de leer.
Noche imperecedera
1
@ImperishableNightpuedes elaborar (a)? No entiendo "algunos elementos deben ser cambiados". Para (b) podría definir una función si necesita claridad de lectura.
tglaria
109

Si está eliminando varios elementos no adyacentes, entonces lo que describe es la mejor manera (y sí, asegúrese de comenzar desde el índice más alto).

Si sus elementos son adyacentes, puede usar la sintaxis de asignación de divisiones:

a[2:10] = []
Greg Hewgill
fuente
95
También se puede decir del a[2:10]con el mismo efecto.
algo de
8
@sth Curiosamente, del es un poco más rápido que la asignación.
thefourtheye
24

Puedes usar numpy.deletelo siguiente:

import numpy as np
a = ['a', 'l', 3.14, 42, 'u']
I = [0, 2]
np.delete(a, I).tolist()
# Returns: ['l', '42', 'u']

Si no le importa terminar con una numpymatriz al final, puede omitir el .tolist(). También debería ver algunas mejoras de velocidad bastante importantes, lo que hace que esta sea una solución más escalable. No lo he comparado, pero las numpyoperaciones son código compilado escrito en C o Fortran.

philE
fuente
1
solución general cuando los elementos no son consecutivos +1
noɥʇʎԀʎzɐɹƆ
1
pregunta aquí, ¿qué hay de eliminar ['a', 42].
evanhutomo
ENORMES puntos de bonificación para esta solución, en comparación con los demás, por la velocidad. Lo que puedo decir es que para un conjunto de datos muy grande, me tomó varios minutos lograr algo que tomó solo unos segundos con un buen olmpy.
Legel
18

Como especialización de la respuesta de Greg, incluso puede usar la sintaxis de corte extendida. p.ej. Si desea eliminar los elementos 0 y 2:

>>> a= [0, 1, 2, 3, 4]
>>> del a[0:3:2]
>>> a
[1, 3, 4]

Esto no cubre ninguna selección arbitraria, por supuesto, pero ciertamente puede funcionar para eliminar dos elementos.

bobince
fuente
16

Como una función:

def multi_delete(list_, *args):
    indexes = sorted(list(args), reverse=True)
    for index in indexes:
        del list_[index]
    return list_

Se ejecuta en n log (n) tiempo, lo que debería convertirlo en la solución correcta más rápida hasta el momento.

Nikhil Chelliah
fuente
1
La versión con args.sort (). Reverse () es definitivamente mejor. También sucede que funciona con dictados en lugar de tirar o, lo que es peor, corromper silenciosamente.
sort () no está definido para tupla, primero debe convertir a la lista. sort () devuelve None, por lo que no podría usar reverse () en él.
SilentGhost
@ R. Pate: eliminé la primera versión por ese motivo. Gracias. @ SilentGhost: lo arregló.
Nikhil Chelliah el
@Nikhil: no, no lo hiciste;) args = list (args) args.sort () args.reverse () pero la mejor opción sería: args = sorted (args, reverse = True)
SilentGhost
2
n log n? De Verdad? No creo que del list[index]sea ​​O (1).
user202729
12

Entonces, ¿esencialmente quieres eliminar varios elementos en una sola pasada? En ese caso, la posición del siguiente elemento a eliminar se compensará con la cantidad que se haya eliminado anteriormente.

Nuestro objetivo es eliminar todas las vocales, que se calculan previamente como índices 1, 4 y 7. Tenga en cuenta que es importante que los índices to_delete estén en orden ascendente, de lo contrario no funcionará.

to_delete = [1, 4, 7]
target = list("hello world")
for offset, index in enumerate(to_delete):
  index -= offset
  del target[index]

Sería más complicado si quisieras eliminar los elementos en cualquier orden. En mi opinión, la clasificación to_deletepodría ser más fácil que averiguar cuándo debería o no restar index.

Richard Levasseur
fuente
8

Soy un principiante total en Python, y mi programación en este momento es cruda y sucia, por decir lo menos, pero mi solución fue usar una combinación de los comandos básicos que aprendí en los primeros tutoriales:

some_list = [1,2,3,4,5,6,7,8,10]
rem = [0,5,7]

for i in rem:
    some_list[i] = '!' # mark for deletion

for i in range(0, some_list.count('!')):
    some_list.remove('!') # remove
print some_list

Obviamente, debido a que tiene que elegir un carácter de "marca para eliminación", esto tiene sus limitaciones.

En cuanto al rendimiento ya que el tamaño de la lista se escala, estoy seguro de que mi solución es subóptima. Sin embargo, es sencillo, lo que espero atraiga a otros principiantes, y funcionará en casos simples en los que some_listsea ​​de un formato conocido, por ejemplo, siempre numérico ...

Pablo
fuente
2
En lugar de usar '!' como tu personaje especial, no uses ninguno. Esto mantiene todos los caracteres válidos y libera sus posibilidades
portforwardpodcast
5

Aquí hay una alternativa, que no utiliza enumerate () para crear tuplas (como en la respuesta original de SilentGhost).

Esto me parece más legible. (Tal vez me sentiría diferente si tuviera la costumbre de usar enumerar). CAVEAT: No he probado el rendimiento de los dos enfoques.

# Returns a new list. "lst" is not modified.
def delete_by_indices(lst, indices):
    indices_as_set = set(indices)
    return [ lst[i] for i in xrange(len(lst)) if i not in indices_as_set ]

NOTA: sintaxis de Python 2.7. Para Python 3, xrange=> range.

Uso:

lst = [ 11*x for x in xrange(10) ]
somelist = delete_by_indices( lst, [0, 4, 5])

somelista

[11, 22, 33, 66, 77, 88, 99]

--- BONIFICACIÓN ---

Eliminar múltiples valores de una lista. Es decir, tenemos los valores que queremos eliminar:

# Returns a new list. "lst" is not modified.
def delete__by_values(lst, values):
    values_as_set = set(values)
    return [ x for x in lst if x not in values_as_set ]

Uso:

somelist = delete__by_values( lst, [0, 44, 55] )

somelista

[11, 22, 33, 66, 77, 88, 99]

Esta es la misma respuesta que antes, pero esta vez proporcionamos los VALORES que se eliminarán [0, 44, 55].

ToolmakerSteve
fuente
He decidido que @ SilentGhost solo era difícil de leer, debido a los nombres de variables no descriptivos utilizados para el resultado de la enumeración. Además, los padres habrían facilitado la lectura. Así que aquí es cómo lo haría palabra que su solución (con "conjunto", agregó, para un rendimiento): [ value for (i, value) in enumerate(lst) if i not in set(indices) ]. Pero dejaré mi respuesta aquí, porque también muestro cómo eliminar por valores. Es un caso más fácil, pero podría ayudar a alguien.
ToolmakerSteve
@ Veedrac- gracias; He reescrito para construir el conjunto primero. ¿Qué piensas: una solución más rápida ahora que la de SilentGhost? (No considerarla lo suficientemente importante como para realmente tiempo, sólo pidiendo su opinión.) Del mismo modo, me gustaría volver a escribir la versión de SilentGhost como indices_as_set = set(indices), [ value for (i, value) in enumerate(lst) if i not in indices_as_set ], para acelerarlo.
ToolmakerSteve
¿Hay alguna razón de estilo para el doble guión bajo delete__by_values()?
Tom
5

Un método alternativo de comprensión de listas que utiliza valores de índice de listas:

stuff = ['a', 'b', 'c', 'd', 'e', 'f', 'woof']
index = [0, 3, 6]
new = [i for i in stuff if stuff.index(i) not in index]

Esto devuelve:

['b', 'c', 'e', 'f']
maullar
fuente
buena respuesta, pero nombrar la lista de índices como indexes engañoso ya que en el iterador de la lista se usa el métodoindex()
Joe
4

Aquí hay otro método que elimina los elementos en su lugar. Además, si su lista es realmente larga, es más rápida.

>>> a = range(10)
>>> remove = [0,4,5]
>>> from collections import deque
>>> deque((list.pop(a, i) for i in sorted(remove, reverse=True)), maxlen=0)

>>> timeit.timeit('[i for j, i in enumerate(a) if j not in remove]', setup='import random;remove=[random.randrange(100000) for i in range(100)]; a = range(100000)', number=1)
0.1704120635986328

>>> timeit.timeit('deque((list.pop(a, i) for i in sorted(remove, reverse=True)), maxlen=0)', setup='from collections import deque;import random;remove=[random.randrange(100000) for i in range(100)]; a = range(100000)', number=1)
0.004853963851928711
usuario545424
fuente
+1: Uso interesante de deque para realizar una acción for como parte de una expresión, en lugar de requerir un bloque "for ..:". Sin embargo, para este caso simple, encuentro que Nikhil's for block es más legible.
ToolmakerSteve
4

Esto se ha mencionado, pero de alguna manera nadie logró hacerlo bien.

En O(n)solución sería:

indices = {0, 2}
somelist = [i for j, i in enumerate(somelist) if j not in indices]

Esto está muy cerca de la versión de SilentGhost , pero agrega dos llaves.

Veedrac
fuente
Esto no O(n)ocurre si cuenta las búsquedas que se realizan log(len(indices))para cada iteración.
Físico loco
@MadPhysicist j not in indiceses O(1).
Veedrac
No estoy seguro de cómo obtener ese número. Dado que los índices son un conjunto, j not in indicesaún requiere búsqueda, que es O(log(len(indices))). Si bien estoy de acuerdo en que una búsqueda en un conjunto de 2 elementos califica como O(1), en el caso general lo será O(log(N)). De cualquier manera O(N log(N))todavía late O(N^2).
Físico loco
3
@MadPhysicist j not in indiceses O(1), en serio.
Veedrac
¿Y qué hicieron exactamente dos aparatos?
Nuclear03020704
4
l = ['a','b','a','c','a','d']
to_remove = [1, 3]
[l[i] for i in range(0, len(l)) if i not in to_remove])

Básicamente es lo mismo que la respuesta más votada, solo una forma diferente de escribirla. Tenga en cuenta que usar l.index () no es una buena idea, ya que no puede manejar elementos duplicados en una lista.

zinc
fuente
2

El método de eliminación provocará muchos cambios en los elementos de la lista. Creo que es mejor hacer una copia:

...
new_list = []
for el in obj.my_list:
   if condition_is_true(el):
      new_list.append(el)
del obj.my_list
obj.my_list = new_list
...
luca
fuente
2

técnicamente, la respuesta es NO, no es posible eliminar dos objetos AL MISMO TIEMPO. Sin embargo, ES posible eliminar dos objetos en una línea de Python hermosa.

del (foo['bar'],foo['baz'])

borrará recusivamente foo['bar'], luegofoo['baz']

David brillante
fuente
Esto se elimina de un objeto dict, no de una lista, pero todavía lo hago +1 porque es bastante bonito.
Ulf Aslak
También se aplica a la lista, con la sintaxis adecuada. Sin embargo, la afirmación es que no es posible eliminar dos objetos al mismo tiempo es falso; ver respuesta por @bobince
Pedro Gimeno
2

podemos hacer esto mediante el uso de un ciclo for iterando sobre los índices después de ordenar la lista de índices en orden descendente

mylist=[66.25, 333, 1, 4, 6, 7, 8, 56, 8769, 65]
indexes = 4,6
indexes = sorted(indexes, reverse=True)
for i in index:
    mylist.pop(i)
print mylist
Gourav Singla
fuente
2

Para los índices 0 y 2 de la lista A:

for x in (2,0): listA.pop(x)

Para algunos índices aleatorios para eliminar de la lista A:

indices=(5,3,2,7,0) 
for x in sorted(indices)[::-1]: listA.pop(x)
mermelada
fuente
2

Quería una forma de comparar las diferentes soluciones que facilitaban girar los mandos.

Primero generé mis datos:

import random

N = 16 * 1024
x = range(N)
random.shuffle(x)
y = random.sample(range(N), N / 10)

Luego definí mis funciones:

def list_set(value_list, index_list):
    index_list = set(index_list)
    result = [value for index, value in enumerate(value_list) if index not in index_list]
    return result

def list_del(value_list, index_list):
    for index in sorted(index_list, reverse=True):
        del(value_list[index])

def list_pop(value_list, index_list):
    for index in sorted(index_list, reverse=True):
        value_list.pop(index)

Luego solía timeitcomparar las soluciones:

import timeit
from collections import OrderedDict

M = 1000
setup = 'from __main__ import x, y, list_set, list_del, list_pop'
statement_dict = OrderedDict([
    ('overhead',  'a = x[:]'),
    ('set', 'a = x[:]; list_set(a, y)'),
    ('del', 'a = x[:]; list_del(a, y)'),
    ('pop', 'a = x[:]; list_pop(a, y)'),
])

overhead = None
result_dict = OrderedDict()
for name, statement in statement_dict.iteritems():
    result = timeit.timeit(statement, number=M, setup=setup)
    if overhead is None:
        overhead = result
    else:
        result = result - overhead
        result_dict[name] = result

for name, result in result_dict.iteritems():
    print "%s = %7.3f" % (name, result)

Salida

set =   1.711
del =   3.450
pop =   3.618

Entonces el generador con los índices en a setfue el ganador. Y deles un poco más rápido entonces pop.

David Cullen
fuente
Gracias por esta comparación, esto me llevó a hacer mis propias pruebas (en realidad solo tomé prestado su código) y para la pequeña cantidad de elementos que eliminar, la sobrecarga para crear un SET lo convierte en la peor solución (use 10, 100, 500 para el longitud de 'y' y verás). Como la mayoría de las veces, esto depende de la aplicación.
tglaria
2

Puedes usar esta lógica:

my_list = ['word','yes','no','nice']

c=[b for i,b in enumerate(my_list) if not i in (0,2,3)]

print c
raghu
fuente
2

Otra implementación de la idea de eliminar del índice más alto.

for i in range(len(yourlist)-1, -1, -1):
    del yourlist(i)
ipramusinto
fuente
1

De hecho, puedo pensar en dos formas de hacerlo:

  1. cortar la lista como (esto elimina los elementos primero, tercero y octavo)

    somelist = somelist [1: 2] + somelist [3: 7] + somelist [8:]

  2. hacer eso en su lugar, pero uno a la vez:

    somelist.pop (2) somelist.pop (0)

Bartosz Radaczyński
fuente
1

Puedes hacerlo así en un dict, no en una lista. En una lista los elementos están en secuencia. En un dict dependen solo del índice.

Código simple solo para explicarlo haciendo :

>>> lst = ['a','b','c']
>>> dct = {0: 'a', 1: 'b', 2:'c'}
>>> lst[0]
'a'
>>> dct[0]
'a'
>>> del lst[0]
>>> del dct[0]
>>> lst[0]
'b'
>>> dct[0]
Traceback (most recent call last):
  File "<pyshell#19>", line 1, in <module>
    dct[0]
KeyError: 0
>>> dct[1]
'b'
>>> lst[1]
'c'

Una forma de "convertir" una lista en un dict es:

>>> dct = {}
>>> for i in xrange(0,len(lst)): dct[i] = lst[i]

El inverso es:

lst = [dct[i] for i in sorted(dct.keys())] 

De todos modos, creo que es mejor comenzar a eliminar del índice más alto como dijiste.

Andrea Ambu
fuente
¿Python garantiza que [dct [i] para i in dct] siempre usará valores crecientes de i? Si es así, list (dct.values ​​()) es seguramente mejor.
No estaba pensando en eso. Tienes razón. No hay garantía al leer [aquí] [1] de que los artículos se recogerán en orden, o al menos el pedido esperado. Yo edité. [1]: docs.python.org/library/stdtypes.html#dict.items
Andrea Ambu
2
Esta respuesta habla sobre los diccionarios de una manera fundamentalmente incorrecta. Un diccionario tiene CLAVES (no INDICES). Sí, los pares clave / valor son independientes entre sí. No, no importa en qué orden elimine las entradas. Convertir en un diccionario solo para eliminar algunos elementos de una lista sería excesivo.
ToolmakerSteve
1

Para generalizar el comentario de @sth . La eliminación de elementos en cualquier clase, que implementa abc.MutableSequence , y listen particular, se realiza mediante un __delitem__método mágico. Este método funciona de manera similar __getitem__, lo que significa que puede aceptar un número entero o un segmento. Aquí hay un ejemplo:

class MyList(list):
    def __delitem__(self, item):
        if isinstance(item, slice):
            for i in range(*item.indices(len(self))):
                self[i] = 'null'
        else:
            self[item] = 'null'


l = MyList(range(10))
print(l)
del l[5:8]
print(l)

Esto dará salida

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 'null', 'null', 'null', 8, 9]
Alexander Zhukov
fuente
1

Importarlo solo por esta razón puede ser exagerado, pero si de pandastodos modos lo está utilizando , entonces la solución es simple y directa:

import pandas as pd
stuff = pd.Series(['a','b','a','c','a','d'])
less_stuff = stuff[stuff != 'a']  # define any condition here
# results ['b','c','d']
Lorinc Nyitrai
fuente
1
some_list.remove(some_list[max(i, j)])

Evita el costo de clasificación y tener que copiar explícitamente la lista.

Chester
fuente
0

¿Qué tal uno de estos (soy muy nuevo en Python, pero parecen estar bien):

ocean_basin = ['a', 'Atlantic', 'Pacific', 'Indian', 'a', 'a', 'a']
for i in range(1, (ocean_basin.count('a') + 1)):
    ocean_basin.remove('a')
print(ocean_basin)

['Atlántico', 'Pacífico', 'Indio']

ob = ['a', 'b', 4, 5,'Atlantic', 'Pacific', 'Indian', 'a', 'a', 4, 'a']
remove = ('a', 'b', 4, 5)
ob = [i for i in ob if i not in (remove)]
print(ob)

['Atlántico', 'Pacífico', 'Indio']

usuario12001090
fuente
0

Ninguna de las respuestas ofrecidas hasta ahora realiza la eliminación en su lugar en O (n) en la longitud de la lista para eliminar un número arbitrario de índices, así que aquí está mi versión:

def multi_delete(the_list, indices):
    assert type(indices) in {set, frozenset}, "indices must be a set or frozenset"
    offset = 0
    for i in range(len(the_list)):
        if i in indices:
            offset += 1
        elif offset:
            the_list[i - offset] = the_list[i]
    if offset:
        del the_list[-offset:]

# Example:
a = [0, 1, 2, 3, 4, 5, 6, 7]
multi_delete(a, {1, 2, 4, 6, 7})
print(a)  # prints [0, 3, 5]
Pedro Gimeno
fuente
0

Puedes usar remove, también.

delete_from_somelist = []
for i in [int(0), int(2)]:
     delete_from_somelist.append(somelist[i])
for j in delete_from_somelist:
     newlist = somelist.remove(j)
Jiwon Kim
fuente
0

Lo puse todo junto en una list_difffunción que simplemente toma dos listas como entradas y devuelve su diferencia, conservando el orden original de la primera lista.

def list_diff(list_a, list_b, verbose=False):

    # returns a difference of list_a and list_b,
    # preserving the original order, unlike set-based solutions

    # get indices of elements to be excluded from list_a
    excl_ind = [i for i, x in enumerate(list_a) if x in list_b]
    if verbose:
        print(excl_ind)

    # filter out the excluded indices, producing a new list 
    new_list = [i for i in list_a if list_a.index(i) not in excl_ind]
    if verbose:
        print(new_list)

    return(new_list)

Uso de la muestra:

my_list = ['a', 'b', 'c', 'd', 'e', 'f', 'woof']
# index = [0, 3, 6]

# define excluded names list
excl_names_list = ['woof', 'c']

list_diff(my_list, excl_names_list)
>> ['a', 'b', 'd', 'e', 'f']
mirekphd
fuente