Excluindo vários elementos de uma lista

160

É possível excluir vários elementos de uma lista ao mesmo tempo? Se eu quiser excluir elementos no índice 0 e 2 e tentar algo como del somelist[0], seguido por del somelist[2], a segunda instrução será realmente excluída somelist[3].

Suponho que sempre posso excluir os elementos com números mais altos primeiro, mas espero que exista uma maneira melhor.

Løiten
fonte

Respostas:

110

Provavelmente não é a melhor solução para este problema:

indices = 0, 2
somelist = [i for j, i in enumerate(somelist) if j not in indices]
SilentGhost
fonte
2
Quase, apenas se você excluir a lista inteira. será len (índices) * len (lista de som). Ele também cria uma cópia, que pode ou não ser desejada.
Richard Levasseur
se você está procurando um valor em uma lista, é isso. o operador 'in' trabalha com os valores de uma lista, enquanto trabalha com as chaves de um ditado. Se eu estiver errado, por favor me aponte para o PEP / referência
Richard Levasseur
5
a razão pela qual escolhi tupla para índices foi apenas a simplicidade do registro. seria um trabalho perfeito para set () dando O (n)
SilentGhost
18
Isso não está excluindo itens de nenhuma lista, mas criando uma lista totalmente nova. Se alguma coisa estiver mantendo uma referência à lista original, ela ainda terá todos os itens.
Tom Future
2
@SilentGhost Não é necessário fazer uma enumeração. Que tal isso somelist = [ lst[i] for i in xrange(len(lst)) if i not in set(indices) ]:?
usar o seguinte
183

Por alguma razão, não gosto de nenhuma das respostas aqui. Sim, eles funcionam, mas, estritamente falando, a maioria deles não está excluindo elementos de uma lista, pois não? (Mas faça uma cópia e substitua a original pela cópia editada).

Por que não excluir o índice mais alto primeiro?

Existe uma razão para isso? Eu apenas faria:

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

Se você realmente não deseja excluir itens de trás para frente, acho que você deve apenas incrementar os valores dos índices maiores que o último índice excluído (não é possível usar o mesmo índice porque você está com uma lista diferente) ou usar uma cópia da lista (que não seria 'excluída', mas substituindo o original por uma cópia editada).

Estou faltando alguma coisa aqui, algum motivo para NÃO excluir na ordem inversa?

tglaria
fonte
1
Não sei por que isso não foi escolhido como resposta aceita! Obrigado por isso.
swathis
4
Existem duas razões. (a) Para uma lista, a complexidade do tempo seria maior que o método "fazer uma cópia" (usando um conjunto de índices) em média (assumindo índices aleatórios) porque alguns elementos precisam ser deslocados para frente várias vezes. (b) Pelo menos para mim, é difícil de ler, porque existe uma função de classificação que não corresponde a nenhuma lógica real do programa e existe apenas por razões técnicas. Embora agora eu já entenda a lógica completamente, ainda sinto que seria difícil de ler.
Noite imperecível
1
@ImperishableNight você poderia elaborar (a)? Não entendo os "alguns elementos precisam ser alterados". Para (b) você pode apenas definir uma função se precisar de clareza de leitura.
tglaria
109

Se você estiver excluindo vários itens não adjacentes, o que você descreve é ​​a melhor maneira (e sim, certifique-se de começar do índice mais alto).

Se seus itens forem adjacentes, você poderá usar a sintaxe de atribuição de fatia:

a[2:10] = []
Greg Hewgill
fonte
95
Você também pode dizer del a[2:10]com o mesmo efeito.
sth
8
@sth Curiosamente, del é um pouco mais rápido que a atribuição.
thefourtheye
24

Você pode usar da numpy.deleteseguinte maneira:

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

Se você não se importa em terminar com uma numpymatriz no final, pode deixar de fora o .tolist(). Você também verá algumas melhorias importantes na velocidade, tornando a solução mais escalável. Eu não o comparei, mas as numpyoperações são código compilado escrito em C ou Fortran.

philE
fonte
1
solução geral quando os elementos não são consecutivos +1
noɥʇʎԀʎzɐɹƆ 23/06
1
pergunta aqui, que tal excluir ['a', 42].
evanhutomo
ENORME pontos de bônus para esta solução, em comparação com os outros, por velocidade. O que posso dizer é que, para um conjunto de dados muito grande, levei alguns minutos para conseguir algo que levou apenas alguns segundos com um bom e velho número.
legel
18

Como especialização da resposta de Greg, você pode até usar a sintaxe de fatia estendida. por exemplo. Se você deseja excluir os itens 0 e 2:

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

Isso não cobre nenhuma seleção arbitrária, é claro, mas certamente pode funcionar para excluir dois itens.

bobince
fonte
16

Como uma função:

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

Executa no tempo n log (n) , o que deve torná-lo a solução correta mais rápida até o momento.

Nikhil Chelliah
fonte
1
A versão com args.sort (). Reverse () é definitivamente melhor. Também acontece com os ditados, em vez de jogar ou, pior, corromper silenciosamente.
sort () não está definido para tupla, você deve converter primeiro na lista. sort () retorna None, então você não pode usar reverse () nele.
SilentGhost
@ R. Pate: Eu removi a primeira versão por esse motivo. Obrigado. @ SilentGhost: Corrigido.
Nikhil Chelliah
@ Nikhil: não, você não fez;) args = list (args) args.sort () args.reverse () mas a melhor opção seria: args = ordenado (args, reverse = True)
SilentGhost
2
n log n? Realmente? Eu não acho que del list[index]é O (1).
user202729
12

Então, você deseja excluir vários elementos de uma só vez? Nesse caso, a posição do próximo elemento a ser excluído será deslocada por muitos que foram excluídos anteriormente.

Nosso objetivo é excluir todas as vogais, que são pré-computadas como índices 1, 4 e 7. Observe que é importante que os índices to_delete estejam em ordem crescente, caso contrário, não funcionará.

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

Seria mais complicado se você quisesse excluir os elementos em qualquer ordem. Na IMO, a classificação to_deletepode ser mais fácil do que descobrir quando você deve ou não subtrair index.

Richard Levasseur
fonte
8

Sou iniciante em Python e minha programação no momento é grosseira e suja, para dizer o mínimo, mas minha solução foi usar uma combinação dos comandos básicos que aprendi nos primeiros tutoriais:

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, por ter que escolher um caractere "marca para exclusão", isso tem suas limitações.

Quanto ao desempenho conforme o tamanho da lista, tenho certeza de que minha solução está abaixo do ideal. No entanto, é simples, o que espero atrair outros iniciantes, e funcionará em casos simples, onde some_listé de um formato conhecido, por exemplo, sempre numérico ...

Paulo
fonte
2
ao invés de usar '!' como seu personagem especial, use Nenhum. Isso mantém todos os personagens válidos e libera o seu possibilidades
portforwardpodcast
5

Aqui está uma alternativa, que não usa enumerate () para criar tuplas (como na resposta original do SilentGhost).

Isso me parece mais legível. (Talvez eu me sentisse diferente se tivesse o hábito de usar enumerar.) CAVEAT: Não testei o desempenho das duas abordagens.

# 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: Sintaxe do 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])

lista de som:

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

--- BÔNUS ---

Exclua vários valores de uma lista. Ou seja, temos os valores que queremos excluir:

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

lista de som:

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

Esta é a mesma resposta de antes, mas desta vez fornecemos os VALORES a serem excluídos [0, 44, 55].

Fabricante de ferramentas
fonte
Decidi que o @ SilentGhost's só era difícil de ler, devido aos nomes de variáveis ​​não descritivos usados ​​para o resultado do enumerado. Além disso, parens teria facilitado a leitura. Então aqui está como eu faria palavra sua solução (com "set", acrescentou, para o desempenho): [ value for (i, value) in enumerate(lst) if i not in set(indices) ]. Mas vou deixar minha resposta aqui, porque também mostro como excluir por valores. Qual é o caso mais fácil, mas pode ajudar alguém.
Home
@ Veedrac- obrigado; Eu reescrevi para criar o conjunto primeiro. O que você acha - solução mais rápida agora do que o SilentGhost? (Eu não consideram importante o suficiente para realmente tempo, apenas pedindo a sua opinião.) Da mesma forma, gostaria de re-escrever a versão de SilentGhost como indices_as_set = set(indices), [ value for (i, value) in enumerate(lst) if i not in indices_as_set ], para acelerá-lo.
Home
Existe uma razão de estilo para o sublinhado duplo em delete__by_values()?
Tom
5

Um método alternativo de compreensão de lista que usa valores de índice de lista:

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]

Isso retorna:

['b', 'c', 'e', 'f']
Miau
fonte
boa resposta, mas nomeando a lista de índices como indexé enganador, uma vez na lista de iterador é usado o métodoindex()
Joe
4

Aqui está outro método que remove os elementos no lugar. Além disso, se sua lista for realmente longa, será mais 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
user545424
fonte
+1: Uso interessante de deque para executar um for action como parte de uma expressão, em vez de exigir um bloco "for ..:". No entanto, para este caso simples, acho o bloco de Nikhil mais legível.
usar o seguinte
4

Isso foi mencionado, mas de alguma forma ninguém conseguiu acertar.

Na O(n)solução seria:

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

Isso está muito próximo da versão do SilentGhost , mas adiciona duas chaves.

Veedrac
fonte
Isso não acontece O(n)se você contar as pesquisas necessárias log(len(indices))para cada iteração.
Físico Louco
@MadPhysicist j not in indicesé O(1).
Veedrac
Não tenho certeza de como você consegue esse número. Como os índices são um conjunto, j not in indicesainda é necessário procurar, o que é O(log(len(indices))). Embora eu concorde que uma pesquisa em um conjunto de 2 elementos se qualifique como O(1), no caso geral, será O(log(N)). De qualquer maneira O(N log(N))ainda bate O(N^2).
Mad Físico
3
@ MadPhysicist j not in indicesé O(1), sério.
Veedrac
E o que exatamente dois aparelhos fazem?
Nuclear03020704 15/06
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])

É basicamente o mesmo que a resposta mais votada, apenas uma maneira diferente de escrevê-la. Observe que usar l.index () não é uma boa ideia, porque não pode manipular elementos duplicados em uma lista.

zinco
fonte
2

O método Remove causa muitas mudanças nos elementos da lista. Eu acho que é melhor fazer uma cópia:

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

tecnicamente, a resposta é NÃO, não é possível excluir dois objetos AO MESMO TEMPO. No entanto, é possível excluir dois objetos em uma linha de belo python.

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

excluirá recusrively foo['bar'], entãofoo['baz']

David Brilliant
fonte
Isso exclui de um objeto dict, não de uma lista, mas ainda estou marcando com +1 porque é muito bonito!
Ulf Aslak
Também se aplica à lista, com sintaxe apropriada. No entanto, a alegação é que não é possível excluir dois objetos ao mesmo tempo, é falso; ver resposta por @bobince
Pedro Gimeno
2

podemos fazer isso usando um loop for iterando sobre os índices depois de classificar a lista de índices em ordem decrescente

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
fonte
2

Para os índices 0 e 2 da lista A:

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

Para remover alguns índices aleatórios da lista A:

indices=(5,3,2,7,0) 
for x in sorted(indices)[::-1]: listA.pop(x)
geléia
fonte
2

Eu queria uma maneira de comparar as diferentes soluções que tornavam mais fácil girar os botões.

Primeiro eu gerei meus dados:

import random

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

Então eu defini minhas funções:

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)

Então eu costumava timeitcomparar as soluções:

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)

Resultado

set =   1.711
del =   3.450
pop =   3.618

Então o gerador com os índices em a setfoi o vencedor. E delé um pouco mais rápido então pop.

David Cullen
fonte
Obrigado por essa comparação, isso me levou a fazer meus próprios testes (na verdade, apenas emprestou seu código) e, para o pequeno número de itens a serem removidos, a sobrecarga para criar um SET torna a pior solução (use 10, 100, 500 para o comprimento de 'y' e você verá). Como na maioria das vezes, isso depende da aplicação.
Tglaria 10/10
2

Você pode 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
fonte
2

Outra implementação da idéia de remover do índice mais alto.

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

Eu posso pensar em duas maneiras de fazer isso:

  1. divida a lista como (isso exclui o primeiro, o terceiro e o oitavo elementos)

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

  2. faça isso no lugar, mas um de cada vez:

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

Bartosz Radaczyński
fonte
1

Você pode fazer isso em um ditado, não em uma lista. Em uma lista, os elementos estão em sequência. Em um ditado, eles dependem apenas do índice.

Código simples apenas para explicar, fazendo :

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

Uma maneira de "converter" uma lista em um ditado é:

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

O inverso é:

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

Enfim, acho melhor começar a excluir do índice mais alto, como você disse.

Andrea Ambu
fonte
O Python garante que [dct [i] para i in dct] sempre usará valores crescentes de i? Nesse caso, list (dct.values ​​()) é certamente melhor.
Eu não estava pensando nisso. Você está certo. Não há garantia, como li aqui, que os itens serão escolhidos em ordem, ou pelo menos na ordem esperada. Eu editei. [1]: docs.python.org/library/stdtypes.html#dict.items
Andrea Ambu
2
Esta resposta fala sobre dicionários de uma maneira fundamentalmente errada. Um dicionário possui CHAVES (não ÍNDICES). Sim, os pares chave / valor são independentes um do outro. Não, não importa em que ordem você exclui as entradas. Converter em dicionário apenas para excluir alguns elementos de uma lista seria um exagero.
usar o seguinte
1

Para generalizar o comentário de @sth . A exclusão do item em qualquer classe que implemente o abc.MutableSequence e, listem particular, é feita por meio do __delitem__método mágico. Esse método funciona de maneira semelhante a __getitem__, o que significa que ele pode aceitar um número inteiro ou uma fatia. Aqui está um exemplo:

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)

Isso produzirá

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

Importá-lo apenas por esse motivo pode ser um exagero, mas se você o estiver usando de pandasqualquer maneira, a solução será simples e direta:

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
fonte
1
some_list.remove(some_list[max(i, j)])

Evita o custo de classificação e a necessidade de copiar explicitamente a lista.

Chester
fonte
0

Que tal um deles (eu sou muito novo no Python, mas eles parecem bem):

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', 'Indiano']

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', 'Indiano']

user12001090
fonte
0

Nenhuma das respostas oferecidas até agora executa a exclusão em O (n) no comprimento da lista para um número arbitrário de índices a serem excluídos, então aqui está minha versão:

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
fonte
0

Você pode usar remover também.

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
fonte
0

Reuni tudo em uma list_difffunção que simplesmente pega duas listas como entradas e retorna sua diferença, preservando a ordem original da primeira 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 da amostra:

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
fonte