【问题标题】:Is there a simple way to remove multiple spaces in a string?有没有一种简单的方法可以删除字符串中的多个空格?
【发布时间】:2010-12-05 11:56:00
【问题描述】:

假设这个字符串:

The   fox jumped   over    the log.

变成:

The fox jumped over the log.

什么是最简单的(1-2 行)实现这一点,而不拆分和进入列表?

【问题讨论】:

  • 您对列表的反感是什么?它们是语言不可分割的一部分,“”.join(list_of_words) 是将字符串列表转换为单个空格分隔字符串的核心惯用语之一。
  • @Tom/@Paul:对于简单的字符串,(string) join 会简单而甜蜜。但是,如果还有其他不想打扰的空白,它会变得更加复杂......在这种情况下,“while”或正则表达式解决方案将是最好的。我在下面发布了一个“正确”的字符串连接,其中包含三种方法的定时测试结果。

标签: python regex string


【解决方案1】:
>>> import re
>>> re.sub(' +', ' ', 'The     quick brown    fox')
'The quick brown fox'

【讨论】:

  • 此解决方案仅处理单个空格字符。它不会像 nsr81 的解决方案那样替换由 \s 处理的制表符或其他空白字符。
  • 没错,string.split 也能处理各种空格。
  • 我更喜欢这个,因为它只关注空格字符,不影响'\n's之类的字符。
  • 是的,没错。但在此之前 strip() 应该完成。它将从两端删除空格。
  • 您可以使用re.sub(' {2,}', ' ', 'The quick brown fox')防止用单空格重复替换单空格
【解决方案2】:

foo 是你的字符串:

" ".join(foo.split())

请注意,尽管这会删除“所有空白字符(空格、制表符、换行符、回车、换页符)”(感谢hhsaffar,请参阅 cmets)。即,"this is \t a test\n" 将有效地以"this is a test" 结束。

【讨论】:

  • “不拆分并进入列表...”
  • 我忽略了“不拆分并进入列表...”,因为我仍然认为这是最好的答案。
  • 这会删除尾随空格。如果你想保留它们: text[0:1] + " ".join(text[1:-1].split()) + text[-1]
  • 也比 re.sub() 解决方案快 6 倍。
  • 这行代码的作用乍一看并不明显。其他人将很难弄清楚您为什么要拆分并重新加入字符串。正则表达式的答案更明确地说明了它的作用。
【解决方案3】:
import re
s = "The   fox jumped   over    the log."
re.sub("\s\s+" , " ", s)

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

因为逗号之前的空格在PEP 8 中被列为宠儿,在cmets 中被列为mentioned by user Martin Thoma

【讨论】:

  • 我倾向于将该正则表达式更改为r"\s\s+",这样它就不会尝试替换已经存在的单个空格。
  • 如果您想要这种行为,为什么不只是 "\s{2,}" 而不是不知道中等高级正则表达式行为的解决方法?
  • 记住sub()不会改变输入字符串s,而是返回新值。
  • 我建议不要使用\s\s+,因为这不会将制表符规范化回正常的空格。 SPACE + TAB 确实会以这种方式被替换。
  • 在执行此操作之前,我还会 strip()(又名修剪)字符串,因为您可能不想要前导和尾随空格。
【解决方案4】:

使用带有 "\s" 的正则表达式并执行简单的 string.split() 将删除其他空格 - 例如换行符、回车符、制表符。除非需要这样做,否则多个空格,我将提供这些示例。

我使用11 paragraphs, 1000 words, 6665 bytes of Lorem Ipsum 进行实际时间测试,并在整个过程中使用了随机长度的额外空格:

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

单行基本上会删除任何前导/尾随空格,并保留前导/尾随空格(但只有 ONE ;-)。

# 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,因为我相信一旦在第一次运行时修改,连续运行会更快(如果只是一点点)。由于这增加了时间,我将此字符串副本添加到其他两个中,以便时间仅在逻辑上显示差异。 Keep in mind that the main stmt on timeit instances will only be executed once;我这样做的原始方式,while 循环在同一标签上工作,original_string,因此第二次运行,将无事可做。它现在的设置方式,调用一个函数,使用两个不同的标签,这不是问题。我已经向所有工作人员添加了assert 语句,以验证我们每次迭代都会更改某些内容(对于那些可能持怀疑态度的人)。例如,更改为 this 并且它会中断:

# 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

对于琐碎的字符串,while 循环似乎是最快的,其次是 Pythonic 字符串拆分/连接,而正则表达式拉到后面。

对于非平凡的字符串,似乎还有更多需要考虑的地方。 32位2.7?这是救援的正则表达式! 2.7 64 位? while 循环是最好的,并且有相当大的优势。 32 位 3.2,使用“正确的”join。 64 位 3.3,使用 while 循环。再次。

最后,如果/在哪里/在需要时可以提高性能,但最好是remember the mantra

  1. 让它发挥作用
  2. 改正
  3. 加快速度

IANAL、YMMV、自告人!

【讨论】:

  • 如果您测试过简单的 ' '.join(the_string.split()),我会更喜欢,因为这是通常的用例,但我想说谢谢您的工作!
  • @wedi:对于其他 cmets(例如来自 Gumbouser984003,尽管她/他的解决方案是假定的并且不会“在所有情况下”起作用),这种解决方案不会t 遵守提问者的要求。可以使用 .split(' ') 和 comp/gen,但在处理前导/尾随空格时会变得更加复杂。
  • @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 的口头禅!并喜欢详细的测试!我很想知道您的想法或观点自 6 年以来是否发生了变化?
  • 缺少使用生成器的版本
【解决方案5】:

我必须同意 Paul McGuire 的评论。对我来说,

' '.join(the_string.split())

比编写正则表达式要好得多。

我的测量结果(Linux 和 Python 2.5)显示 split-then-join 几乎比执行“re.sub(...)”快五倍,如果您预编译正则表达式一次并且仍然快三倍多次执行该操作。而且它更容易理解——更多更 Pythonic。

【讨论】:

  • 这会删除尾随空格。如果你想保留它们: text[0:1] + " ".join(text[1:-1].split()) + text[-1]
  • 一个简单的正则表达式更好读。永远不要在需要之前优化性能。
  • @gcb:为什么不呢?如果您期待高吞吐量场景(例如,因为需求量大)怎么办?在这种情况下,为什么不部署您希望从一开始就占用较少资源的东西?
  • @HassanBaig 如果您已经有性能要求,那么这并不是真正的过早优化,对吧?我的观点是,当您还不需要痴迷于性能时,以可读性为目标总是更好。
【解决方案6】:

与之前的解决方案类似,但更具体:将两个或多个空格替换为一个:

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

【讨论】:

  • 你为什么重复回答?
【解决方案7】:

一个简单的灵魂

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

【讨论】:

    【解决方案8】:

    我尝试了以下方法,它甚至适用于极端情况,例如:

    str1='          I   live    on    earth           '
    
    ' '.join(str1.split())
    

    但如果你更喜欢正则表达式,可以这样做:

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

    尽管必须进行一些预处理才能删除尾随和结束空格。

    【讨论】:

      【解决方案9】:

      您还可以在 Pandas DataFrame 中使用字符串拆分技术,而无需使用 .apply(..),如果您需要对大量字符串快速执行操作,这很有用。这是一行:

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

      【讨论】:

        【解决方案10】:
        import re
        string = re.sub('[ \t\n]+', ' ', 'The     quick brown                \n\n             \t        fox')
        

        这将删除所有制表符、新行和带有单个空格的多个空格。

        【讨论】:

        • 但是,如果您的空格(不可打印)字符不在您的范围内,例如 '\x00' 到 '\x0020',则代码不会删除它们。
        【解决方案11】:

        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

        【讨论】:

        • 这是使用正则表达式(google it)但基本上'+'表示一个或多个空格......所以基本上我用一个空格替换一个或多个空格。
        【解决方案12】:

        一行代码删除句子前后所有多余的空格:

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

        解释:

        1. 将整个字符串拆分成一个列表。
        2. 过滤列表中的空元素。
        3. 用一个空格重新连接其余元素*

        *剩下的元素应该是单词或带有标点符号的单词等。我没有对此进行广泛的测试,但这应该是一个很好的起点。万事如意!

        【讨论】:

          【解决方案13】:

          " ".join(foo.split()) 对于所提出的问题并不完全正确,因为它还 完全 删除了单个前导和/或尾随空格。因此,如果它们也应替换为 1 个空白,您应该执行以下操作:

          " ".join(('*' + foo + '*').split()) [1:-1]
          

          当然,它不那么优雅。

          【讨论】:

            【解决方案14】:

            在某些情况下,希望将每个连续出现的空白字符替换为 字符的单个实例。您可以使用带有反向引用的正则表达式来做到这一点。

            (\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'
            

            【讨论】:

              【解决方案15】:

              另一种选择:

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

              【讨论】:

                【解决方案16】:

                对于用户生成的字符串,您可以获得的最快速度是:

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

                短路使它比pythonlarry's comprehensive answer 稍快。如果您追求效率并且严格希望清除单个空格种类中的额外空格,请选择此方法。

                【讨论】:

                  【解决方案17】:

                  因为@pythonlarry 在这里询问了缺少的基于生成器的版本

                  groupby 加入很简单。 Groupby 将使用相同的键对连续的元素进行分组。并返回每个组的键对和元素列表。因此,当键是空格时,将返回一个空格,否则返回整个组。

                  from itertools import groupby
                  def group_join(string):
                    return ''.join(' ' if chr==' ' else ''.join(times) for chr,times in groupby(string))
                  

                  按变体分组很简单,但速度很慢。所以现在对于生成器变体。在这里,我们使用一个迭代器,即字符串,并产生除字符后面的字符之外的所有字符。

                  def generator_join_generator(string):
                    last=False
                    for c in string:
                      if c==' ':
                        if not last:
                          last=True
                          yield ' '
                      else:
                        last=False
                      yield c
                  
                  def generator_join(string):
                    return ''.join(generator_join_generator(string))
                  

                  所以我用其他一些 lorem ipsum 测量了时间。

                  • while_replace 0.015868543065153062
                  • re_replace 0.22579886706080288
                  • proper_join 0.40058281796518713
                  • group_join 5.53206754301209
                  • generator_join 1.6673167790286243

                  Hello 和 World 之间用 64KB 的空格隔开

                  • while_replace 2.991308711003512
                  • re_replace 0.08232860406860709
                  • proper_join 6.294375243945979
                  • group_join 2.4320066600339487
                  • generator_join 6.329648651066236

                  不要忘记原句

                  • while_replace 0.002160938922315836
                  • re_replace 0.008620491018518806
                  • proper_join 0.005650000995956361
                  • group_join 0.028368217987008393
                  • generator_join 0.009435956948436797

                  这里有趣的是几乎只有空间的字符串组连接并没有那么糟糕 时间显示始终是七次运行的中位数,每次运行一千次。

                  【讨论】:

                    【解决方案18】:

                    非常令人惊讶 - 没有人发布比所有其他发布的解决方案更快的简单功能。就是这样:

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

                    【讨论】:

                    • 这怎么更快?!您正在使用循环遍历整个字符串。如果这是一个超大字符串,可能需要很长时间。正则表达式更快。更不用说当正则表达式在 1 行中执行时您占用了 5 行。我更喜欢尽可能避免循环。
                    【解决方案19】:
                    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='') 
                    

                    结果:作为代码

                    "Remove all space:Youcanselectbelowtrimsforremovingwhitespace!!BRAliakbar"
                    "Remove leading space:You can select below trims for removing white space!!   BR Aliakbar"     
                    "Remove trailing spaces: You can select below trims for removing white space!!   BR Aliakbar"
                    "Remove leading and trailing spaces:You can select below trims for removing white space!!   BR Aliakbar"
                    "Remove more than one space: You can select below trims for removing white space!! BR Aliakbar" 
                    

                    【讨论】:

                      【解决方案20】:
                      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())
                      

                      【讨论】:

                        【解决方案21】:
                        string = 'This is a             string full of spaces          and taps'
                        string = string.split(' ')
                        while '' in string:
                            string.remove('')
                        string = ' '.join(string)
                        print(string)
                        

                        结果

                        这是一个充满空格和抽头的字符串

                        【讨论】:

                          【解决方案22】:

                          要删除空格,考虑单词之间的前导、尾随和额外空格,请使用:

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

                          第一个or 处理前导空格,第二个or 处理字符串开头的前导空格,最后一个处理尾随空格。

                          为了使用证明,此链接将为您提供测试。

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

                          这将与re.split 函数一起使用。

                          【讨论】:

                            【解决方案23】:

                            我没有对其他示例进行大量阅读,但我刚刚创建了这种用于合并多个连续空格字符的方法。

                            它不使用任何库,虽然它的脚本长度相对较长,但它的实现并不复杂:

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

                            【讨论】:

                              【解决方案24】:

                              这样做并且将会这样做::)

                              # python... 3.x
                              import operator
                              ...
                              # line: line of text
                              return " ".join(filter(lambda a: operator.is_not(a, ""), line.strip().split(" ")))
                              

                              【讨论】:

                                猜你喜欢
                                • 1970-01-01
                                • 2021-03-30
                                • 1970-01-01
                                • 2011-11-15
                                • 1970-01-01
                                • 2011-07-03
                                • 1970-01-01
                                • 2011-02-27
                                相关资源
                                最近更新 更多