分裂逃脱的分隔符

时间:2015-09-22 02:12:01

标签: python regex serialization escaping delimiter

修改 重新提出问题以便更好地理解。

对于我正在使用霍夫曼压缩的项目,我需要序列化我的霍夫曼树。

以下文字

  

"买了乘坐巨型螺旋式水滑梯或跑步的门票   通过由明亮的胶合板制成的游戏迷宫。整个夏天   很久,笑声"

将产生一个霍夫曼树,其序列化将如下所示:

'N57|L23, |N34|N16|N8|N4|N2|L1,made|L1,long|N2|L1,bought' \
'|L1,summer|N4|N2|L1,painted|L1,from|N2|L1,|L1,sounds|N8|N4|N2|L1,play|' \
'L1,tickets|N2|L1,All|L1,down|N4|N2|L1,brightly|L1,spiraling|N2|L1,giant|' \
'L1,ride|N18|N8|N4|N2|L1,. |L1,plywood|N2|L1,laughingreplace|L1,water|N4|' \
'N2|L1,the|L1,to|N2|L1,of|L1,through|N10|N4|N2|L1,run|L1,or|L2,a|N6|N3|' \
'L1,slide|N2|L1,maze|L1,, |L3,'

注意:这是分隔树符号的正则表达式:

'(\W+)'

文本也可以是HTML并包含字符。

'|' and '\'

为了逃避它们,我改变了

'|' to '\|'
'\' to '\\'

分割数据时,我需要忽略转义字符,只删除管道。考虑到下一个输入,这将成为一个问题:

'replace( /(^|\s)client-nojs(\s|$)/, "$1client-js$2" );</script>'

这是序列化输出

'N19|N8|N4|N2|L1,)|L1," );</|N2|L1,script|L1,client|' \
'N4|N2|L1,2|L1,js|N2|L1,(\|L1,nojs|N11|N4|L2,s|N2|L1,replace|L1,>' \
'|N7|N3|L1,1client|N2|L1,$|L1,( /(^\|\|N4|N2|L1,\|$)/, "$|L1,|L2,-'

现在尝试将其拆分成为一个问题。我明白我需要删除前面有偶数斜线的管道。

['\\|', '|', '\\\\|', ...] Valid delimiters
['\|', '\\\|', ...] Invalid delimiters

当我的序列化字符串最后包含斜杠时会发生这种情况。

'N54, test\\' will turn into 'N54, test\\\\|N44 ...'

到目前为止,我已经达到了这个正则表达式

的程度
r'(?<!\\)(\\\\)*\|'

在它们之前捕获具有偶数斜线的管道。但是在使用时 re.split()我将永远有两个问题之一

  1. 斜杠将与管道一起移除。
  2. 斜杠将包含在列表中的单元格中。
  3. 两者都打破了我的反序列化。 我需要移除管道,而忽略斜线。

    如果可能的话,我想知道如何使用re.split()来做到这一点,尽管我开始认为它只能用re.findall()

    修改 澄清:拆分数据不应该有空字符串。

3 个答案:

答案 0 :(得分:4)

说明

  

我需要移除管道,而忽略斜杠。

     

如果可能的话,我想知道如何用re.split()来做到这一点   我开始认为只有re.findall()

才有可能

理论上不可能简单地使用re.split(),因为正如您所说,将发生以下任何一种情况: 编辑 (澄清之后) Patrick Maupin在他的answer中表现出色的方法。

理论上不可能匹配&#34; |&#34;使用正则表达式解决方案的分隔符,以便使用Python的标准re package拆分该字符。正如您所说,将发生以下任何一种情况:

  1. 斜杠将与管道一起移除。
  2. 斜杠将包含在列表中的单元格中。
  3. 原因是你需要向后断言以使奇数次转义失败,同时不消耗匹配的字符。但是,lookbehind assertions必须是python中的固定宽度(并且在大多数正则表达式中)。

    <强>备选方案:

    以下列表侧重于正则表达式解决方案,它们实际上可以匹配分隔符。它们基于使用不同的策略来生成树,或者使用不同的正则表达式进行解析。

    1. 使用后缀表示法转义:

      '|' to '|\'
      '\' to '\\'
      
    2. 使用不能成为符号一部分的分支分隔符(因此不需要对其进行转义)。

      symbol 1{next}symbol 2{next}...
      
    3. 调用允许resetting the match的正则表达式库(例如regex package by Matthew BarnettPCRE中的\K)。 Demo

    4. 导入regex package并使用正则表达式控制谓词(*SKIP)(*FAIL)(也在PCRE中实现)。 Demo
    5. .net中的代码(lookbehinds允许可变宽度子模式)。 Demo
    6. 在解析之前反转字符串,然后反转以规范化。 Demo
    7. 定义可能位于分隔符之前的最大反斜杠数。 Demo

      regex = r'(?<!(?<!\\)\\)(?<!(?<!\\)\\\\\\)(?<!(?<!\\)\\\\\\\\\\)(?<!(?<!\\)\\\\\\\\\\\\\\)[|]'
                  # Up to 8 preceding backslashes
      
    8. 解决方案1:

      • 假设没有空符号(令牌),或者可以忽略空符号。

      不是拆分,而是匹配每个令牌。这是匹配(或断言)前面的转义,同时将它们包含在python中作为令牌的一部分的唯一方法。

      <强>代码:

      regex = r'(?:[^|\\]+|\\.)+'
      data = '|1 \\|2 \\\\|3 \\\\\\|4 \\\\\\\\|5 \\\\\\|\\\\|6'
      result = re.findall(regex, data)
      
      print (result)
      

      这将匹配除|\以外的任何字符,并且它还会匹配后面的任何字符后面的反斜杠。

      <强>输出:

      ['1 \\|2 \\\\', '3 \\\\\\|4 \\\\\\\\', '5 \\\\\\|\\\\', '6']
      

      DEMO

      解决方案2:

      如果您还想包含空标记,则需要使用捕获组并循环每个匹配。这是为了保证如果最后一场比赛以&#34; |&#34;它将被视为一个空令牌。否则,将无法区分a|ba|b|

      <强>代码:

      import re
      
      regex = re.compile(r'((?:[^|\\]+|\\.)*)([|])?')
      data = '|1 \\|2 \\\\|3 \\\\\\|4 \\\\\\\\|5 \\\\\\|\\\\|6'
      result = []
      
      for m in regex.finditer(data):
          result.append(m.group(1))
          if (not m.group(2)):
              break
      
      print (result)
      

      <强>输出:

      ['', '1 \\|2 \\\\', '3 \\\\\\|4 \\\\\\\\', '5 \\\\\\|\\\\', '6']
      

      DEMO

      修改

      上述解决方案的重点是提供一个明确的例子,说明如何使用正则表达式解决这个问题。目标字符串和结果都不会被解析。然而,正如帕特里克·莫平(Patrick Maupin)在outstanding solution中所展示的那样,他们缺乏表现。这就是为什么我提供的另一种解决方案证明比使用split()大约30%faster。上述解决方案的主要问题是如何处理前导或尾随位置的空令牌。这可以通过一个小的技巧来解决。

      最终解决方案:

      为避免检查是否有空令牌,我们可以添加&#34; |&#34; data的分隔符。因此,我们可以使用findall()模式,在每个标记之前需要一个分隔符。

      <强>代码:

      import re
      
      # the delimiter must precede each token
      regex = r'[|]((?:[^|\\]|\\.)*)'
      data = '|1 \\|2 \\\\|3 \\\\\\|4 \\\\\\\\|5 \\\\\\|\\\\|6'
      
      # the data is prefixed with a '|' before it's passed to findall()
      result = re.findall( regex, '|' + data)
      
      print(result)
      

      <强>输出:

      ['', '1 \\|2 \\\\', '3 \\\\\\|4 \\\\\\\\', '5 \\\\\\|\\\\', '6']
      

      DEMO

答案 1 :(得分:2)

我写了一个折磨测试,创造并组合了几个小字符串 - 我认为它应该照顾大多数角落情况。

马里亚诺的finditer()答案以绚丽的色彩通过了这项测试。但是,在我的机器上,它比使用split()慢15%-20%。

但是,他有一个新的findall()解决方案,在将字符串传递给re之前修改字符串,这比此处显示的split()解决方案更快更简单。

请注意,最近澄清了OP在管道字符之间永远不会有任何空字符串,Mariano提出的原始findall()示例(不需要初始字符串修改)是最适合原始海报的。

Mariano的新findall()解决方案带有预先修改过的字符串可能最适合一般情况。 split()位居第二,但这是我关注的焦点,因为它是原始问题的焦点: - )

以下代码适用于Python 2和Python 3。

import re
import itertools
import time


def use_finditer(data):
    regex = re.compile(r'((?:[^|\\]+|\\.)*)([|])?')
    result = []

    for m in regex.finditer(data):
        result.append(m.group(1))
        if (not m.group(2)):
            break
    return result


def use_split(data):
    regex = re.compile(r'(?:\|)?((?:[^|\\]|\\.)*)')
    result = regex.split(data)
    start_delete = data.startswith('|') * 2 if data else 1
    del result[start_delete::2]
    return result


def check_split(split_func):
    values = '', '', '', ' ', ' ', '|', '|', '\\', '\\\\', 'abc', 'd|ef', 'ghi\\'
    values = [x.replace('\\', '\\\\').replace('|', '\\|') for x in values]
    stuff = [], []
    for i in range(1, 6):
        srclist = list(itertools.permutations(values, i))
        for src in srclist:
            src = tuple(src)
            dst = tuple(split_func('|'.join(src)))
            stuff[dst != src].append((src, dst))

    if not stuff[1]:
        print("Successfully executed %d splits" % len(stuff[0]))
        return

    print(len(stuff[0]), len(stuff[1]))

    stuff[1].sort(key=lambda x: (len(x), x))
    for x, y in stuff[1][:20]:
        z = '|'.join(x)
        print(x, repr(z), y)


def check_loop(func, count=20):
    start = time.time()
    for i in range(count):
        check_split(func)
    print('Execution time: %0.2f' % (time.time() - start))

print('\nUsing finditer')
check_loop(use_finditer)
print('\nUsing split')
check_loop(use_split)

答案 2 :(得分:1)

我对你要做的事情感到有些困惑。您只想分割一串由|字符分隔的序列化数据?

>>> import re
>>> data = '|1 \\|2 \\\\|3 \\\\\\|4 \\\\\\\\|5 \\\\\\|\\\\|6'
>>> re.split(r'\|', data)
['', '1 \\', '2 \\\\', '3 \\\\\\', '4 \\\\\\\\', '5 \\\\\\', '\\\\', '6']