Czy istnieje prosty sposób na usunięcie wielu spacji w ciągu?


390

Załóżmy, że ten ciąg:

The   fox jumped   over    the log.

Zmieniając się w:

The fox jumped over the log.

Co jest najprostsze (1-2 wiersze), aby to osiągnąć, bez dzielenia się i przechodzenia na listy?


22
Jaka jest twoja awersja do list? Są one integralną częścią języka, a „” .join (lista_słów) jest jednym z podstawowych idiomów służących do tworzenia listy ciągów w pojedynczy ciąg rozdzielany spacjami.
PaulMcG

3
@ Tom / @ Paul: W przypadku prostych ciągów łączenie (ciąg) byłoby proste i słodkie. Ale staje się bardziej skomplikowane, jeśli istnieją inne białe znaki, których NIE chcemy przeszkadzać ... w takim przypadku najlepsze byłyby rozwiązania „while” lub regex. Umieściłem poniżej łączenie ciągów, które byłoby „poprawne”, z wynikami testów czasowych dla trzech sposobów wykonania tego.
pythonlarry

Odpowiedzi:


529
>>> import re
>>> re.sub(' +', ' ', 'The     quick brown    fox')
'The quick brown fox'

20
To rozwiązanie obsługuje tylko pojedyncze znaki spacji. Nie zastąpiłby tabulacji ani innych białych znaków obsługiwanych przez \ s jak w rozwiązaniu nsr81.
Taylor Leese

2
To prawda, string.splitobsługuje także wszelkiego rodzaju białe znaki.
Josh Lee

6
Wolę ten, ponieważ skupia się tylko na znaku spacji i nie wpływa na znaki takie jak '\ n's.
hhsaffar

2
Tak, racja. Ale przed tym należy zrobić strip (). Usunie spacje z obu końców.
Hardik Patel

17
Możesz użyć, re.sub(' {2,}', ' ', 'The quick brown fox')aby zapobiec zbędnemu zastępowaniu pojedynczej spacji pojedynczą spacją .
AneesAhmed777

541

foo jest twój ciąg:

" ".join(foo.split())

Ostrzegamy jednak, że usuwa to „wszystkie białe znaki (spacja, tabulator, nowa linia, powrót, formatowanie)” (dzięki hhsaffar , patrz komentarze). Tj. "this is \t a test\n"Skutecznie skończy jako "this is a test".


19
„Bez dzielenia się i przechodzenia na listy ...”
Gumbo,

72
Zignorowałem „Bez dzielenia i przechodzenia na listy ...”, ponieważ nadal uważam, że to najlepsza odpowiedź.
Taylor Leese

1
To usuwa końcowe spacje. Jeśli chcesz je zachować, wykonaj: text [0: 1] + "" .join (text [1: -1] .split ()) + text [-1]
user984003

6 razy szybsze niż rozwiązanie re.sub ().
nerdfever.com

1
@ AstraUvarova-Saturn'sstar Wyprofilowałem go.
nerdfever.com

85
import re
s = "The   fox jumped   over    the log."
re.sub("\s\s+" , " ", s)

lub

re.sub("\s\s+", " ", s)

ponieważ przestrzeń przed przecinkiem jest wymieniona jako wkurzony w PEP 8 , o czym wspomniał użytkownik Martin Thoma w komentarzach.


2
Chciałbym zmienić to wyrażenie regularne na, r"\s\s+"aby nie próbowało zastępować już pojedynczych spacji.
Ben Blank,

19
Jeśli chciałeś tego zachowania, dlaczego nie "\s{2,}"zamiast obejścia problemu polegającego na tym, że nie znasz średnio zaawansowanych wyrażeń regularnych?
Chris Lutz

2
pamiętaj, że sub () nie zmienia ciągu wejściowego s, ale zwraca nową wartość.
gcb

1
@moose - To optymalizacja czytelności niż wydajność. \s+spowodowałoby, że wiersz brzmiałby „zamień jedną lub więcej spacji spacją”, zamiast „zamień dwie lub więcej spacji spacją”. Ten pierwszy natychmiast każe mi się zatrzymać i pomyśleć „Po co zastępować jedną spację jedną spacją? To głupie”. Dla mnie jest to (bardzo niewielki) zapach kodu. I faktycznie nie można oczekiwać, aby istniała jakakolwiek różnica wydajności w ogóle między nimi, jak to będzie skopiowanie do nowego łańcucha tak, i musi się zatrzymać i test niezależnie od tego, gdzie przestrzeń jest kopiowany z .
Ben Blank

8
Odradzam, \s\s+ponieważ nie spowoduje to normalizacji znaku TAB na normalnym polu. SPACE + TAB zostaje zastąpiony w ten sposób.
vdboor

51

Używanie wyrażeń regularnych z „\ s” i wykonywanie prostych string.split () spowoduje również usunięcie innych białych znaków - takich jak znaki nowej linii, znaki powrotu karetki, tabulatory. Jeśli nie jest to pożądane, aby zrobić tylko wiele spacji , przedstawiam te przykłady.

Użyłem 11 akapitów, 1000 słów, 6665 bajtów Lorem Ipsum, aby uzyskać realistyczne testy czasu i użyłem dodatkowych spacji o losowej długości w całym:

original_string = ''.join(word + (' ' * random.randint(1, 10)) for word in lorem_ipsum.split(' '))

One-liner zasadniczo wykona pasek dowolnych spacji wiodących / spływowych i zachowa spację wiodącą / spływową (ale tylko JEDEN ;-).

# setup = '''

import re

def while_replace(string):
    while '  ' in string:
        string = string.replace('  ', ' ')

    return string

def re_replace(string):
    return re.sub(r' {2,}' , ' ', string)

def proper_join(string):
    split_string = string.split(' ')

    # To account for leading/trailing spaces that would simply be removed
    beg = ' ' if not split_string[ 0] else ''
    end = ' ' if not split_string[-1] else ''

    # versus simply ' '.join(item for item in string.split(' ') if item)
    return beg + ' '.join(item for item in split_string if item) + end

original_string = """Lorem    ipsum        ... no, really, it kept going...          malesuada enim feugiat.         Integer imperdiet    erat."""

assert while_replace(original_string) == re_replace(original_string) == proper_join(original_string)

#'''

# while_replace_test
new_string = original_string[:]

new_string = while_replace(new_string)

assert new_string != original_string

# re_replace_test
new_string = original_string[:]

new_string = re_replace(new_string)

assert new_string != original_string

# proper_join_test
new_string = original_string[:]

new_string = proper_join(new_string)

assert new_string != original_string

UWAGA: whileWersja” wykonała kopię original_string, jak sądzę, że raz zmodyfikowana przy pierwszym uruchomieniu, kolejne uruchomienia byłyby szybsze (choćby odrobinę). Ponieważ to dodaje czasu, dodałem tę kopię ciągu do pozostałych dwóch, aby czasy pokazały różnicę tylko w logice. Należy pamiętać, że głównym stmtna timeitinstancji zostanie wykonany tylko raz ; oryginalny sposób, w jaki to zrobiłem, whilepętla działała na tej samej etykiecie original_string, więc przy drugim uruchomieniu nie byłoby nic do zrobienia. Sposób, w jaki jest teraz skonfigurowany, wywoływanie funkcji, używanie dwóch różnych etykiet, nie stanowi problemu. Dodałem assertoświadczenia do wszystkich pracowników, aby sprawdzić, czy zmieniamy coś w każdej iteracji (dla tych, którzy mogą być wątpliwi). Np. Zmień to i psuje się:

# while_replace_test
new_string = original_string[:]

new_string = while_replace(new_string)

assert new_string != original_string # will break the 2nd iteration

while '  ' in original_string:
    original_string = original_string.replace('  ', ' ')

Tests run on a laptop with an i5 processor running Windows 7 (64-bit).

timeit.Timer(stmt = test, setup = setup).repeat(7, 1000)

test_string = 'The   fox jumped   over\n\t    the log.' # trivial

Python 2.7.3, 32-bit, Windows
                test |      minum |    maximum |    average |     median
---------------------+------------+------------+------------+-----------
  while_replace_test |   0.001066 |   0.001260 |   0.001128 |   0.001092
     re_replace_test |   0.003074 |   0.003941 |   0.003357 |   0.003349
    proper_join_test |   0.002783 |   0.004829 |   0.003554 |   0.003035

Python 2.7.3, 64-bit, Windows
                test |      minum |    maximum |    average |     median
---------------------+------------+------------+------------+-----------
  while_replace_test |   0.001025 |   0.001079 |   0.001052 |   0.001051
     re_replace_test |   0.003213 |   0.004512 |   0.003656 |   0.003504
    proper_join_test |   0.002760 |   0.006361 |   0.004626 |   0.004600

Python 3.2.3, 32-bit, Windows
                test |      minum |    maximum |    average |     median
---------------------+------------+------------+------------+-----------
  while_replace_test |   0.001350 |   0.002302 |   0.001639 |   0.001357
     re_replace_test |   0.006797 |   0.008107 |   0.007319 |   0.007440
    proper_join_test |   0.002863 |   0.003356 |   0.003026 |   0.002975

Python 3.3.3, 64-bit, Windows
                test |      minum |    maximum |    average |     median
---------------------+------------+------------+------------+-----------
  while_replace_test |   0.001444 |   0.001490 |   0.001460 |   0.001459
     re_replace_test |   0.011771 |   0.012598 |   0.012082 |   0.011910
    proper_join_test |   0.003741 |   0.005933 |   0.004341 |   0.004009

test_string = lorem_ipsum
# Thanks to http://www.lipsum.com/
# "Generated 11 paragraphs, 1000 words, 6665 bytes of Lorem Ipsum"

Python 2.7.3, 32-bit
                test |      minum |    maximum |    average |     median
---------------------+------------+------------+------------+-----------
  while_replace_test |   0.342602 |   0.387803 |   0.359319 |   0.356284
     re_replace_test |   0.337571 |   0.359821 |   0.348876 |   0.348006
    proper_join_test |   0.381654 |   0.395349 |   0.388304 |   0.388193    

Python 2.7.3, 64-bit
                test |      minum |    maximum |    average |     median
---------------------+------------+------------+------------+-----------
  while_replace_test |   0.227471 |   0.268340 |   0.240884 |   0.236776
     re_replace_test |   0.301516 |   0.325730 |   0.308626 |   0.307852
    proper_join_test |   0.358766 |   0.383736 |   0.370958 |   0.371866    

Python 3.2.3, 32-bit
                test |      minum |    maximum |    average |     median
---------------------+------------+------------+------------+-----------
  while_replace_test |   0.438480 |   0.463380 |   0.447953 |   0.446646
     re_replace_test |   0.463729 |   0.490947 |   0.472496 |   0.468778
    proper_join_test |   0.397022 |   0.427817 |   0.406612 |   0.402053    

Python 3.3.3, 64-bit
                test |      minum |    maximum |    average |     median
---------------------+------------+------------+------------+-----------
  while_replace_test |   0.284495 |   0.294025 |   0.288735 |   0.289153
     re_replace_test |   0.501351 |   0.525673 |   0.511347 |   0.508467
    proper_join_test |   0.422011 |   0.448736 |   0.436196 |   0.440318

W przypadku trywialnego ciągu wydaje się, że pętla while jest najszybsza, po niej następuje Pythonic split-split / join i regex ciągnący w górę.

W przypadku nietrywialnych ciągów wydaje się, że jest jeszcze coś do rozważenia. 32-bitowy 2.7? To wyrażenie regularne na ratunek! 2.7 64-bitowy? whilePętla jest najlepszy, o przyzwoitej marży. 32-bit 3.2, wybierz „właściwe” join. 64-bit 3.3, idź na whilepętlę. Jeszcze raz.

Ostatecznie można poprawić wydajność, jeśli / gdzie / kiedy jest to potrzebne , ale zawsze najlepiej jest pamiętać mantrę :

  1. Niech to zadziała
  2. Zrób to dobrze
  3. Zrób to szybko

IANAL, YMMV, Caveat Emptor!


1
Wolałbym, gdybyś przetestował prosty, ' '.join(the_string.split())ponieważ jest to zwykły przypadek użycia, ale chciałbym podziękować za twoją pracę!
wedi

@wedi: Zgodnie z innymi komentarzami (np. z Gumbo ; user984003 , chociaż jego / jego rozwiązanie jest przypuszczalne i nie będzie działać „we wszystkich przypadkach”), tego rodzaju rozwiązanie nie spełnia prośby pytającego. Można użyć .split ('') i komp / gen, ale staje się bardziej włochaty, aby poradzić sobie ze spacjami ołowianymi / końcowymi.
pythonlarry

@wedi: Np .: ' '.join(p for p in s.split(' ') if p)<- nadal stracił ołów / końcowe spacje, ale uwzględniał wiele spacji. Aby je zatrzymać, musisz zrobić jak parts = s.split(' '); (' ' if not parts[0] else '') + ' '.join(p for p in s.split(' ') if p) + (' ' if not parts[-1] else '')!
pythonlarry

Dzięki @pythonlarry za mantrę! i uwielbiam szczegółowy test! Jestem ciekawy, czy twoje przemyślenia lub poglądy na ten temat zmieniły się od 6 lat?
JayRizzo

Brak wersji używającej generatorów
Lee,

42

Muszę się zgodzić z komentarzem Paula McGuire'a. Dla mnie,

' '.join(the_string.split())

jest zdecydowanie lepsza niż wyrzucenie wyrażenia regularnego.

Moje pomiary (Linux i Python 2.5) pokazują, że łączenie z podziałem, a następnie łączenie jest prawie pięciokrotnie szybsze niż wykonanie polecenia „re.sub (...)” i wciąż trzykrotnie szybsze, jeśli raz skompilujesz wyrażenie regularne i wykonasz operację wiele razy. I pod każdym względem jest łatwiejszy do zrozumienia - znacznie bardziej Pythonic.


To usuwa końcowe spacje. Jeśli chcesz je zachować, wykonaj: text [0: 1] + "" .join (text [1: -1] .split ()) + text [-1]
user984003

4
prosty regexp jest o wiele lepszy do przeczytania. nigdy nie należy optymalizować wydajności, zanim będzie to konieczne.
gcb

@ gcb: Dlaczego nie? Co jeśli oczekujesz scenariusza o wysokiej przepustowości (np. Z powodu dużego popytu)? Dlaczego nie wdrożyć w tym scenariuszu czegoś, co od samego początku będzie mniej obciążające zasoby?
Hassan Baig

1
@HassanBaig, jeśli masz już wymagania dotyczące wydajności, to nie jest tak naprawdę przedwczesna optymalizacja, prawda? Chodzi mi o to, że kiedy nie musisz mieć obsesji na punkcie wydajności, zawsze lepiej jest dążyć do czytelności.
gcb

14

Podobne do poprzednich rozwiązań, ale bardziej szczegółowe: zamień dwie lub więcej spacji na jedną:

>>> import re
>>> s = "The   fox jumped   over    the log."
>>> re.sub('\s{2,}', ' ', s)
'The fox jumped over the log.'

11

Prosta dusza

>>> import re
>>> s="The   fox jumped   over    the log."
>>> print re.sub('\s+',' ', s)
The fox jumped over the log.

6

Możesz także użyć techniki dzielenia ciągów w ramce Pandas DataFrame bez konieczności używania .apply (..), co jest przydatne, jeśli chcesz szybko wykonać operację na dużej liczbie ciągów. Oto jedna linia:

df['message'] = (df['message'].str.split()).str.join(' ')

6
import re
string = re.sub('[ \t\n]+', ' ', 'The     quick brown                \n\n             \t        fox')

Spowoduje to usunięcie wszystkich zakładek, nowych linii i wielu białych spacji z pojedynczą białą spacją.


Ale jeśli masz spacje (niedrukowalne) znaki spoza twojego zakresu, takie jak „\ x00” do „\ x0020”, kod ich nie usunie.
Muskovets

5

Wypróbowałem następującą metodę, która działa nawet w ekstremalnych przypadkach, takich jak:

str1='          I   live    on    earth           '

' '.join(str1.split())

Ale jeśli wolisz wyrażenie regularne, możesz to zrobić jako:

re.sub('\s+', ' ', str1)

Chociaż konieczne jest pewne wstępne przetwarzanie w celu usunięcia spacji końcowej i końcowej.


3

To także wydaje się działać:

while "  " in s:
    s = s.replace("  ", " ")

Gdzie zmienna sreprezentuje twój ciąg.


2

W niektórych przypadkach pożądane jest zastąpienie kolejnych wystąpień każdego znaku spacji jednym wystąpieniem tego znaku. W tym celu użyłbyś wyrażenia regularnego z odnośnikami zwrotnymi.

(\s)\1{1,}dopasowuje dowolny znak spacji, po którym następuje jedno lub więcej wystąpień tego znaku. Teraz wszystko, co musisz zrobić, to określić pierwszą grupę ( \1) jako zamiennik dopasowania.

Zawijanie tego w funkcję:

import re

def normalize_whitespace(string):
    return re.sub(r'(\s)\1{1,}', r'\1', string)
>>> normalize_whitespace('The   fox jumped   over    the log.')
'The fox jumped over the log.'
>>> normalize_whitespace('First    line\t\t\t \n\n\nSecond    line')
'First line\t \nSecond line'

2

Inna alternatywa:

>>> import re
>>> str = 'this is a            string with    multiple spaces and    tabs'
>>> str = re.sub('[ \t]+' , ' ', str)
>>> print str
this is a string with multiple spaces and tabs

2

Jeden wiersz kodu, aby usunąć wszystkie dodatkowe spacje przed, po i wewnątrz zdania:

sentence = "  The   fox jumped   over    the log.  "
sentence = ' '.join(filter(None,sentence.split(' ')))

Wyjaśnienie:

  1. Podziel cały ciąg na listę.
  2. Filtruj puste elementy z listy.
  3. Dołącz do pozostałych elementów * jednym spacją

* Pozostałe elementy powinny być słowami lub słowami ze znakami interpunkcyjnymi itp. Nie testowałem tego obszernie, ale powinien to być dobry punkt wyjścia. Wszystkiego najlepszego!


2

Rozwiązanie dla programistów Python:

import re

text1 = 'Python      Exercises    Are   Challenging Exercises'
print("Original string: ", text1)
print("Without extra spaces: ", re.sub(' +', ' ', text1))

Wynik:
Original string: Python Exercises Are Challenging Exercises Without extra spaces: Python Exercises Are Challenging Exercises


1
def unPretty(S):
   # Given a dictionary, JSON, list, float, int, or even a string...
   # return a string stripped of CR, LF replaced by space, with multiple spaces reduced to one.
   return ' '.join(str(S).replace('\n', ' ').replace('\r', '').split())

1

Najszybsze, jakie możesz uzyskać dla ciągów generowanych przez użytkownika, to:

if '  ' in text:
    while '  ' in text:
        text = text.replace('  ', ' ')

Krótkie spięcie sprawia, że ​​jest to nieco szybsze niż wyczerpująca odpowiedź pythonlarry . Skorzystaj z tego, jeśli zależy Ci na wydajności i chcesz oddzielić dodatkowe białe spacje odmiany pojedynczej przestrzeni .


1

Całkiem zaskakujące - nikt nie opublikował prostej funkcji, która będzie znacznie szybsza niż WSZYSTKIE inne publikowane rozwiązania. Oto jest:

def compactSpaces(s):
    os = ""
    for c in s:
        if c != " " or os[-1] != " ":
            os += c 
    return os


0
string = 'This is a             string full of spaces          and taps'
string = string.split(' ')
while '' in string:
    string.remove('')
string = ' '.join(string)
print(string)

Wyniki :

To ciąg pełen spacji i kranów


0

Aby usunąć spację, uwzględniając początkowe, końcowe i dodatkowe białe spacje między wyrazami, użyj:

(?<=\s) +|^ +(?=\s)| (?= +[\n\0])

Pierwszy ordotyczy wiodącej białej przestrzeni, drugior dotyczy początku sznurka wiodącej białej przestrzeni, a ostatni dotyczy końcowej białej spacji.

W celu potwierdzenia użycia ten link zapewni Ci test.

https://regex101.com/r/meBYli/4

Należy tego używać z funkcją re.split .


0

Mam swoją prostą metodę, której użyłem na studiach.

line = "I     have            a       nice    day."

end = 1000
while end != 0:
    line.replace("  ", " ")
    end -= 1

To zastąpi każde podwójne miejsce pojedynczym miejscem i zrobi to 1000 razy. Oznacza to, że możesz mieć 2000 dodatkowych miejsc i nadal będzie działać. :)


Jest to (praktycznie) identyczne z odpowiedzią Anakimi (opublikowaną ponad dwa lata wcześniej).
Peter Mortensen

0

Mam prostą metodę bez podziału:

a = "Lorem   Ipsum Darum     Diesrum!"
while True:
    count = a.find("  ")
    if count > 0:
        a = a.replace("  ", " ")
        count = a.find("  ")
        continue
    else:
        break

print(a)

1
Czym różni się to od odpowiedzi Anakimi (opublikowanej ponad trzy lata wcześniej)? Czy to nie jest tylko bardziej skomplikowana wersja?
Peter Mortensen

0
import re

Text = " You can select below trims for removing white space!!   BR Aliakbar     "
  # trims all white spaces
print('Remove all space:',re.sub(r"\s+", "", Text), sep='') 
# trims left space
print('Remove leading space:', re.sub(r"^\s+", "", Text), sep='') 
# trims right space
print('Remove trailing spaces:', re.sub(r"\s+$", "", Text), sep='')  
# trims both
print('Remove leading and trailing spaces:', re.sub(r"^\s+|\s+$", "", Text), sep='')
# replace more than one white space in the string with one white space
print('Remove more than one space:',re.sub(' +', ' ',Text), sep='') 

Wynik:

Usuń wszystkie spacje: Możesz wybrać następujące wykończenia do usunięcia białej przestrzeni! BRAliakbar Usuń spację wiodącą: Możesz wybrać poniżej wykończenia do usunięcia białych spacji !! BR Aliakbar
Usuń końcowe spacje: Możesz wybrać poniższe wykończenia w celu usunięcia białych spacji !! BR Aliakbar Usuń wiodące i końcowe spacje: Możesz wybrać poniżej wykończenia w celu usunięcia białych spacji !! BR Aliakbar Usuń więcej niż jedną spację: Możesz wybrać poniższe wykończenia w celu usunięcia spacji !! BR Aliakbar


-1

Nie czytałem dużo w innych przykładach, ale właśnie stworzyłem tę metodę do konsolidacji wielu kolejnych znaków spacji.

Nie korzysta z żadnych bibliotek i chociaż jest stosunkowo długi pod względem długości skryptu, nie jest złożoną implementacją:

def spaceMatcher(command):
    """
    Function defined to consolidate multiple whitespace characters in
    strings to a single space
    """
    # Initiate index to flag if more than one consecutive character
    iteration
    space_match = 0
    space_char = ""
    for char in command:
      if char == " ":
          space_match += 1
          space_char += " "
      elif (char != " ") & (space_match > 1):
          new_command = command.replace(space_char, " ")
          space_match = 0
          space_char = ""
      elif char != " ":
          space_match = 0
          space_char = ""
   return new_command

command = None
command = str(input("Please enter a command ->"))
print(spaceMatcher(command))
print(list(spaceMatcher(command)))
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.