Existe-t-il un moyen simple de supprimer plusieurs espaces dans une chaîne?

390

Supposons que cette chaîne:

The   fox jumped   over    the log.

Se transformer en:

The fox jumped over the log.

Quelle est la plus simple (1-2 lignes) pour y parvenir, sans se diviser et entrer dans des listes?

TIMEX
la source
22
Quelle est votre aversion pour les listes? Ils font partie intégrante du langage et "" .join (list_of_words) est l'un des idiomes principaux pour faire une liste de chaînes en une seule chaîne délimitée par des espaces.
PaulMcG
3
@ Tom / @ Paul: Pour les chaînes simples, la jointure (chaîne) serait simple et agréable. Mais cela devient plus complexe s'il y a d'autres espaces blancs que l'on ne veut PAS déranger ... dans ce cas, des solutions "pendant" ou regex seraient les meilleures. J'ai publié ci-dessous une jointure de chaîne qui serait "correcte", avec des résultats de test chronométrés pour trois façons de procéder.
pythonlarry

Réponses:

529
>>> import re
>>> re.sub(' +', ' ', 'The     quick brown    fox')
'The quick brown fox'
Josh Lee
la source
20
Cette solution ne gère que les caractères d'espacement simples. Il ne remplacerait pas un onglet ou d'autres caractères d'espacement gérés par \ s comme dans la solution de nsr81.
Taylor Leese
2
C'est vrai, string.splitgère également toutes sortes d'espaces blancs.
Josh Lee
6
Je préfère celui-ci car il se concentre uniquement sur le caractère espace et n'affecte pas les caractères comme '\ n's.
hhsaffar
2
Oui bien. Mais avant cela, strip () doit être fait. Il supprimera les espaces des deux extrémités.
Hardik Patel
17
Vous pouvez utiliser re.sub(' {2,}', ' ', 'The quick brown fox')pour empêcher les remplacements redondants d'un espace unique par un espace unique .
AneesAhmed777
541

foo est votre chaîne:

" ".join(foo.split())

Soyez averti bien que cela supprime "tous les espaces blancs (espace, tabulation, retour à la ligne, retour, saut de page)" (merci à hhsaffar , voir commentaires). C'est-à-dire, "this is \t a test\n"finira effectivement par "this is a test".

Taylor Leese
la source
19
"Sans se diviser et entrer dans les listes ..."
Gumbo
72
J'ai ignoré "Sans se diviser et entrer dans les listes ..." parce que je pense toujours que c'est la meilleure réponse.
Taylor Leese
1
Cela supprime les espaces de fin. Si vous voulez les garder, faites: text [0: 1] + "" .join (text [1: -1] .split ()) + text [-1]
user984003
6 fois plus rapide que la solution re.sub () également.
nerdfever.com
1
@ AstraUvarova-Saturn'sstar Je l'ai profilé.
nerdfever.com
85
import re
s = "The   fox jumped   over    the log."
re.sub("\s\s+" , " ", s)

ou

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

puisque l'espace avant la virgule est répertorié comme une bête noire dans PEP 8 , comme mentionné par l'utilisateur Martin Thoma dans les commentaires.

Nasir
la source
2
J'aurais tendance à changer cette expression rationnelle pour r"\s\s+"qu'elle n'essaye pas de remplacer des espaces déjà uniques.
Ben Blank
19
Si vous vouliez ce comportement, pourquoi pas juste "\s{2,}"au lieu d'une solution de contournement pour ne pas connaître le comportement regex modérément avancé?
Chris Lutz
2
rappelez-vous que sub () ne modifie pas la chaîne d'entrée s, mais renvoie la nouvelle valeur.
gcb
1
@moose - C'est une optimisation de la lisibilité qu'une performance. \s+entraînerait la ligne à lire "remplacer un ou plusieurs espaces par un espace", plutôt que "remplacer deux ou plusieurs espaces par un espace". Le premier me fait immédiatement arrêter et penser "Pourquoi remplacer un espace par un espace? C'est idiot." Pour moi, c'est une odeur de code (très mineure). En fait , je me attends pas qu'il y ait une différence de performance du tout entre les deux, car il va être copier dans une nouvelle chaîne de toute façon, et doit cesser et le test quel que soit l'endroit où est copié l'espace à partir .
Ben Blank
8
Je déconseille \s\s+car cela ne normalisera pas un caractère TAB dans un espace normal. un SPACE + TAB est remplacé de cette façon.
vdboor
51

Utiliser des expressions rationnelles avec "\ s" et faire de simples string.split () supprimera également d' autres espaces comme les retours à la ligne, les retours chariot et les tabulations. À moins que cela ne soit souhaité, pour ne faire que plusieurs espaces , je présente ces exemples.

J'ai utilisé 11 paragraphes, 1000 mots, 6665 octets de Lorem Ipsum pour obtenir des tests de temps réalistes et utilisé des espaces supplémentaires de longueur aléatoire tout au long:

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

Le one-liner fera essentiellement une bande de tous les espaces de début / fin, et il conserve un espace de début / fin (mais seulement UN ;-).

# 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

REMARQUE: La " whileversion" a fait une copie de la original_string, comme je crois une fois modifiée lors de la première exécution, les exécutions successives seraient plus rapides (ne serait-ce que par un peu). Comme cela ajoute du temps, j'ai ajouté cette copie de chaîne aux deux autres afin que les heures ne montrent la différence que dans la logique. Gardez à l' esprit que le principal stmtsur les timeitinstances ne sera exécutée une fois ; comme je l'ai fait à l'origine, la whileboucle fonctionnait sur le même label original_string, donc la deuxième manche, il n'y aurait rien à faire. La façon dont il est configuré maintenant, appeler une fonction, en utilisant deux étiquettes différentes, ce n'est pas un problème. J'ai ajouté des assertdéclarations à tous les travailleurs pour vérifier que nous changeons quelque chose à chaque itération (pour ceux qui peuvent être douteux). Par exemple, changez cela et ça casse:

# 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

Pour la chaîne triviale, il semblerait qu'une boucle while soit la plus rapide, suivie de la division / jointure de chaîne Pythonic et de l'expression régulière tirant vers l'arrière.

Pour les chaînes non triviales , il semble qu'il y ait un peu plus à considérer. 32 bits 2,7? C'est regex à la rescousse! 2.7 64 bits? Une whileboucle est préférable, avec une marge décente. 32 bits 3.2, allez avec le "bon" join. 64 bits 3.3, optez pour une whileboucle. Encore.

En fin de compte, on peut améliorer les performances si / où / quand cela est nécessaire , mais il est toujours préférable de se rappeler le mantra :

  1. Fais-le fonctionner
  2. Fais-le bien
  3. Faites vite

IANAL, YMMV, Caveat Emptor!

pythonlarry
la source
1
J'aurais préféré que vous ayez testé le simple ' '.join(the_string.split())car c'est le cas d'usage habituel mais j'aimerais vous dire merci pour votre travail!
wedi
@wedi: Selon d'autres commentaires (comme de Gumbo ; user984003 , bien que sa solution soit présomptive et ne fonctionnera pas "dans tous les cas"), ce type de solution n'adhère pas à la demande de l'interrogateur. On peut utiliser .split (''), et un comp / gen, mais devient plus poilu pour gérer les espaces de plomb / de fuite.
pythonlarry
@wedi: Par exemple: ' '.join(p for p in s.split(' ') if p)<- toujours perdu des espaces de plomb / de fin, mais représentait plusieurs espaces. Pour les garder, faut faire comme parts = s.split(' '); (' ' if not parts[0] else '') + ' '.join(p for p in s.split(' ') if p) + (' ' if not parts[-1] else '')!
pythonlarry
Merci @pythonlarry pour le mantra! et j'adore le test détaillé! Je suis curieux de savoir si vos pensées ou vos opinions ont changé à ce sujet depuis 6 ans?
JayRizzo
Version manquante qui utilise des générateurs
Lee
42

Je suis d'accord avec le commentaire de Paul McGuire. Tome,

' '.join(the_string.split())

est largement préférable à fouetter une expression régulière.

Mes mesures (Linux et Python 2.5) montrent que le split-then-join est presque cinq fois plus rapide que de faire le "re.sub (...)", et encore trois fois plus rapide si vous précompilez le regex une fois et effectuez l'opération plusieurs fois. Et il est de toute façon plus facile à comprendre - beaucoup plus Pythonic.

Kevin Little
la source
Cela supprime les espaces de fin. Si vous voulez les garder, faites: text [0: 1] + "" .join (text [1: -1] .split ()) + text [-1]
user984003
4
une expression rationnelle simple est bien meilleure à lire. n'optimisez jamais les performances avant d'en avoir besoin.
gcb
@gcb: Pourquoi pas? Et si vous vous attendez à un scénario à haut débit (par exemple en raison d'une forte demande)? Pourquoi ne pas déployer quelque chose que vous pensez être moins gourmand en ressources dès le départ dans ce scénario?
Hassan Baig
1
@HassanBaig si vous avez déjà l'exigence de performance, ce n'est pas vraiment une optimisation prématurée, non? Mon point est que lorsque vous n'avez pas encore besoin d'être obsédé par les performances, il est toujours préférable de viser la lisibilité.
gcb
14

Similaire aux solutions précédentes, mais plus spécifique: remplacez deux ou plusieurs espaces par un:

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

Une soultion simple

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

Vous pouvez également utiliser la technique de fractionnement de chaînes dans un Pandas DataFrame sans avoir besoin d'utiliser .apply (..), qui est utile si vous devez effectuer l'opération rapidement sur un grand nombre de chaînes. Ici, c'est sur une seule ligne:

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

Cela supprimera tous les onglets, les nouvelles lignes et les multiples espaces blancs avec un seul espace blanc.

Rakesh Kumar
la source
Mais si vous avez des espaces (non imprimables) qui ne sont pas dans votre plage comme '\ x00' à '\ x0020' le code ne les supprimera pas.
Muskovets
5

J'ai essayé la méthode suivante et cela fonctionne même avec le cas extrême comme:

str1='          I   live    on    earth           '

' '.join(str1.split())

Mais si vous préférez une expression régulière, cela peut être fait comme:

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

Bien qu'un certain prétraitement doive être fait afin de supprimer l'espace de fin et de fin.

ravi tanwar
la source
3

Cela semble également fonctionner:

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

Où la variable sreprésente votre chaîne.

Anakimi
la source
2

Dans certains cas, il est souhaitable de remplacer les occurrences consécutives de chaque caractère d'espacement par une seule instance de ce caractère. Vous utiliseriez une expression régulière avec des références arrières pour ce faire.

(\s)\1{1,}correspond à n'importe quel caractère d'espacement, suivi d'une ou plusieurs occurrences de ce caractère. Maintenant, tout ce que vous devez faire est de spécifier le premier groupe ( \1) comme remplacement pour la correspondance.

Envelopper cela dans une fonction:

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
la source
2

Une autre alternative:

>>> 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
la source
2

Une ligne de code pour supprimer tous les espaces supplémentaires avant, après et dans une phrase:

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

Explication:

  1. Divisez la chaîne entière en une liste.
  2. Filtrez les éléments vides de la liste.
  3. Rejoignez les éléments restants * avec un seul espace

* Les éléments restants devraient être des mots ou des mots avec des signes de ponctuation, etc. Je n'ai pas testé cela de manière approfondie, mais cela devrait être un bon point de départ. Bonne chance!

gabchan
la source
2

Solution pour les développeurs Python:

import re

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

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

Chadee Fouad
la source
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
la source
1

Le plus rapide que vous pouvez obtenir pour les chaînes générées par l'utilisateur est:

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

Le court-circuitage le rend légèrement plus rapide que la réponse complète de pythonlarry . Allez-y si vous recherchez l'efficacité et que vous cherchez strictement à éliminer les espaces supplémentaires de la variété à espace unique .

Hassan Baig
la source
1

Assez surprenant - personne n'a publié de fonction simple qui sera beaucoup plus rapide que TOUTES les autres solutions publiées. Ça y est:

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

Résultats :

Ceci est une chaîne pleine d'espaces et de robinets

Hassan Abdul-Kareem
la source
0

Pour supprimer les espaces blancs, en tenant compte des espaces blancs de début, de fin et supplémentaires entre les mots, utilisez:

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

Le premier orconcerne les principaux espaces blancs, le secondor de début début des espaces blancs de début de chaîne et le dernier les espaces blancs de fin.

Pour preuve d'utilisation, ce lien vous fournira un test.

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

Il doit être utilisé avec la fonction re.split .

CameronE
la source
0

J'ai ma méthode simple que j'ai utilisée au collège.

line = "I     have            a       nice    day."

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

Cela remplacera chaque double espace par un seul espace et le fera 1000 fois. Cela signifie que vous pouvez avoir 2000 espaces supplémentaires et fonctionnera toujours. :)

Peter Mortensen
la source
Ceci est (pratiquement) identique à la réponse d' Anakimi (publiée plus de deux ans auparavant).
Peter Mortensen
0

J'ai une méthode simple sans diviser:

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
la source
1
En quoi est-ce différent de la réponse d' Anakimi (publiée plus de trois ans auparavant)? N'est-ce pas juste une version plus compliquée?
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='') 

Résultat:

Supprimer tout espace: vous pouvez sélectionner les bords inférieurs pour supprimer les espaces blancs! BR Aliakbar
Supprimer les espaces de fin: Vous pouvez sélectionner ci-dessous les garnitures pour supprimer les espaces blancs !! BR Aliakbar Supprimer les espaces de début et de fin: Vous pouvez sélectionner ci-dessous les garnitures pour supprimer les espaces blancs !! BR Aliakbar Supprimer plus d'un espace: Vous pouvez sélectionner ci-dessous les garnitures pour supprimer les espaces blancs !! BR Aliakbar

Aliakbar Hosseinzadeh
la source
-1

Je n'ai pas beaucoup lu dans les autres exemples, mais je viens de créer cette méthode pour consolider plusieurs caractères d'espace consécutifs.

Il n'utilise aucune bibliothèque, et bien qu'il soit relativement long en termes de longueur de script, ce n'est pas une implémentation complexe:

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