Existe uma maneira simples de remover vários espaços em uma string?

390

Suponha que esta sequência:

The   fox jumped   over    the log.

Transformando em:

The fox jumped over the log.

Qual é a mais simples (1-2 linhas) para conseguir isso, sem dividir e entrar em listas?

TIMEX
fonte
22
Qual é a sua aversão às listas? Eles são parte integrante da linguagem e "" .join (list_of_words) é um dos idiomas principais para transformar uma lista de strings em uma única string delimitada por espaço.
9789 PaulMcG
3
@ Tom / @ Paul: Para strings simples, a junção (string) seria simples e agradável. Mas fica mais complexo se houver outro espaço em branco que NÃO se queira perturbar ... nesse caso, as soluções "while" ou regex seriam as melhores. Postei abaixo uma junção de string que seria "correta", com resultados de teste cronometrados para três maneiras de fazer isso.
Pythonlarry

Respostas:

529
>>> import re
>>> re.sub(' +', ' ', 'The     quick brown    fox')
'The quick brown fox'
Josh Lee
fonte
20
Esta solução lida apenas com caracteres de espaço único. Não substituiria uma guia ou outros caracteres de espaço em branco manipulados por \ s, como na solução do nsr81.
21411 Taylor Leese
2
Isso é verdade, string.splittambém lida com todos os tipos de espaços em branco.
Josh Lee
6
Prefiro este porque ele se concentra apenas no caractere de espaço e não afeta caracteres como '\ n's.
Hhsaffar 17/10/2014
2
Sim certo. Mas antes dessa faixa () deve ser feita. Ele removerá espaços de ambas as extremidades.
precisa
17
Você pode usar re.sub(' {2,}', ' ', 'The quick brown fox')para impedir substituições redundantes de espaço único por espaço único .
AneesAhmed777
541

foo é sua string:

" ".join(foo.split())

Esteja avisado, porém isso remove "todos os caracteres de espaço em branco (espaço, guia, nova linha, retorno, alimentação de formulário )" (graças a hhsaffar , consulte os comentários). Ou seja, "this is \t a test\n"vai acabar efetivamente como "this is a test".

Taylor Leese
fonte
19
“Sem a divisão e entrar em listas ...”
Gumbo
72
Eu ignorei "Sem dividir e entrar em listas ..." porque ainda acho que é a melhor resposta.
Taylor Leese
11
Isso remove os espaços finais. Se você quiser mantê-los fazer: texto [0: 1] + "" .join (texto [1: -1] .Split ()) + texto [-1]
user984003
6x mais rápido que a solução re.sub () também.
nerdfever.com 20/03
11
@ AstraUvarova-Saturn'sstar Eu perfilei isso.
nerdfever.com 22/04
85
import re
s = "The   fox jumped   over    the log."
re.sub("\s\s+" , " ", s)

ou

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

já que o espaço antes da vírgula é listado como uma irritação no PEP 8 , conforme mencionado pelo usuário Martin Thoma nos comentários.

Nasir
fonte
2
Eu tenderia a alterar esse regex para r"\s\s+"que ele não tente substituir espaços já únicos.
9119 Ben Blank
19
Se você queria esse comportamento, por que não apenas "\s{2,}"uma solução alternativa para não conhecer o comportamento regex moderadamente avançado?
22720 Chris Lutz
2
lembre-se de que sub () não altera a sequência de entrada s, mas retorna o novo valor.
gcb
11
@moose - É uma otimização de legibilidade do que uma de desempenho. \s+faria com que a linha lesse "substitua um ou mais espaços por um espaço", em vez de "substitua dois ou mais espaços por um espaço". O primeiro imediatamente me faz parar e pensar: "Por que substituir um espaço por um espaço? Isso é bobagem". Para mim, isso é um cheiro de código (muito pequeno). Na verdade, eu não esperaria que haja qualquer diferença de desempenho em tudo entre os dois, como ele vai ser copiar para uma nova seqüência de qualquer maneira, e tem que parar e teste independentemente de onde o espaço está sendo copiado a partir .
Ben Blank
8
Eu desaconselho, \s\s+porque isso não normaliza um caractere TAB de volta para um espaço normal. um SPACE + TAB é substituído dessa maneira.
vdboor
51

O uso de expressões regulares com "\ s" e a execução simples de string.split () também removerão outros espaços em branco - como novas linhas, retornos de carro, guias. A menos que isso seja desejado, para fazer apenas vários espaços , apresento esses exemplos.

Usei 11 parágrafos, 1000 palavras e 6665 bytes de Lorem Ipsum para obter testes de tempo realísticos e usei espaços extras de tamanho aleatório em todo:

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

O one-liner essencialmente faz uma faixa de qualquer espaço à esquerda / à direita e preserva um espaço à frente / à direita (mas apenas UM ;-).

# 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

NOTA: A " whileversão" fez uma cópia do original_string, como acredito que uma vez modificado na primeira execução, as execuções sucessivas seriam mais rápidas (mesmo que apenas um pouco). À medida que isso aumenta o tempo, adicionei essa cópia de cadeia às outras duas, para que os horários mostrassem a diferença apenas na lógica. Tenha em mente que o principal stmtem timeitcasos só será executada uma vez ; da maneira original que eu fiz isso, o whileloop funcionou no mesmo rótulo original_string, portanto, na segunda execução, não havia nada a fazer. A maneira como está configurada agora, chamando uma função, usando dois rótulos diferentes, isso não é um problema. Adicionei assertdeclarações a todos os trabalhadores para verificar se alteramos algo a cada iteração (para aqueles que podem ter dúvidas). Por exemplo, mude para isso e ele quebra:

# 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

Para a string trivial, parece que um loop while é o mais rápido, seguido pela divisão / junção de cordas Pythonic e pelo regex puxando a traseira.

Para strings não triviais , parece que há um pouco mais a considerar. 32 bits 2.7? É regex para o resgate! 2,7 64 bits? Um whileloop é melhor, por uma margem decente. 32 bits 3.2, vá com o "adequado" join. 64 bits 3.3, vá para um whileloop. Novamente.

No final, pode-se melhorar o desempenho se / onde / quando necessário , mas é sempre melhor lembrar o mantra :

  1. Faça funcionar
  2. Make It Right
  3. Make It Fast

IANAL, YMMV, Advertência Emptor!

pythonlarry
fonte
11
Eu teria preferido se você tivesse testado o simples, ' '.join(the_string.split())pois esse é o caso de uso usual, mas eu gostaria de agradecer pelo seu trabalho!
wedi
@wedi: Por outros comentários (como Gumbo ; user984003 , embora sua solução seja presuntiva e não funcione "em todos os casos"), esse tipo de solução não atende à solicitação do interlocutor. Pode-se usar .split ('') e um comp / gen, mas fica mais cabeludo para lidar com espaços de avanço / final.
Pythonlarry 26/10/14
@wedi: Por exemplo: ' '.join(p for p in s.split(' ') if p)<- ainda perdemos espaços de avanço / fuga, mas foram responsáveis ​​por vários espaços. Para mantê-los, deve fazer o mesmo parts = s.split(' '); (' ' if not parts[0] else '') + ' '.join(p for p in s.split(' ') if p) + (' ' if not parts[-1] else '')!
Pythonlarry 26/10/14
Obrigado @pythonlarry pelo mantra! e ame o teste detalhado! Estou curioso para saber se seus pensamentos ou opiniões mudaram sobre isso desde que se passaram 6 anos?
JayRizzo 15/05/19
Versão faltante que usa geradores #
Lee Lee
42

Eu tenho que concordar com o comentário de Paul McGuire. Para mim,

' '.join(the_string.split())

é muito preferível a sacar um regex.

Minhas medidas (Linux e Python 2.5) mostram que a divisão de junção e junção é quase cinco vezes mais rápida do que fazer o "re.sub (...)" e ainda três vezes mais rápida se você pré-compilar o regex uma vez e executar a operação várias vezes. E é por qualquer medida mais fácil de entender - muito mais pitônico.

Kevin Little
fonte
Isso remove os espaços finais. Se você quiser mantê-los fazer: texto [0: 1] + "" .join (texto [1: -1] .Split ()) + texto [-1]
user984003
4
um simples regexp é muito melhor para ler. nunca otimize o desempenho antes de precisar.
gcb
@gcb: Por que não? E se você estiver esperando um cenário de alto rendimento (por exemplo, devido à alta demanda)? Por que não implantar algo que você espera consumir menos recursos desde o início nesse cenário?
Hassan Baig
11
@HassanBaig Se você já possui o requisito de desempenho, não é realmente uma otimização prematura, certo? O que quero dizer é que, quando você ainda não precisa ficar obcecado com o desempenho, é sempre melhor buscar a legibilidade.
gcb
14

Semelhante às soluções anteriores, mas mais específico: substitua dois ou mais espaços por um:

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

Uma alma simples

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

Você também pode usar a técnica de divisão de string em um DataFrame do Pandas sem precisar usar .apply (..), que é útil se você precisar executar a operação rapidamente em um grande número de strings. Aqui está em uma linha:

df['message'] = (df['message'].str.split()).str.join(' ')
devinbost
fonte
6
import re
string = re.sub('[ \t\n]+', ' ', 'The     quick brown                \n\n             \t        fox')

Isso removerá todas as guias, novas linhas e vários espaços em branco com um único espaço em branco.

Rakesh Kumar
fonte
Mas se você tiver caracteres de espaço em branco (não imprimíveis) fora do seu intervalo, como '\ x00' a '\ x0020', o código não os removerá.
Muskovets
5

Eu tentei o seguinte método e até funciona com casos extremos como:

str1='          I   live    on    earth           '

' '.join(str1.split())

Mas se você preferir uma expressão regular, isso pode ser feito como:

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

Embora seja necessário fazer algum pré-processamento para remover o espaço final e final.

Ravi Tanwar
fonte
3

Isso também parece funcionar:

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

Onde a variável srepresenta sua sequência.

Anakimi
fonte
2

Em alguns casos, é desejável substituir ocorrências consecutivas de cada caractere de espaço em branco por uma única instância desse caractere. Você usaria uma expressão regular com referências anteriores para fazer isso.

(\s)\1{1,}corresponde a qualquer caractere de espaço em branco, seguido por uma ou mais ocorrências desse caractere. Agora, tudo o que você precisa fazer é especificar o primeiro grupo ( \1) como substituto da partida.

Agrupando isso em uma função:

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

Outra alternativa:

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

Uma linha de código para remover todos os espaços extras antes, depois e dentro de uma frase:

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

Explicação:

  1. Divida a sequência inteira em uma lista.
  2. Filtre os elementos vazios da lista.
  3. Junte os elementos restantes * com um único espaço

* Os elementos restantes devem ser palavras ou palavras com pontuações, etc. Não testei isso extensivamente, mas esse deve ser um bom ponto de partida. Muito bem sucedida!

gabchan
fonte
2

Solução para desenvolvedores de Python:

import re

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

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

Chadee Fouad
fonte
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())
jw51
fonte
1

O mais rápido que você pode obter para seqüências de caracteres geradas pelo usuário é:

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

O curto-circuito o torna um pouco mais rápido que a resposta abrangente de pythonlarry . Faça isso se estiver buscando eficiência e procurando estritamente eliminar espaços em branco extras da variedade de espaço único .

Hassan Baig
fonte
1

Bastante surpreendente - ninguém postou uma função simples que será muito mais rápida do que TODAS as outras soluções postadas. Aqui vai:

def compactSpaces(s):
    os = ""
    for c in s:
        if c != " " or os[-1] != " ":
            os += c 
    return os
rafal chlopek
fonte
0
string = 'This is a             string full of spaces          and taps'
string = string.split(' ')
while '' in string:
    string.remove('')
string = ' '.join(string)
print(string)

Resultados :

Esta é uma cadeia cheia de espaços e torneiras

Hassan Abdul-Kareem
fonte
0

Para remover o espaço em branco, considerando os espaços à esquerda, à direita e extra entre as palavras, use:

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

O primeiro orlida com o espaço em branco à esquerda, o segundo orlida com o início do espaço em branco à esquerda e o último lida com o espaço em branco à direita.

Para comprovação de uso, este link fornecerá um teste.

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

Isso deve ser usado com a função re.split .

CameronE
fonte
0

Eu tenho o meu método simples que eu usei na faculdade.

line = "I     have            a       nice    day."

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

Isso substituirá cada espaço duplo por um único espaço e fará isso 1000 vezes. Isso significa que você pode ter 2000 espaços extras e ainda funcionará. :)

Peter Mortensen
fonte
Isso é (praticamente) idêntico à resposta de Anakimi (publicada mais de dois anos antes).
Peter Mortensen
0

Eu tenho um método simples sem dividir:

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

print(a)
Balduin Scheffbuch
fonte
11
Como isso difere da resposta de Anakimi (publicada mais de três anos antes)? Não é apenas uma versão mais complicada?
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='') 

Resultado:

Remover todo o espaço: Você pode selecionar abaixo os acabamentos para remover o espaço em branco !! BRAliakbar Remover espaço à esquerda: Você pode selecionar os acabamentos abaixo para remover o espaço em branco !! BR Aliakbar
Remover espaços finais: Você pode selecionar os acabamentos abaixo para remover os espaços em branco !! BR Aliakbar Remover espaços à esquerda e à direita: Você pode selecionar os acabamentos abaixo para remover os espaços em branco !! BR Aliakbar Remova mais de um espaço: Você pode selecionar os acabamentos abaixo para remover o espaço em branco !! BR Aliakbar

Aliakbar Hosseinzadeh
fonte
-1

Ainda não li muito sobre outros exemplos, mas acabei de criar esse método para consolidar vários caracteres de espaço consecutivos.

Ele não usa nenhuma biblioteca e, embora seja relativamente longo em termos de tamanho do script, não é uma implementação complexa:

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)))
Scott Anderson
fonte