Remover cadeias de caracteres vazias de uma lista de cadeias

682

Eu quero remover todas as seqüências de caracteres vazias de uma lista de seqüências de caracteres em python.

Minha ideia é assim:

while '' in str_list:
    str_list.remove('')

Existe alguma maneira mais pitônica de fazer isso?

zerodx
fonte
45
@Vo, nenhuma dessas afirmações é verdadeira. Você nunca deve modificar uma lista que for x in listvocê está usando repetidamente. Se você estiver usando um while loop, tudo bem. o loop demonstrado removerá cadeias vazias até que não haja mais cadeias vazias e depois pare. Na verdade, eu nem sequer olhei para a pergunta (apenas o título), mas respondi exatamente com o mesmo loop que uma possibilidade! Se você não deseja usar compreensões ou filtros para fins de memória, é uma solução muito pitônica.
Aaronasterling 02/10/10
4
Ainda um ponto muito válido nunca alterar a lista que você está interagindo sobre :)
Eduard Luca
1
@EduardLuca se o objetivo de iterar sobre uma lista for alterá-la, é o contrário do que você deve fazer. Você só precisa ter cuidado para saber que não causa um comportamento inesperado ao fazê-lo.
JFA 01/04
1
@EduardLuca, @JFA: O ponto é que ele NÃO está iterando sobre nenhuma lista. Ele escreveria se tivesse escrito algo na forma for var in list:, mas aqui, ele escreveu while const in list:. que não está iterando sobre nada. é apenas repetir o mesmo código até que uma condição seja falsa.
Camion

Respostas:

1150

Eu usaria filter:

str_list = filter(None, str_list)
str_list = filter(bool, str_list)
str_list = filter(len, str_list)
str_list = filter(lambda item: item, str_list)

Python 3 retorna um iterador de filter, portanto, deve ser agrupado em uma chamada paralist()

str_list = list(filter(None, str_list))
livibetter
fonte
11
Se você é tão pressionado pelo desempenho, itertooloifilter é ainda mais rápido >>> timeit('filter(None, str_list)', 'str_list=["a"]*1000', number=100000) 2.3468542098999023; >>> timeit('itertools.ifilter(None, str_list)', 'str_list=["a"]*1000', number=100000) 0.04442191123962402.
Humphrey Bogart
4
@cpburnz Muito verdade. No entanto, com os ifilterresultados são avaliados preguiçosamente, não de uma só vez - eu diria que, na maioria dos casos, ifilteré melhor. Interessante que o uso filterainda é mais rápido do que envolver um ifilterem um listpensamento.
Humphrey Bogart
3
Se você fizer isso em uma lista de números, observe que os zeros também serão removidos (nota: usei apenas os três primeiros métodos); portanto, você precisará de um método alternativo.
precisa saber é o seguinte
2
Isso se concentra apenas na velocidade, não em quão pitônica é a solução (a pergunta que foi feita). As compreensões de lista são a solução pitônica e o filtro deve ser usado apenas se a criação de perfil provar que o listcomp é um gargalo.
Tritium21
3
@ quem mencionar sobre Python-3 ou implicar-3, basta editar e atualizar a resposta. Estávamos discutindo apenas para o Python 2 quando essa pergunta foi feita, até o Python 3 foi lançado por quase 2 anos. Mas atualize os resultados do Python 2 e 3.
livibetter 29/03/16
236

Usar uma compreensão de lista é a maneira mais pitônica:

>>> strings = ["first", "", "second"]
>>> [x for x in strings if x]
['first', 'second']

Se a lista precisar ser modificada no local, porque existem outras referências que devem ver os dados atualizados, use uma atribuição de fatia:

strings[:] = [x for x in strings if x]
Ib33X
fonte
16
Eu gosto desta solução porque é facilmente adaptável. Se eu precisava para remover não apenas strings vazias, mas cordas que são apenas espaços em branco, por exemplo: [x for x in strings if x.strip()].
Bond
67

filtro realmente tem uma opção especial para isso:

filter(None, sequence)

Ele filtrará todos os elementos que avaliarem como Falso. Não há necessidade de usar uma chamada real aqui, como bool, len e assim por diante.

É igualmente rápido como mapa (bool, ...)

Ivo van der Wijk
fonte
5
Este é um idioma python, de fato. É também a única vez em que ainda uso o filter (); as compreensões de lista assumiram todo o resto.
Kaleissin
24
>>> lstr = ['hello', '', ' ', 'world', ' ']
>>> lstr
['hello', '', ' ', 'world', ' ']

>>> ' '.join(lstr).split()
['hello', 'world']

>>> filter(None, lstr)
['hello', ' ', 'world', ' ']

Compare tempo

>>> from timeit import timeit
>>> timeit('" ".join(lstr).split()', "lstr=['hello', '', ' ', 'world', ' ']", number=10000000)
4.226747989654541
>>> timeit('filter(None, lstr)', "lstr=['hello', '', ' ', 'world', ' ']", number=10000000)
3.0278358459472656

Observe que filter(None, lstr)não remove seqüências de caracteres vazias com um espaço ' ', apenas remove-as ''enquanto ' '.join(lstr).split()remove as duas.

Para usar filter()com as cadeias de espaço em branco removidas, leva muito mais tempo:

>>> timeit('filter(None, [l.replace(" ", "") for l in lstr])', "lstr=['hello', '', ' ', 'world', ' ']", number=10000000)
18.101892948150635
Aziz Alto
fonte
não funcionará se você tiver espaço entre a sequência de uma palavra. por exemplo: ['olá mundo', '', 'olá', '']. >> ['helloworld', '', 'hello', ''] você tem outra solução para manter espaços dentro de um item da lista, mas remover outros?
Reihan_amn
Observe que filter(None, lstr)não remove cadeias vazias com um espaço' ' Sim, porque não é uma cadeia vazia.
AMC
15

A resposta de @ Ib33X is awesome. Se você deseja remover todas as cordas vazias, depois de retiradas. você precisa usar o método strip também. Caso contrário, ele retornará a sequência vazia também se tiver espaços em branco. Como "" também será válido para essa resposta. Então, pode ser alcançado por.

strings = ["first", "", "second ", " "]
[x.strip() for x in strings if x.strip()]

A resposta para isso será ["first", "second"].
Se você deseja usar o filtermétodo, pode fazer o mesmo
list(filter(lambda item: item.strip(), strings)). Este é o mesmo resultado.

ssi-anik
fonte
12

Em vez de se x, eu usaria se X! = '' Para eliminar apenas cadeias vazias. Como isso:

str_list = [x for x in str_list if x != '']

Isso preservará Nenhum tipo de dados em sua lista. Além disso, caso sua lista tenha números inteiros e 0 seja um deles, ela também será preservada.

Por exemplo,

str_list = [None, '', 0, "Hi", '', "Hello"]
[x for x in str_list if x != '']
[None, 0, "Hi", "Hello"]
thiruvenkadam
fonte
2
Se suas listas tiverem tipos diferentes (exceto Nenhum), você poderá ter um problema maior.
Tritium21
Quais tipos? Eu tentei com int e outros tipos numéricos, seqüências de caracteres, listas, tupes, conjuntos e nenhum e nenhum problema lá. Pude ver que, se houver algum tipo definido pelo usuário que não suporte o método str, isso pode causar um problema. Eu deveria estar preocupado com outro?
thiruvenkadam 23/02
1
Se você tiver um str_list = [None, '', 0, "Hi", '', "Hello"], é um sinal de um aplicativo mal projetado. Você não deve ter mais de uma interface (tipo) e Nenhuma na mesma lista.
Tritium21
3
Recuperando dados do db? lista de argumentos para uma função durante o teste automatizado?
238156 thirteenkadam
3
Geralmente são tuplas.
Tritium21
7

Dependendo do tamanho da sua lista, pode ser mais eficiente se você usar list.remove () em vez de criar uma nova lista:

l = ["1", "", "3", ""]

while True:
  try:
    l.remove("")
  except ValueError:
    break

Isso tem a vantagem de não criar uma nova lista, mas a desvantagem de ter que pesquisar desde o início de cada vez, embora, ao contrário do while '' in lque foi proposto acima, requer apenas uma pesquisa por ocorrência de ''(certamente existe uma maneira de manter o melhor ambos os métodos, mas é mais complicado).

Andrew Jaffe
fonte
1
Você pode editar a lista no local fazendo ary[:] = [e for e in ary if e]. Muito mais limpo e não usa exceções para o fluxo de controle.
Krzysztof Karski
2
Bem, isso não está realmente "no lugar" - tenho certeza de que isso cria uma nova lista e a atribui apenas ao nome da pessoa antiga.
Andrew Jaffe
Isso funciona muito mal à medida que a cauda dos dados é embaralhada na memória a cada remoção. Melhor remover tudo em um hit.
wim 9/01
7

Lembre-se de que, se você quiser manter os espaços em branco em uma string , remova-os sem querer usando algumas abordagens. Se você tem esta lista

['olá mundo', '', '', 'olá']] o que você pode querer ['olá mundo', 'olá']

primeiro apare a lista para converter qualquer tipo de espaço em branco em string vazia:

space_to_empty = [x.strip() for x in _text_list]

em seguida, remova a string vazia da lista deles

space_clean_list = [x for x in space_to_empty if x]
Reihan_amn
fonte
se você quiser manter os espaços em branco em uma string, remova-os sem querer usando algumas abordagens. Como esta abordagem, então?
AMC
Obrigado cara, funcionou para mim com uma pequena mudança. iespace_clean_list = [x.strip() for x in y if x.strip()]
Muhammad Mehran Khan Attari
6

Use filter:

newlist=filter(lambda x: len(x)>0, oldlist) 

As desvantagens de usar o filtro, conforme apontado, é que ele é mais lento que as alternativas; Além disso, lambdageralmente é caro.

Ou você pode optar pelo mais simples e mais iterativo de todos:

# I am assuming listtext is the original list containing (possibly) empty items
for item in listtext:
    if item:
        newlist.append(str(item))
# You can remove str() based on the content of your original list

esse é o método mais intuitivo e o faz em tempo decente.

Aamir Mushtaq
fonte
9
Bem-vindo ao SO. Você não foi ignorado. Você não foi atacado por um downvoter desagradável. Você recebeu feedback. Amplificação: o primeiro argumento proposto para o filtro é pior do lambda x: len(x)que lambda x : xo pior e a pior das 4 soluções na resposta selecionada. O funcionamento correto é preferido, mas não suficiente. Passe o cursor sobre o botão de voto negativo: ele diz "Esta resposta não é útil".
John Machin 11/01
5

Conforme relatado por Aziz Alto filter(None, lstr) , não remove cadeias vazias com um espaço, ' 'mas se você tiver certeza de que o lstr contém apenas cadeias, você pode usarfilter(str.strip, lstr)

>>> lstr = ['hello', '', ' ', 'world', ' ']
>>> lstr
['hello', '', ' ', 'world', ' ']
>>> ' '.join(lstr).split()
['hello', 'world']
>>> filter(str.strip, lstr)
['hello', 'world']

Compare o tempo no meu pc

>>> from timeit import timeit
>>> timeit('" ".join(lstr).split()', "lstr=['hello', '', ' ', 'world', ' ']", number=10000000)
3.356455087661743
>>> timeit('filter(str.strip, lstr)', "lstr=['hello', '', ' ', 'world', ' ']", number=10000000)
5.276503801345825

A solução mais rápida para remover ''e esvaziar cordas com um espaço ' 'permanece ' '.join(lstr).split().

Conforme relatado em um comentário, a situação será diferente se suas sequências contiverem espaços.

>>> lstr = ['hello', '', ' ', 'world', '    ', 'see you']
>>> lstr
['hello', '', ' ', 'world', '    ', 'see you']
>>> ' '.join(lstr).split()
['hello', 'world', 'see', 'you']
>>> filter(str.strip, lstr)
['hello', 'world', 'see you']

Você pode ver que filter(str.strip, lstr)preserva as strings com espaços, mas as ' '.join(lstr).split()dividirá.

Paolo Melchiorre
fonte
1
Isso funciona apenas se suas seqüências de caracteres não contiverem espaços. Caso contrário, você também estará dividindo essas strings.
phillyslick
1
A @BenPolinsky, como você relatou, a joinsolução dividirá as strings com espaço, mas o filtro não. Obrigado pelo seu comentário, melhorei minha resposta.
Paolo Melchiorre
-1

Resuma as melhores respostas:

1. Eliminar vazios SEM remover:

Ou seja, as seqüências de todos os espaços são mantidas:

slist = list(filter(None, slist))

PROs:

  • mais simples;
  • mais rápido (veja os benchmarks abaixo).

2. Para eliminar vazios após a remoção ...

2.a ... quando as strings NÃO contêm espaços entre as palavras:

slist = ' '.join(slist).split()

PROs:

  • código pequeno
  • rápido (mas não mais rápido com grandes conjuntos de dados devido à memória, ao contrário do que resulta em @ paolo-melchiorre)

2.b ... quando strings contêm espaços entre as palavras?

slist = list(filter(str.strip, slist))

PROs:

  • o mais rápido;
  • compreensibilidade do código.

Benchmarks em uma máquina de 2018:

## Build test-data
#
import random, string
nwords = 10000
maxlen = 30
null_ratio = 0.1
rnd = random.Random(0)                  # deterministic results
words = [' ' * rnd.randint(0, maxlen)
         if rnd.random() > (1 - null_ratio)
         else
         ''.join(random.choices(string.ascii_letters, k=rnd.randint(0, maxlen)))
         for _i in range(nwords)
        ]

## Test functions
#
def nostrip_filter(slist):
    return list(filter(None, slist))

def nostrip_comprehension(slist):
    return [s for s in slist if s]

def strip_filter(slist):
    return list(filter(str.strip, slist))

def strip_filter_map(slist): 
    return list(filter(None, map(str.strip, slist))) 

def strip_filter_comprehension(slist):  # waste memory
    return list(filter(None, [s.strip() for s in slist]))

def strip_filter_generator(slist):
    return list(filter(None, (s.strip() for s in slist)))

def strip_join_split(slist):  # words without(!) spaces
    return ' '.join(slist).split()

## Benchmarks
#
%timeit nostrip_filter(words)
142 µs ± 16.8 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

%timeit nostrip_comprehension(words)
263 µs ± 19.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit strip_filter(words)
653 µs ± 37.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit strip_filter_map(words)
642 µs ± 36 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit strip_filter_comprehension(words)
693 µs ± 42.2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit strip_filter_generator(words)
750 µs ± 28.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit strip_join_split(words)
796 µs ± 103 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
ankostis
fonte
s and s.strip()pode ser simplificado para apenas s.strip().
AMC
s and s.strip() é necessário se quisermos replicar completamente filter(None, words) , a resposta aceita. Corrigi x2 funções de amostra acima e larguei x2 más.
ankostis 10/01
-2

Para uma lista com uma combinação de espaços e valores vazios, use a compreensão simples da lista -

>>> s = ['I', 'am', 'a', '', 'great', ' ', '', '  ', 'person', '!!', 'Do', 'you', 'think', 'its', 'a', '', 'a', '', 'joke', '', ' ', '', '?', '', '', '', '?']

Então, como você pode ver, esta lista possui uma combinação de espaços e elementos nulos. Usando o snippet -

>>> d = [x for x in s if x.strip()]
>>> d
>>> d = ['I', 'am', 'a', 'great', 'person', '!!', 'Do', 'you', 'think', 'its', 'a', 'a', 'joke', '?', '?']
Scid
fonte