【问题标题】:replace new line with space only if it isn't followed by another new line (undo hard wrap in text)仅当新行后面没有新行时才用空格替换新行(撤消文本中的硬换行)
【发布时间】:2016-10-23 04:20:18
【问题描述】:

我有一堆带有硬换行的文本文件(即大约 80 个字符的新行)。我想撤消它并将所有这些句子连接在一起,但在它们是新章节或段落的地方保留新行。

即当且仅当以下字符不是另一个'\n'时,我想用''替换'\n'

以下 python 代码可以满足我的需求,但效率不高,我宁愿使用 regex 和/或 sed 来实现。

s = open(filename, 'r').read()
p = s.split('\n\n') # split into paragraphs
p = [x.replace('\n', ' ') for x in p] # iterate all paragraphs, replace \n
s2 = '\n\n'.join(p) # join paragraphs back together

例如

Lorem ipsum dolor sit amet, consectetur adipiscing
elit. Vivamus porta dui quis aliquet interdum. Sed
in pellentesque libero. Quisque tempus nisl nec
nisl condimentum ullamcorper.

Mauris vulputate nibh nec ipsum mattis rutrum.
Nunc nec tristique magna, non sagittis lacus.
Aliquam id urna lectus.

Maecenas volutpat libero quis erat mollis, et
aliquet purus dignissim. Sed faucibus, lectus in
auctor ornare, dolor libero ultrices sem, vel
iaculis ex nulla quis lacus.

应该变成:

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus porta dui quis aliquet interdum. Sed in pellentesque libero. Quisque tempus nisl nec nisl condimentum ullamcorper.

Mauris vulputate nibh nec ipsum mattis rutrum. Nunc nec tristique magna, non sagittis lacus. Aliquam id urna lectus.

Maecenas volutpat libero quis erat mollis, et aliquet purus dignissim. Sed faucibus, lectus in auctor ornare, dolor libero ultrices sem, vel iaculis ex nulla quis lacus.

更新

我已经在一个 5MB 的文本文件上尝试并计时了下面的 5 种 python 方法。我很惊讶地发现所有 3 种正则表达式方法都比 python 的 split/replace/join 方法慢了一个数量级。

def m1(s):
    p = s.split('\n\n') # split into paragraphs
    p = [x.replace('\n', ' ') for x in p] # iterate all paragraphs, replace \n
    r = '\n\n'.join(p) # join paragraphs back together
    return r

def m2(s):
    r = re.sub(r"(?<!\n)\n(?!\n)", " ", s)
    return r

def m3(s):
    p = re.compile(ur'(?<!^)\n(?=\S)', re.MULTILINE)
    r = re.sub(p, u" ", s)
    return r

def m4(s):
    r = "".join(["".join(v) if k else " ".join(map(str.strip, v))+"\n"  for k, v in groupby(s, str.isspace)])
    return r


def repl(m):
    return (' ' if len(m.group(1))==1 else m.group(1)) + m.group(2)
def m5(s):
    r = re.sub(r'(\n+)(.)', repl, s)
    return r

结果:

np.array( timeit.repeat('r=m1(s)', 'from __main__ import *', repeat=5, number=N) )/N
Out[4]: array([ 0.01343679,  0.0136183 ,  0.0153013 ,  0.0122381 ,  0.01205051])

np.array( timeit.repeat('r=m2(s)', 'from __main__ import *', repeat=5, number=N) )/N
Out[5]: array([ 0.10881839,  0.108728  ,  0.10904381,  0.10862441,  0.10867569])

np.array( timeit.repeat('r=m3(s)', 'from __main__ import *', repeat=5, number=N) )/N
Out[6]: array([ 0.1358021 ,  0.1352592 ,  0.13556101,  0.1357465 ,  0.1354876 ])

np.array( timeit.repeat('r=m4(s)', 'from __main__ import *', repeat=5, number=N) )/N
Out[7]: array([ 2.51403842,  2.37821078,  2.4169096 ,  2.56688828,  2.36240571])

np.array( timeit.repeat('r=m5(s)', 'from __main__ import *', repeat=5, number=N) )/N
Out[8]: array([ 0.16381941,  0.1616353 ,  0.1620033 ,  0.1617353 ,  0.1615443 ])

【问题讨论】:

    标签: python regex sed


    【解决方案1】:

    使用re.sub(),然后你必须玩弄否定 后视和前瞻断言。如果您的输入很大,这将不是很有效。

    后视:

    (?<!...)
         Matches if the current position in the string is not preceded by a match for .... This is called a negative lookbehind assertion. Similar to positive lookbehind assertions, the contained pattern must only match strings of some fixed length. Patterns which start with negative lookbehind assertions may match at the beginning of the string being searched.
    

    前瞻:

    (?!...)
         Matches if ... doesn’t match next. This is a negative lookahead assertion. For example, Isaac (?!Asimov) will match 'Isaac ' only if
     it’s not followed by 'Asimov'. 
    

    这是一个例子:

    >>> text = """Lorem ipsum dolor sit amet, consectetur adipiscing
    elit. Vivamus porta dui quis aliquet interdum. Sed
    in pellentesque libero. Quisque tempus nisl nec
    nisl condimentum ullamcorper.
    
    Mauris vulputate nibh nec ipsum mattis rutrum.
    Nunc nec tristique magna, non sagittis lacus.
    Aliquam id urna lectus.
    
    Maecenas volutpat libero quis erat mollis, et
    aliquet purus dignissim. Sed faucibus, lectus in
    auctor ornare, dolor libero ultrices sem, vel
    iaculis ex nulla quis lacus."""
    
    >>> re.sub(r"(?<!\n)\n(?!\n)", " ", text)
    'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus porta dui quis aliquet interdum. Sed in pellentesque libero. Quisque tempus nisl nec nisl condimentum ullamcorper.\n\nMauris vulputate nibh nec ipsum mattis rutrum. Nunc nec tristique magna, non sagittis lacus. Aliquam id urna lectus.\n\nMaecenas volutpat libero quis erat mollis, et aliquet purus dignissim. Sed faucibus, lectus in auctor ornare, dolor libero ultrices sem, vel iaculis ex nulla quis lacus.'
    
    >>> print(_)
    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus porta dui quis aliquet interdum. Sed in pellentesque libero. Quisque tempus nisl nec nisl condimentum ullamcorper.
    
    Mauris vulputate nibh nec ipsum mattis rutrum. Nunc nec tristique magna, non sagittis lacus. Aliquam id urna lectus.
    
    Maecenas volutpat libero quis erat mollis, et aliquet purus dignissim. Sed faucibus, lectus in auctor ornare, dolor libero ultrices sem, vel iaculis ex nulla quis lacus.
    

    【讨论】:

    • 谢谢。不知道为什么这得到了-ve投票。它对我有用。虽然我必须使用空格而不是 "" 否则相邻的单词会粘在一起(在您的示例中,请参见 adipiscingelit.re.sub("\n(?!\n)", " ", text)
    • 我没有投反对票,但这不会保留段落。
    • 它确实保留了我的段落。
    • @memo 根据您的输出,应保留两行之间的空格,此处未满足
    • 此外,它不保留adipiscingelit 之间的空间
    【解决方案2】:

    你可以使用awk,像这样:

    awk '{$1=$1}1' RS='' ORS='\n\n' OFS=' ' file
    

    解释:

    • {$1=$1} 看起来不会改变任何东西。没错,但awk 仍然会使用新的分隔符重新组合记录(如下所示)

    • 1 始终评估为 true,因为没有指定任何操作,awk 将打印整个当前记录

    • RS=''输入记录分隔符中。空字符串是它的特殊值。这意味着用空行分割记录,用新行分割字段。

    • ORS='\n\n' 也将输出记录分隔符设置为空行。

    • OFS=' '输出字段分隔符设置为空格。

    输出:

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus porta dui quis aliquet interdum. Sed in pellentesque libero. Quisque tempus nisl nec nisl condimentum ullamcorper.
    
    Mauris vulputate nibh nec ipsum mattis rutrum. Nunc nec tristique magna, non sagittis lacus. Aliquam id urna lectus.
    
    Maecenas volutpat libero quis erat mollis, et aliquet purus dignissim. Sed faucibus, lectus in auctor ornare, dolor libero ultrices sem, vel iaculis ex nulla quis lacus.
    

    【讨论】:

    • 谢谢,我不知道 awk。会检查出来。看起来对其他事情也很有用。
    • 是的。此外,与正则表达式解决方案相比,它的速度非常快
    【解决方案3】:

    您可以使用 groupby,对空格进行分组:

    from itertools import groupby
    
    with open("test.txt") as f:
        print("".join(["".join(v) if k else " ".join(map(str.strip, v))+"\n"  for k, v in groupby(f, str.isspace)]))
    

    这会给你:

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus porta dui quis aliquet interdum. Sed in pellentesque libero. Quisque tempus nisl nec nisl condimentum ullamcorper.
    
    Mauris vulputate nibh nec ipsum mattis rutrum. Nunc nec tristique magna, non sagittis lacus. Aliquam id urna lectus.
    
    Maecenas volutpat libero quis erat mollis, et aliquet purus dignissim. Sed faucibus, lectus in auctor ornare, dolor libero ultrices sem, vel iaculis ex nulla quis lacus.
    

    【讨论】:

    • 谢谢,我不知道 groupby,尽管它的执行速度比 split/replace/join 慢得多。
    • @memo,groupy 发生在 c 级别,因此非常高效。
    【解决方案4】:

    我会尝试在 python 中遵循正则表达式:

    假设text 变量包含您的示例文本

    import re
    p = re.compile(ur'(?<!^)\n(?=\S)', re.MULTILINE)
    
    result = re.sub(p, u" ", text)
    print(result)
    

    它将打印以下文本,用空格替换单个换行符。

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus porta dui quis aliquet interdum. Sed in pellentesque libero. Quisque tempus nisl nec nisl condimentum ullamcorper.
    
    Mauris vulputate nibh nec ipsum mattis rutrum. Nunc nec tristique magna, non sagittis lacus. Aliquam id urna lectus.
    
    Maecenas volutpat libero quis erat mollis, et aliquet purus dignissim. Sed faucibus, lectus in auctor ornare, dolor libero ultrices sem, vel iaculis ex nulla quis lacus.
    

    regex101查看演示

    【讨论】:

      【解决方案5】:

      有时可以通过将函数作为第二个参数传递给re.sub() 来完成复杂的替换。

      import re
      
      ipsum = '''Lorem ipsum dolor sit amet, consectetur adipiscing
      elit. Vivamus porta dui quis aliquet interdum. Sed
      in pellentesque libero. Quisque tempus nisl nec
      nisl condimentum ullamcorper.
      
      Mauris vulputate nibh nec ipsum mattis rutrum.
      Nunc nec tristique magna, non sagittis lacus.
      Aliquam id urna lectus.
      
      Maecenas volutpat libero quis erat mollis, et
      aliquet purus dignissim. Sed faucibus, lectus in
      auctor ornare, dolor libero ultrices sem, vel
      iaculis ex nulla quis lacus.
      '''
      
      ipsum = re.sub(
          r'(\n+)(?=.)',
          lambda m: ' ' if len(m.group(1))==1 else m.group(1),
          ipsum)
      
      print ipsum
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-06-27
        • 2016-08-08
        • 2023-03-29
        • 1970-01-01
        • 1970-01-01
        • 2018-11-06
        • 2022-01-22
        • 2013-01-10
        相关资源
        最近更新 更多