¿Hay una manera simple de eliminar múltiples espacios en una cadena?

390

Supongamos que esta cadena:

The   fox jumped   over    the log.

Transformándose en:

The fox jumped over the log.

¿Cuál es el más simple (1-2 líneas) para lograr esto, sin dividir y entrar en listas?

TIMEX
fuente
22
¿Cuál es tu aversión a las listas? Son una parte integral del lenguaje, y "" .join (list_of_words) es uno de los modismos principales para hacer una lista de cadenas en una sola cadena delimitada por espacios.
PaulMcG
3
@ Tom / @ Paul: Para cadenas simples, (string) join sería simple y dulce. Pero se vuelve más complejo si hay otro espacio en blanco que uno NO quiere molestar ... en cuyo caso las soluciones "while" o regex serían las mejores. He publicado debajo una unión de cadenas que sería "correcta", con resultados de prueba cronometrados para tres formas de hacerlo.
pythonlarry

Respuestas:

529
>>> import re
>>> re.sub(' +', ' ', 'The     quick brown    fox')
'The quick brown fox'
Josh Lee
fuente
20
Esta solución solo maneja caracteres de espacio único. No reemplazaría una pestaña u otros caracteres de espacio en blanco manejados por \ s como en la solución de nsr81.
Taylor Leese
2
Eso es cierto, string.splittambién maneja todo tipo de espacios en blanco.
Josh Lee
66
Prefiero este porque solo se enfoca en el carácter espacial y no afecta a caracteres como '\ n's.
hhsaffar
2
Si claro. Pero antes de esa tira () debe hacerse. Eliminará espacios de ambos extremos.
Hardik Patel
17
Puede usar re.sub(' {2,}', ' ', 'The quick brown fox')para evitar reemplazos redundantes de espacio único con espacio único .
AneesAhmed777
541

foo es tu cadena:

" ".join(foo.split())

Tenga en cuenta que esto elimina "todos los caracteres de espacio en blanco (espacio, tabulación, nueva línea, retorno, avance de página)" (gracias a hhsaffar , ver comentarios). Es decir, "this is \t a test\n"efectivamente terminará como "this is a test".

Taylor Leese
fuente
19
"Sin dividir y entrar en listas ..."
Gumbo
72
Ignoré "Sin dividirme y entrar en listas ..." porque todavía creo que es la mejor respuesta.
Taylor Leese
1
Esto elimina los espacios finales. Si desea conservarlos, haga: text [0: 1] + "" .join (text [1: -1] .split ()) + text [-1]
user984003
6 veces más rápido que la solución re.sub (), también.
nerdfever.com
1
@ AstraUvarova-Saturn'sstar lo perfilé.
nerdfever.com
85
import re
s = "The   fox jumped   over    the log."
re.sub("\s\s+" , " ", s)

o

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

dado que el espacio antes de la coma se enumera como un motivo favorito en PEP 8 , como lo menciona el usuario Martin Thoma en los comentarios.

Nasir
fuente
2
Tiende a cambiar esa expresión regular para r"\s\s+"que no intente reemplazar espacios ya individuales.
Ben Blank
19
Si quería ese comportamiento, ¿por qué no solo en "\s{2,}"lugar de una solución alternativa para no conocer el comportamiento de expresiones regulares moderadamente avanzado?
Chris Lutz
2
recuerde que sub () no cambia la cadena de entrada s, pero devuelve el nuevo valor.
gcb
1
@moose: es una optimización de legibilidad que una de rendimiento. \s+causaría que la línea lea "reemplazar uno o más espacios con un espacio", en lugar de "reemplazar dos o más espacios con un espacio". Lo primero inmediatamente me hace parar y pensar "¿Por qué reemplazar un espacio con un espacio? Eso es una tontería". Para mí, ese es un olor a código (muy pequeño). En realidad no se puede esperar que haya ninguna diferencia de rendimiento en absoluto entre los dos, ya que va a ser copiado en una nueva cadena de todas formas, y tiene que parar y probar independientemente del lugar donde el espacio está siendo copiado a partir .
Ben Blank
8
Aconsejaría en contra \s\s+porque esto no normalizará un personaje TAB a un espacio normal. un SPACE + TAB se reemplaza de esta manera.
vdboor
51

El uso de expresiones regulares con "\ s" y hacer una cadena simple. Split () también eliminará otros espacios en blanco, como líneas nuevas, retornos de carro, pestañas. A menos que esto se desee, para hacer solo múltiples espacios , presento estos ejemplos.

Utilicé 11 párrafos, 1000 palabras, 6665 bytes de Lorem Ipsum para obtener pruebas de tiempo realistas y utilicé espacios adicionales de longitud aleatoria en todo:

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

El one-liner esencialmente hará una tira de los espacios iniciales / finales, y conserva un espacio inicial / final (pero solo UNO ;-).

# 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: La " whileversión" hizo una copia de la original_string, como creo que una vez modificada en la primera ejecución, las ejecuciones sucesivas serían más rápidas (aunque solo sea un poco). Como esto agrega tiempo, agregué esta copia de cadena a las otras dos para que los tiempos mostraran la diferencia solo en la lógica. Tenga en cuenta que las instancias principales stmten timeitsolo se ejecutarán una vez ; De la forma original en que hice esto, el whilebucle funcionó en la misma etiqueta original_string, por lo tanto, en la segunda ejecución, no habría nada que hacer. La forma en que está configurada ahora, llamando a una función, usando dos etiquetas diferentes, eso no es un problema. He agregado assertdeclaraciones a todos los trabajadores para verificar que cambiemos algo cada iteración (para aquellos que puedan tener dudas). Por ejemplo, cambia a esto y se rompe:

# 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 la cadena trivial, parecería que un bucle while es el más rápido, seguido de la división / unión de cadenas Pythonic, y la expresión regular tirando hacia arriba.

Para cadenas no triviales , parece que hay un poco más para considerar. 32 bits 2.7? Es regex al rescate! 2.7 de 64 bits? Un whilebucle es mejor, por un margen decente. 32-bit 3.2, vaya con el "correcto" join. 64 bits 3.3, vaya por un whilebucle. De nuevo.

Al final, uno puede mejorar el rendimiento si / donde / cuando sea necesario , pero siempre es mejor recordar el mantra :

  1. Hazlo funcionar
  2. Hacer lo correcto
  3. Hazlo rápido

IANAL, YMMV, Caveat Emptor!

pitonaria
fuente
1
Hubiera preferido si hubiera probado lo simple, ' '.join(the_string.split())ya que este es el caso de uso habitual, pero me gustaría agradecerle por su trabajo.
Mié
@wedi: Según otros comentarios (como de Gumbo ; user984003 , aunque su solución es presuntiva y no funcionará "en todos los casos"), este tipo de solución no cumple con la solicitud del interlocutor. Uno puede usar .split (''), y un comp / gen, pero se vuelve más complicado tratar con espacios iniciales / finales.
pythonlarry
@wedi: Por ejemplo: ' '.join(p for p in s.split(' ') if p)<- todavía perdió espacios iniciales / finales, pero representó múltiples espacios. Para mantenerlos, debe hacer como parts = s.split(' '); (' ' if not parts[0] else '') + ' '.join(p for p in s.split(' ') if p) + (' ' if not parts[-1] else '')!
pythonlarry
Gracias @pythonlarry por el mantra! y me encanta la prueba detallada! Tengo curiosidad por saber si sus pensamientos u opiniones han cambiado sobre esto desde que han pasado 6 años.
JayRizzo
Versión faltante que usa generadores
Lee
42

Tengo que estar de acuerdo con el comentario de Paul McGuire. A mi,

' '.join(the_string.split())

es muy preferible a sacar una expresión regular.

Mis mediciones (Linux y Python 2.5) muestran que dividir y unir es casi cinco veces más rápido que hacer "re.sub (...)", y aún tres veces más rápido si precompila la expresión regular una vez y realiza la operación varias veces. Y es, en cualquier medida, más fácil de entender, mucho más Pythonic.

Kevin Little
fuente
Esto elimina los espacios finales. Si desea conservarlos, haga: text [0: 1] + "" .join (text [1: -1] .split ()) + text [-1]
user984003
44
Una expresión regular simple es mucho mejor para leer. nunca optimice el rendimiento antes de que lo necesite.
gcb
@gcb: ¿Por qué no? ¿Qué sucede si espera un escenario de alto rendimiento (por ejemplo, debido a la alta demanda)? ¿Por qué no implementar algo que espera que requiera menos recursos desde el principio en ese escenario?
Hassan Baig
1
@HassanBaig si ya tienes el requisito de rendimiento, entonces no es realmente una optimización prematura, ¿verdad? Mi punto es cuando aún no necesita obsesionarse con el rendimiento, siempre es mejor apuntar a la legibilidad.
gcb
14

Similar a las soluciones anteriores, pero más específico: reemplace dos o más espacios con uno:

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

Una simple almación

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

También puede usar la técnica de división de cadenas en un Pandas DataFrame sin necesidad de usar .apply (..), que es útil si necesita realizar la operación rápidamente en una gran cantidad de cadenas. Aquí está en una línea:

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

Esto eliminará todas las pestañas, nuevas líneas y múltiples espacios en blanco con un solo espacio en blanco.

Rakesh Kumar
fuente
Pero si tiene caracteres de espacio en blanco (no imprimibles) que no están en su rango, como '\ x00' a '\ x0020', el código no los eliminará.
Muskovets
5

He intentado el siguiente método e incluso funciona con casos extremos como:

str1='          I   live    on    earth           '

' '.join(str1.split())

Pero si prefiere una expresión regular, puede hacerlo como:

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

Aunque se debe realizar un preprocesamiento para eliminar el espacio final y final.

ravi tanwar
fuente
3

Esto también parece funcionar:

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

Donde la variable srepresenta tu cadena.

Anakimi
fuente
2

En algunos casos, es deseable reemplazar las ocurrencias consecutivas de cada carácter de espacio en blanco con una sola instancia de ese carácter. Usaría una expresión regular con referencias posteriores para hacer eso.

(\s)\1{1,}coincide con cualquier carácter de espacio en blanco, seguido de una o más apariciones de ese carácter. Ahora, todo lo que necesita hacer es especificar el primer grupo ( \1) como el reemplazo para el partido.

Envolviendo esto en una función:

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

Otra 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
fuente
2

Una línea de código para eliminar todos los espacios adicionales antes, después y dentro de una oración:

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

Explicación:

  1. Divide toda la cadena en una lista.
  2. Filtrar elementos vacíos de la lista.
  3. Vuelva a unir los elementos restantes * con un solo espacio

* Los elementos restantes deben ser palabras o palabras con signos de puntuación, etc. No probé esto exhaustivamente, pero este debería ser un buen punto de partida. ¡Todo lo mejor!

gabchan
fuente
2

Solución para desarrolladores de Python:

import re

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

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

Chadee Fouad
fuente
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
fuente
1

Lo más rápido que puede obtener para cadenas generadas por el usuario es:

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

El cortocircuito lo hace un poco más rápido que la respuesta integral de pythonlarry . Busque esto si busca eficiencia y busca estrictamente eliminar espacios en blanco adicionales de la variedad de espacio único .

Hassan Baig
fuente
1

Muy sorprendente: nadie publicó una función simple que será mucho más rápida que TODAS las demás soluciones publicadas. Aquí va:

def compactSpaces(s):
    os = ""
    for c in s:
        if c != " " or os[-1] != " ":
            os += c 
    return os
rafal chlopek
fuente
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 es una cadena llena de espacios y grifos

Hassan Abdul-Kareem
fuente
0

Para eliminar el espacio en blanco, considerando los espacios en blanco iniciales, finales y adicionales entre las palabras, use:

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

El primero ortrata con el espacio en blanco orinicial , el segundo trata con el inicio del espacio en blanco inicial con una cadena y el último trata con el espacio en blanco final.

Como prueba de uso, este enlace le proporcionará una prueba.

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

Esto se debe utilizar con la función re.split .

CameronE
fuente
0

Tengo mi método simple que he usado en la universidad.

line = "I     have            a       nice    day."

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

Esto reemplazará cada espacio doble con un solo espacio y lo hará 1000 veces. Significa que puede tener 2000 espacios adicionales y seguirá funcionando. :)

Peter Mortensen
fuente
Esto es (prácticamente) idéntico a la respuesta de Anakimi (publicado más de dos años antes).
Peter Mortensen
0

Tengo un método simple sin 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
fuente
1
¿En qué se diferencia esto de la respuesta de Anakimi (publicada más de tres años antes)? ¿No es solo una versión más 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:

Eliminar todo el espacio: ¡Puede seleccionar debajo de los bordes para eliminar el espacio en blanco! BRAliakbar Eliminar el espacio inicial: ¡Puede seleccionar los ajustes a continuación para eliminar el espacio en blanco! BR Aliakbar
Eliminar espacios finales: ¡Puede seleccionar los siguientes ajustes para eliminar espacios en blanco! BR Aliakbar Eliminar espacios iniciales y finales: ¡Puede seleccionar los siguientes ajustes para eliminar espacios en blanco! BR Aliakbar Eliminar más de un espacio: ¡Puede seleccionar los siguientes ajustes para eliminar el espacio en blanco! BR Aliakbar

Aliakbar Hosseinzadeh
fuente
-1

No he leído mucho en los otros ejemplos, pero acabo de crear este método para consolidar múltiples caracteres de espacio consecutivos.

No utiliza ninguna biblioteca, y aunque es relativamente largo en términos de longitud de script, no es una implementación compleja:

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
fuente