Usuń puste ciągi z listy ciągów


683

Chcę usunąć wszystkie puste ciągi z listy ciągów w pythonie.

Mój pomysł wygląda następująco:

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

Czy jest jakiś pythoniczny sposób na zrobienie tego?


45
@Ivo, żadne z tych stwierdzeń nie jest prawdziwe. Nigdy nie powinieneś modyfikować listy, której używasz podczas iteracji. for x in listJeśli używasz, while loopto jest w porządku. pokazana pętla usunie puste ciągi, dopóki nie będzie już pustych ciągów, a następnie zatrzyma się. Właściwie nawet nie spojrzałem na pytanie (tylko tytuł), ale odpowiedziałem dokładnie taką samą pętlą jak to możliwe! Jeśli nie chcesz używać wyrażeń ani filtrów ze względu na pamięć, jest to bardzo pytoniczne rozwiązanie.
aaronasterling

4
Nadal bardzo ważny punkt, aby nigdy nie zmieniać listy, nad którą się iterujesz :)
Eduard Luca

1
@EduardLuca, jeśli celem iteracji po liście jest jej zmiana, jest to odwrotność tego, co powinieneś zrobić. Musisz tylko uważać, aby nie spowodować nieoczekiwanego zachowania.
JFA

1
@EduardLuca, @JFA: Chodzi o to, że NIE iteruje po żadnej liście. Zrobiłby, gdyby napisał coś w formie for var in list:, ale tutaj napisał while const in list:. co nie jest niczym. po prostu powtarza ten sam kod, dopóki warunek nie zostanie spełniony.
Camion

Odpowiedzi:


1154

Użyłbym 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 zwraca iterator filter, dlatego powinien być zawarty w wywołaniu dolist()

str_list = list(filter(None, str_list))

11
Jeśli jesteś , że wciśnięty do wykonania, itertooljestifilter to nawet 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 Very true. Jednak ifilterwyniki są oceniane leniwie, a nie za jednym razem - argumentowałbym, że w większości przypadków ifilterjest to lepsze. Ciekawe, że przy użyciu filterjest nadal szybszy niż owijania ifilterw listchociaż.
Humphrey Bogart,

3
Jeśli zrobisz to z listą liczb, zauważ, że zera również zostaną usunięte (uwaga: użyłem tylko pierwszych 3 metod), więc będziesz potrzebować alternatywnej metody.
SnoringFrog

2
Koncentruje się tylko na szybkości, a nie na pythonicznym rozwiązaniu (pytanie, które zostało zadane). Zrozumienie listy jest rozwiązaniem pythonowym, a filtru należy używać tylko wtedy, gdy profilowanie wykazało, że listcomp jest wąskim gardłem.
Tryt21

3
@ ktokolwiek-wspomina o-lub-sugeruje-Python-3, po prostu edytuj i zaktualizuj odpowiedź. Dyskutowaliśmy o Pythonie 2, kiedy zadano to pytanie, nawet Python 3 został wydany prawie 2 lata. Ale zaktualizuj zarówno wyniki Python 2, jak i 3.
Livivetter

237

Korzystanie ze zrozumienia listy jest najbardziej pythonicznym sposobem:

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

Jeśli lista musi zostać zmodyfikowana w miejscu, ponieważ istnieją inne odwołania, które muszą zobaczyć zaktualizowane dane, użyj przypisania wycinka:

strings[:] = [x for x in strings if x]

16
Podoba mi się to rozwiązanie, ponieważ można je łatwo dostosować. Gdybym musiał usunąć nie tylko pustych strun, ale struny, które są po prostu białe znaki, np [x for x in strings if x.strip()].
Bond

67

filtr ma do tego specjalną opcję:

filter(None, sequence)

Odfiltruje wszystkie elementy, które mają wartość False. Nie ma potrzeby używania tutaj rzeczywistego urządzenia do wywoływania, takiego jak bool, len i tak dalej.

Jest równie szybki jak mapa (bool, ...)


5
W rzeczywistości jest to idiom python. Jest to również jedyny raz, kiedy wciąż używam filter (), wyliczenia list przejęły się wszędzie indziej.
kaleissin

24
>>> lstr = ['hello', '', ' ', 'world', ' ']
>>> lstr
['hello', '', ' ', 'world', ' ']

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

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

Porównaj czas

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

Zauważ, że filter(None, lstr)nie usuwa pustych ciągów ze spacją ' ', tylko przycina się ''podczas ' '.join(lstr).split()usuwania obu.

Aby użyć filter()z usuniętymi ciągami białych znaków, zajmuje to dużo więcej czasu:

>>> timeit('filter(None, [l.replace(" ", "") for l in lstr])', "lstr=['hello', '', ' ', 'world', ' ']", number=10000000)
18.101892948150635

nie zadziała, jeśli między ciągiem słowa jest spacja. na przykład: [„witaj świecie”, „”, „witaj”, „”]. >> [„helloworld”, „”, „hello”, „”] Czy masz jakieś inne rozwiązanie, aby zachować spacje w obrębie elementu na liście, ale usuwać inne?
Reihan_amn

Zauważ, że filter(None, lstr)nie usuwa pustych ciągów ze spacją.' ' Tak, ponieważ to nie jest pusty ciąg.
AMC

15

Odpowiedź od @ Ib33X jest niesamowita. Jeśli chcesz usunąć każdy pusty ciąg, po usunięciu. musisz także użyć metody pasków. W przeciwnym razie zwróci również pusty ciąg, jeśli ma białe spacje. Podobnie „” będzie również ważne dla tej odpowiedzi. Można to osiągnąć przez.

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

Odpowiedź na to będzie ["first", "second"].
Jeśli filterzamiast tego chcesz użyć metody, możesz zrobić podobnie
list(filter(lambda item: item.strip(), strings)). To daje ten sam wynik.


12

Zamiast if x użyłbym if X! = '', Aby po prostu wyeliminować puste ciągi. Lubię to:

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

Pozwoli to zachować typ danych Brak na liście. Ponadto, jeśli twoja lista zawiera liczby całkowite, a 0 jest jedną z nich, również zostanie zachowana.

Na przykład,

str_list = [None, '', 0, "Hi", '', "Hello"]
[x for x in str_list if x != '']
[None, 0, "Hi", "Hello"]

2
Jeśli Twoje listy mają różne typy (z wyjątkiem Brak), możesz mieć większy problem.
Tryt21

Jakie typy? Próbowałem z int i innymi typami liczbowymi, łańcuchami, listami, tupami, setami i None i żadnych problemów. Widziałem, że jeśli istnieją jakieś typy zdefiniowane przez użytkownika, które nie obsługują metody str, mogą stanowić problem. Czy powinienem się martwić o coś innego?
thiruvenkadam

1
Jeśli masz str_list = [None, '', 0, "Hi", '', "Hello"], jest to znak źle zaprojektowanej aplikacji. Nie powinieneś mieć więcej niż jednego interfejsu (typu) i Brak na tej samej liście.
Tryt21,

3
Odzyskiwanie danych z bazy danych? lista argumentów funkcji podczas wykonywania testów automatycznych?
thiruvenkadam

3
Są to zwykle krotki.
Tryt21

7

W zależności od wielkości listy może być najbardziej wydajne, jeśli używasz list.remove () zamiast tworzyć nową listę:

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

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

Ma to tę zaletę, że nie tworzy nowej listy, ale ma tę wadę, że za każdym razem trzeba wyszukiwać od początku, chociaż w przeciwieństwie do korzystania z while '' in lpropozycji powyżej, wymaga ona wyszukiwania tylko raz dla każdego wystąpienia ''(z pewnością istnieje sposób, aby zachować najlepszą z obie metody, ale jest to bardziej skomplikowane).


1
Możesz edytować listę w miejscu, wykonując ary[:] = [e for e in ary if e]. Znacznie czystszy i nie używa wyjątków dla przepływu sterowania.
Krzysztof Karski

2
Cóż, to nie jest tak naprawdę „na miejscu” - jestem prawie pewien, że tworzy to nową listę i po prostu przypisuje ją do nazwy starego.
Andrew Jaffe,

Działa to bardzo słabo, ponieważ ogon danych jest tasowany w pamięci przy każdym usuwaniu. Lepiej usunąć wszystko za jednym razem.
wim

7

Pamiętaj, że jeśli chcesz zachować białe spacje w ciągu , możesz je przypadkowo usunąć za pomocą niektórych metod. Jeśli masz tę listę

[„witaj świecie”, „”, „”, „witaj”] czego możesz chcieć [„witaj świecie”, „witaj”]

najpierw przytnij listę, aby przekonwertować dowolny biały znak na pusty ciąg znaków:

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

następnie usuń pusty ciąg z listy

space_clean_list = [x for x in space_to_empty if x]

jeśli chcesz zachować białe spacje w ciągu, możesz je przypadkowo usunąć za pomocą niektórych metod. Podobało ci się to podejście?
AMC

Dzięki stary, zadziałało to dla mnie z małą zmianą. tj.space_clean_list = [x.strip() for x in y if x.strip()]
Muhammad Mehran Khan Attari

6

Użyj filter:

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

Wady używania filtra, jak wskazano, polegają na tym, że jest on wolniejszy niż alternatywy; jest również lambdazwykle kosztowne.

Lub możesz wybrać najprostszą i najbardziej iteracyjną ze wszystkich:

# 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

jest to najbardziej intuicyjna metoda i robi to w przyzwoitym czasie.


9
Witamy w SO. Nie zostałeś zignorowany. Nie zostałeś zaatakowany przez anonimowego zstępującego. Otrzymałeś informację zwrotną. Wzmocnienie: Twój proponowany pierwszy argument dla filtra jest gorszy niż ten, lambda x: len(x)który jest gorszy niż lambda x : xto, które jest najgorsze z 4 rozwiązań w wybranej odpowiedzi. Preferowane jest prawidłowe funkcjonowanie, ale niewystarczające. Najedź kursorem na przycisk głosowania: mówi „Ta odpowiedź nie jest przydatna”.
John Machin

5

Jak donosi Aziz Alto filter(None, lstr) , nie usuwa pustych łańcuchów ze spacją, ' 'ale jeśli jesteś pewien, że lstr zawiera tylko łańcuch, którego możesz użyćfilter(str.strip, lstr)

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

Porównaj czas na moim komputerze

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

Pozostaje najszybsze rozwiązanie do usuwania ''i opróżniania łańcuchów ze spacją .' '' '.join(lstr).split()

Jak podano w komentarzu, sytuacja wygląda inaczej, jeśli ciągi znaków zawierają spacje.

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

Możesz zobaczyć, że filter(str.strip, lstr)zachowaj ciągi ze spacjami, ale ' '.join(lstr).split()podzieli to ciągi.


1
Działa to tylko wtedy, gdy ciągi nie zawierają spacji. W przeciwnym razie podzielisz również te ciągi.
phillyslick

1
@BenPolinsky, jak informujesz, joinrozwiązanie podzieli ciągi znaków spacją, ale filtr nie. Dziękuję za komentarz Poprawiłem swoją odpowiedź.
Paolo Melchiorre

-1

Podsumuj najlepsze odpowiedzi:

1. Wyeliminuj opróżnienia BEZ usuwania:

Oznacza to, że zachowane są ciągi znaków spacji:

slist = list(filter(None, slist))

PRO:

  • najprostszy;
  • najszybszy (patrz testy porównawcze poniżej).

2. Aby wyeliminować opróżnienia po rozebraniu ...

2.a ... gdy ciągi NIE zawierają spacji między słowami:

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

PRO:

  • mały kod
  • szybki (ALE nie najszybszy z dużymi zestawami danych z powodu pamięci, w przeciwieństwie do wyników @ paolo-melchiorre)

2.b ... kiedy ciągi znaków zawierają spacje między wyrazami?

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

PRO:

  • najszybszy;
  • zrozumiałość kodu.

Testy porównawcze na maszynie 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)

s and s.strip()można uprościć do just s.strip().
AMC

s and s.strip()jest potrzebne, jeśli chcemy w pełni powielić filter(None, words)zaakceptowaną odpowiedź. Poprawiłem powyżej przykładowe funkcje x2 i upuściłem x2 złe.
ankostis

-2

Aby uzyskać listę z kombinacją spacji i pustych wartości, użyj prostego zrozumienia listy -

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

Widzisz więc, ta lista zawiera kombinację spacji i elementów zerowych. Korzystanie z fragmentu -

>>> d = [x for x in s if x.strip()]
>>> d
>>> d = ['I', 'am', 'a', 'great', 'person', '!!', 'Do', 'you', 'think', 'its', 'a', 'a', 'joke', '?', '?']
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.