Чи є простий спосіб видалити декілька пробілів у рядку?


390

Припустимо, цей рядок:

The   fox jumped   over    the log.

Перетворення в:

The fox jumped over the log.

Що найпростіше (1-2 рядки) досягти цього, не розбиваючи і не переходячи на списки?


22
Яке ваше відраза від списків? Вони є невід'ємною частиною мови, і "" .join (list_of_words) є однією з основних ідіом для складання списку рядків в єдиний розділений пробілом рядок.
PaulMcG

3
@ Tom / @ Paul: Для простих рядків, (string) приєднання було б простим і солодким. Але це стає складнішим, якщо є інший пробіл, який НЕ хочеться турбувати ... в цьому випадку рішення "під час" або регулярні виразки були б найкращими. Я розмістив нижче рядкове з'єднання, яке було б "правильним", з приуроченими результатами тестування для трьох способів цього зробити.
pythonlarry

Відповіді:


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

20
Це рішення обробляє лише символи простору. Він не замінить вкладку чи інші символи пробілу, якими керується \ s, як у рішенні nsr81.
Тейлор Ліз

2
Це правда, string.splitтакож обробляє всі види пробілів.
Джош Лі

6
Я вважаю за краще цей, оскільки він зосереджений лише на символі пробілу і не впливає на таких символів, як '\ n'.
hhsaffar

2
Так правильно. Але перед цим слід зробити смужку (). Це видалить пробіли з обох кінців.
Hardik Patel

17
Ви можете використовувати re.sub(' {2,}', ' ', 'The quick brown fox')для запобігання надмірної заміни однопросторового на однопросторовий .
AneesAhmed777

541

foo ваша рядок:

" ".join(foo.split())

Будьте попереджені, хоча це видаляє "всі символи пробілу (пробіл, вкладка, новий рядок, повернення, formfeed)" (завдяки hhsaffar , див. Коментарі). Тобто, "this is \t a test\n"фактично вийде як "this is a test".


19
"Без розбивки і не потрапляння на списки ..."
Gumbo

72
Я проігнорував "Без розбиття і не потрапляючи на списки ...", бо все ще вважаю, що це найкраща відповідь.
Тейлор Ліз

1
Це видаляє пробіли. Якщо ви хочете зробити так: текст [0: 1] + "" .join (текст [1: -1] .split ()) + текст [-1]
користувач984003

На 6 разів швидше, ніж рішення re.sub ().
nerdfever.com

1
@ AstraUvarova-Saturn'sstar Я профілював це.
nerdfever.com

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

або

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

оскільки простір перед комою занесено до домашнього улюбленця в PEP 8 , про що згадує користувач Мартін Тома в коментарях.


2
Я схильний змінити цей регулярний вираз r"\s\s+"так, щоб він не намагався замінити вже єдині пробіли.
Бен Бланк

19
Якщо ви хотіли такої поведінки, чому б не просто "\s{2,}"замість вирішення проблеми, що не знала помірковано розвиненої регексу?
Кріс Лутц

2
пам’ятайте, що sub () не змінює рядок введення s, але повертає нове значення.
gcb

1
@moose - це оптимізація читабельності, ніж ефективність. \s+призведе до того, що рядок буде читати "замінити один чи більше пробілів пробілом", а не "замінити два чи більше пробілів пробілом". Колишній одразу змушує мене зупинитися і подумати: "Навіщо замінювати один простір одним? Це дурне". Для мене це (дуже другорядний) запах коду. Я на насправді не очікую , що будь-яка різниця в продуктивності взагалі між ними, як це буде копіювати в новий рядок , в будь-якому випадку, і повинен зупинитися і випробування , незалежно від того, де простору копіюються з .
Бен Бланк

8
Я б радив, \s\s+оскільки це не нормалізує символ TAB назад у звичайний простір. SPACE + TAB замінюється таким чином.
vdboor

51

Використання шрифтів з "\ s" та простого string.split () також видалить інші пробіли - наприклад, нові рядки, повернення каретки, вкладки. Якщо це не бажано, щоб тільки зробити кілька прогалин , я уявляю ці приклади.

Я використовував 11 абзаців, 1000 слів, 6665 байт Lorem Ipsum, щоб отримати реалістичні тести часу та використовував додаткові пробіли випадкової довжини протягом:

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

Одне вкладиш буде, по суті, робити смугу будь-яких провідних / кінцевих просторів, і він зберігає провідний / кінцевий простір (але лише ОДИН ;-).

# 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

ПРИМІТКА . " whileВерсія" зробила копію original_string, як я вважаю, що колись було змінено під час першого запуску, послідовне виконання буде швидше (хоч би трохи). Оскільки це додає часу, я додав цю строкову копію до двох інших, щоб часи показали різницю лише в логіці. Майте на увазі, що головнеstmt в timeitекземплярах буде виконано лише один раз ; коли б я це робив, whileцикл працював над тією ж міткою original_string, таким чином, при другому виконанні нічого робити не було. То, як це налаштовано зараз, виклик функції за допомогою двох різних міток, це не проблема. Я додав assertзаяви до всіх працівників, щоб переконатися, що ми змінюємо щось під час кожної ітерації (для тих, хто може бути сумнівним). Наприклад, змініть це і воно порушиться:

# 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

Для тривіальної струни, здається, що цикл у той час є найшвидшим, за ним слід пітонічна струна-розділ / з'єднання та витяг, що підтягується ззаду.

Для нетривіальних рядків , здається, варто ще трохи розглянути. 32-розрядні 2,7? Це виручка на допомогу! 2,7 64-розрядні? whileПетля краще, пристойний запас. 32-бітний 3.2, перейдіть з "належним" join. 64-бітний 3.3, перейдіть до whileциклу. Знову.

Зрештою, можна покращити продуктивність, якщо / де / коли потрібно , але найкраще пам’ятати про мантру :

  1. Зробити це працює
  2. Зроби це правильно
  3. Зробити це швидко

IANAL, YMMV, Caveat Emptor!


1
Я вважав за краще, якби ви протестували просте, ' '.join(the_string.split())оскільки це звичайний випадок використання, але я хотів би сказати дякую за вашу роботу!
срі

@wedi: За іншими коментарями (наприклад, від Gumbo ; user984003 , хоча її / його рішення є презумпційним і не працюватиме "у всіх випадках"), таке рішення не дотримується запиту запитувача. Можна використовувати .split ('') та comp / gen, але отримує хайєр для вирішення проміжних / кінцевих просторів.
pythonlarry

@wedi: Напр .: ' '.join(p for p in s.split(' ') if p)<- все ще втрачені провідні / кінцеві пробіли, але припадають на кілька пробілів. Щоб їх утримати, треба робити так parts = s.split(' '); (' ' if not parts[0] else '') + ' '.join(p for p in s.split(' ') if p) + (' ' if not parts[-1] else '')!
pythonlarry

Дякую @pythonlarry за мантру! і любите детальний тест! Мені цікаво дізнатися, чи змінилися ваші думки чи погляди з цього моменту, коли минуло 6 років?
JayRizzo

Відсутня версія, яка використовує генератори
Лі

42

Я маю згоду з коментарем Пола Макгуайра. Для мене, мені,

' '.join(the_string.split())

є переважним перевагою, ніж вибивання регексу.

Мої вимірювання (Linux і Python 2.5) показують, що розділення, а потім приєднання буде майже в п’ять разів швидше, ніж робити "re.sub (...)", і все-таки втричі швидше, якщо попередньо скомпілювати регекс і виконати операцію кілька разів. І це будь-якою мірою простіше зрозуміти - набагато більше піфонічного.


Це видаляє пробіли. Якщо ви хочете, щоб вони робили: text [0: 1] + "" .join (текст [1: -1] .split ()) + текст [-1]
user984003

4
простий rgexp набагато краще читати. ніколи не оптимізуйте продуктивність, перш ніж вам потрібно.
gcb

@gcb: Чому ні? Що робити, якщо ви очікуєте високої пропускної спроможності (наприклад, через високий попит)? Чому б не розгорнути те, що, як ви очікуєте, буде менш ресурсомістким, ніж розпочати цей сценарій?
Хассан Байг

1
@HassanBaig якщо у вас вже є вимога до продуктивності, то це насправді не передчасна оптимізація, правда? Моя думка полягає в тому, коли вам ще не потрібно нав'язливо ставитися до продуктивності, завжди краще прагнути до читабельності.
gcb

14

Подібні до попередніх рішень, але більш конкретні: замініть два чи більше пробілів на один:

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

11

Проста душа

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

6

Ви також можете використовувати техніку розбиття рядків у програмі Pandas DataFrame, не використовуючи .apply (..), що корисно, якщо вам потрібно швидко виконати операцію на великій кількості рядків. Ось це в одному рядку:

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

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

Це видалить усі вкладки, нові лінії та декілька пробілів з одним пробілом.


Але якщо у вас є пробіли (недруковані) символи, які не входять у ваш діапазон, як-от "\ x00" до "\ x0020", код не зніме їх.
Мусковець

5

Я спробував наступний метод, і він працює навіть у крайньому випадку, наприклад:

str1='          I   live    on    earth           '

' '.join(str1.split())

Але якщо ви віддаєте перевагу регулярному вираженню, це можна зробити так:

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

Хоча для попередньої обробки та закінчення простору необхідно виконати деяку попередню обробку.


3

Це також, здається, працює:

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

Де змінна sпредставляє вашу рядок.


2

У деяких випадках бажано замінити послідовне виникнення кожного символу пробілу одним екземпляром цього символу. Для цього ви використовуєте регулярний вираз із зворотними посиланнями.

(\s)\1{1,}відповідає будь-якому символу пробілу, за яким слідує одне або кілька входів цього символу. Тепер все, що вам потрібно зробити, це вказати першу групу ( \1) як заміну на матч.

Обгортання цієї функції:

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

Ще одна альтернатива:

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

Один рядок коду для видалення всіх зайвих пробілів до, після та в реченні:

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

Пояснення:

  1. Розбийте весь рядок на список.
  2. Фільтруйте порожні елементи зі списку.
  3. З’єднайте решта елементів * з одним пробілом

* Решта елементів повинні бути слова або слова з розділовими знаками і т. Д. Я не перевіряв це широко, але це повинно бути гарною відправною точкою. Все найкраще!


2

Рішення для розробників Python:

import re

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

Вихід:
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

Найшвидше ви можете отримати для створених користувачем рядків:

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

Коротке замикання робить це трохи швидше, ніж вичерпна відповідь пітонарі . Займіться цим, якщо ви досягли ефективності і чітко прагнете вилучити зайві пробіли з одного простору .


1

Досить дивно - ніхто не розмістив просту функцію, яка буде набагато швидшою, ніж ВСІ інші розміщені рішення. Ось це іде:

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)

Результати :

Це рядок, повний пробілів і кранів


0

Щоб видалити пробіл, враховуючи провідні, відсталі та зайвий пробіл між словами, використовуйте:

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

Перший orстосується провідних білих просторів, другийor стосується початку білого простору, який веде до рядків, а останній стосується пробілу білого простору.

Для підтвердження використання це посилання надасть вам тест.

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

Це потрібно використовувати з функцією re.split .


0

У мене є простий метод, який я використовував у коледжі.

line = "I     have            a       nice    day."

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

Це замінить кожен подвійний простір на один простір і зробить це 1000 разів. Це означає, що ви можете мати 2000 додаткових просторів і все одно працюватимете. :)


Це (практично) ідентично відповіді Анакімі (опублікованому раніше, ніж за два роки).
Пітер Мортенсен

0

У мене простий метод без розщеплення:

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
Чим це відрізняється від відповіді Анакімі (опублікованого раніше, ніж за три роки)? Це не просто складніша версія?
Пітер Мортенсен

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='') 

Результат:

Вилучіть увесь простір: Ви можете вибрати вибрані нижче рамки, щоб зняти простір !! BR Aliakbar
Видалення пробілів: Ви можете вибрати нижню частину обрізки для видалення пробілів !! BR Aliakbar Видаліть провідні та кінцеві пробіли: Ви можете вибрати нижню обробку для видалення пробілів !! BR Aliakbar Видаліть більше одного простору: Ви можете вибрати нижню обробку для видалення пробілу !! BR Aliakbar


-1

Я не багато читав в інших прикладах, але я тільки що створив цей метод для консолідації кількох послідовних символів пробілу.

Він не використовує жодних бібліотек, і хоча він відносно довгий з точки зору довжини сценарію, це не є складною реалізацією:

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)))
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.