Supprimer des chaînes vides d'une liste de chaînes

684

Je veux supprimer toutes les chaînes vides d'une liste de chaînes en python.

Mon idée ressemble à ceci:

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

Existe-t-il un moyen plus pythonique de le faire?

zerodx
la source
45
@Ivo, aucune de ces affirmations n'est vraie. Vous ne devez jamais modifier une liste que votre itération sur l'utilisation for x in listSi vous utilisez un while loopalors c'est très bien. la boucle démontrée supprimera les chaînes vides jusqu'à ce qu'il n'y ait plus de chaînes vides, puis s'arrêtera. En fait, je n'avais même pas regardé la question (juste le titre) mais j'ai répondu avec la même boucle exacte que possible! Si vous ne voulez pas utiliser de compréhensions ou de filtres pour la mémoire, c'est une solution très pythonique.
aaronasterling
4
Encore un point très valable pour ne jamais changer la liste que vous parcourez :)
Eduard Luca
1
@EduardLuca si le but d'itérer sur une liste est de la changer, c'est le contraire de ce que vous devez faire. Vous devez juste faire attention à ne pas provoquer un comportement inattendu en le faisant.
JFA
1
@EduardLuca, @JFA: Le fait est qu'il n'itère PAS sur une liste. Il le ferait s'il avait écrit quelque chose sous la forme for var in list:, mais ici, il a écrit while const in list:. qui n’itère pas sur quoi que ce soit. c'est simplement répéter le même code jusqu'à ce qu'une condition soit fausse.
Camion

Réponses:

1155

J'utiliserais 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 renvoie un itérateur de filter, devrait donc être encapsulé dans un appel àlist()

str_list = list(filter(None, str_list))
livibetter
la source
11
Si vous êtes que pressé pour la performance, itertooll »ifilter est même faster- >>> 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 Très vrai. Cependant, les ifilterrésultats sont évalués paresseusement, pas d'un seul coup - je dirais que pour la plupart des cas, ifilterc'est mieux. Il filterest intéressant de noter que l'utilisation est toujours plus rapide que d'envelopper un ifilterdans un list.
Humphrey Bogart le
3
Si vous effectuez cette opération sur une liste de nombres, notez que les zéros seront également supprimés (remarque: je n'ai utilisé que les 3 premières méthodes), vous aurez donc besoin d'une autre méthode.
SnoringFrog
2
Cela se concentre uniquement sur la vitesse, pas sur le caractère pythonique de la solution (la question qui a été posée). Les compréhensions de liste sont la solution pythonique, et le filtre ne doit être utilisé que si le profilage a prouvé que listcomp est un goulot d'étranglement.
Tritium21
3
@ whoever-mentions-about-or-imply-Python-3, veuillez simplement modifier et mettre à jour la réponse. Nous ne discutions que pour Python 2 lorsque cette question a été posée, même Python 3 est sorti presque 2 ans. Mais mettez à jour les résultats Python 2 et 3.
livibetter
237

Utiliser une compréhension de liste est la manière la plus Pythonique:

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

Si la liste doit être modifiée sur place, car d'autres références doivent voir les données mises à jour, utilisez une affectation de tranche:

strings[:] = [x for x in strings if x]
Ib33X
la source
16
J'aime cette solution car elle est facilement adaptable. Si je devais retirer non seulement des chaînes vides , mais les chaînes qui sont juste des espaces, par exemple: [x for x in strings if x.strip()].
Bond
67

le filtre a en fait une option spéciale pour cela:

filter(None, sequence)

Il filtrera tous les éléments évalués comme faux. Pas besoin d'utiliser un véritable appelable ici comme bool, len et ainsi de suite.

C'est aussi rapide que la carte (bool, ...)

Ivo van der Wijk
la source
5
C'est un idiome python, en fait. C'est aussi la seule fois où j'utilise encore filter (), les compréhensions de liste ont pris le dessus partout ailleurs.
kaleissin
24
>>> lstr = ['hello', '', ' ', 'world', ' ']
>>> lstr
['hello', '', ' ', 'world', ' ']

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

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

Comparez le temps

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

Notez que filter(None, lstr)cela ne supprime pas les chaînes vides avec un espace ' ', il ne taille ''que pendant qu'il ' '.join(lstr).split()supprime les deux.

Pour utiliser filter()avec les chaînes d'espaces supprimées, cela prend beaucoup plus de temps:

>>> timeit('filter(None, [l.replace(" ", "") for l in lstr])', "lstr=['hello', '', ' ', 'world', ' ']", number=10000000)
18.101892948150635
Aziz Alto
la source
cela ne fonctionnera pas si vous avez de l'espace parmi la chaîne d'un mot. par exemple: ['Bonjour tout le monde', '', 'Bonjour', '']. >> ['helloworld', '', 'hello', ''] avez-vous une autre solution pour conserver des espaces dans un élément de la liste mais en supprimer d'autres?
Reihan_amn
Notez que filter(None, lstr)cela ne supprime pas les chaînes vides avec un espace' ' Oui, car ce n'est pas une chaîne vide.
AMC
15

La réponse de @ Ib33X est géniale. Si vous souhaitez supprimer toutes les chaînes vides, après les avoir supprimées. vous devez également utiliser la méthode des bandes. Sinon, il renverra également la chaîne vide s'il contient des espaces blancs. Par exemple, "" sera également valable pour cette réponse. Donc, peut être atteint par.

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

La réponse sera ["first", "second"].
Si vous souhaitez utiliser la filterméthode à la place, vous pouvez faire comme
list(filter(lambda item: item.strip(), strings)). Cela donne le même résultat.

ssi-anik
la source
12

Au lieu de if x, j'utiliserais if X! = '' Afin d'éliminer simplement les chaînes vides. Comme ça:

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

Cela préservera le type de données Aucun dans votre liste. De plus, si votre liste contient des entiers et que 0 en fait partie, elle sera également conservée.

Par exemple,

str_list = [None, '', 0, "Hi", '', "Hello"]
[x for x in str_list if x != '']
[None, 0, "Hi", "Hello"]
thiruvenkadam
la source
2
Si vos listes ont des types disparates (sauf Aucun), vous pouvez avoir un problème plus important.
Tritium21
Quels types? J'ai essayé avec int et d'autres types numériques, chaînes, listes, tupes, ensembles et Aucun et aucun problème là-bas. Je pourrais voir que s'il existe des types définis par l'utilisateur qui ne prennent pas en charge la méthode str, cela pourrait poser un problème. Dois-je m'inquiéter pour un autre?
thiruvenkadam
1
Si vous en avez un str_list = [None, '', 0, "Hi", '', "Hello"], c'est le signe d'une application mal conçue. Vous ne devriez pas avoir plus d'une interface (type) et Aucune dans la même liste.
Tritium21
3
Récupérer des données de db? liste d'arguments pour une fonction lors de tests automatisés?
thiruvenkadam
3
Ce sont généralement des tuples.
Tritium21
7

Selon la taille de votre liste, cela peut être plus efficace si vous utilisez list.remove () plutôt que de créer une nouvelle liste:

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

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

Cela a l'avantage de ne pas créer une nouvelle liste, mais l'inconvénient d'avoir à chercher à chaque fois depuis le début, bien que contrairement à l'utilisation while '' in lcomme proposé ci-dessus, cela ne nécessite une recherche qu'une fois par occurrence de ''(il existe certainement un moyen de conserver le meilleur de les deux méthodes, mais c'est plus compliqué).

Andrew Jaffe
la source
1
Vous pouvez modifier la liste en place en faisant ary[:] = [e for e in ary if e]. Beaucoup plus propre et n'utilise pas d'exceptions pour contrôler le flux.
Krzysztof Karski
2
Eh bien, ce n'est pas vraiment "en place" - je suis presque sûr que cela crée une nouvelle liste et l'attribue simplement au nom de l'ancien.
Andrew Jaffe
Cela fonctionne très mal car la queue de données est mélangée dans la mémoire à chaque retrait. Mieux vaut supprimer tout en un coup.
wim
7

Gardez à l'esprit que si vous souhaitez conserver les espaces blancs dans une chaîne , vous pouvez les supprimer involontairement en utilisant certaines approches. Si vous avez cette liste

['bonjour le monde', '', '', 'bonjour'] ce que vous voudrez peut-être ['bonjour le monde', 'bonjour']

coupez d'abord la liste pour convertir tout type d'espace blanc en chaîne vide:

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

puis supprimez la chaîne vide de leur liste

space_clean_list = [x for x in space_to_empty if x]
Reihan_amn
la source
si vous souhaitez conserver les espaces blancs dans une chaîne, vous pouvez les supprimer involontairement en utilisant certaines approches. Vous aimez cette approche, alors?
AMC
Merci mec, ça a fonctionné pour moi avec un petit changement. à savoirspace_clean_list = [x.strip() for x in y if x.strip()]
Muhammad Mehran Khan Attari
6

Utilisation filter:

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

L'inconvénient de l'utilisation du filtre comme indiqué est qu'il est plus lent que les alternatives; aussi, lambdaest généralement coûteux.

Ou vous pouvez opter pour le plus simple et le plus itératif de tous:

# 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

c'est la plus intuitive des méthodes et elle le fait en temps décent.

Aamir Mushtaq
la source
9
Bienvenue chez SO. Vous n'avez pas été ignoré. Vous n'avez pas été attaqué par un électeur abattu non anodin. Vous avez reçu des commentaires. Amplification: le premier argument proposé pour le filtre est pire que lambda x: len(x)ce qui est pire que lambda x : xla pire des 4 solutions dans la réponse sélectionnée. Un fonctionnement correct est préférable, mais pas suffisant. Passez votre curseur sur le bouton downvote: il dit "Cette réponse n'est pas utile".
John Machin
5

Comme indiqué par Aziz Alto filter(None, lstr) ne supprime pas les chaînes vides avec un espace ' 'mais si vous êtes sûr que lstr ne contient que des chaînes, vous pouvez utiliserfilter(str.strip, lstr)

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

Comparer le temps sur mon 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

La solution la plus rapide pour supprimer ''et vider les chaînes avec un espace ' 'reste ' '.join(lstr).split().

Comme indiqué dans un commentaire, la situation est différente si vos chaînes contiennent des espaces.

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

Vous pouvez voir que filter(str.strip, lstr)conserver les chaînes avec des espaces, mais ' '.join(lstr).split()diviser ces chaînes.

Paolo Melchiorre
la source
1
Cela ne fonctionne que si vos chaînes ne contiennent pas d'espaces. Sinon, vous divisez également ces chaînes.
phillyslick
1
@BenPolinsky, comme vous l'avez signalé join, divisera les chaînes avec de l'espace, mais pas le filtre. Merci pour ton commentaire J'ai amélioré ma réponse.
Paolo Melchiorre
-1

Résumez les meilleures réponses:

1. Éliminez les vides SANS décapage:

Autrement dit, les chaînes de tous les espaces sont conservées:

slist = list(filter(None, slist))

Avantages:

  • le plus simple;
  • plus rapide (voir les repères ci-dessous).

2. Pour éliminer les vides après décapage ...

2.a ... lorsque les chaînes ne contiennent PAS d'espaces entre les mots:

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

Avantages:

  • petit code
  • rapide (MAIS pas plus rapide avec les grands ensembles de données en raison de la mémoire, contrairement aux résultats @ paolo-melchiorre)

2.b ... lorsque les chaînes contiennent des espaces entre les mots?

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

Avantages:

  • le plus rapide;
  • compréhensibilité du code.

Repères sur une machine 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
la source
s and s.strip()peut être simplifié à juste s.strip().
AMC
s and s.strip()est nécessaire si nous voulons reproduire entièrement filter(None, words), la réponse acceptée. J'ai corrigé x2 exemples de fonctions ci-dessus et supprimé x2 mauvaises.
ankostis
-2

Pour une liste avec une combinaison d'espaces et de valeurs vides, utilisez la compréhension de liste simple -

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

Donc, vous pouvez voir, cette liste a une combinaison d'espaces et d'éléments nuls. Utilisation de l'extrait -

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