修改 重新提出问题以便更好地理解。
对于我正在使用霍夫曼压缩的项目,我需要序列化我的霍夫曼树。
以下文字
"买了乘坐巨型螺旋式水滑梯或跑步的门票 通过由明亮的胶合板制成的游戏迷宫。整个夏天 很久,笑声"
将产生一个霍夫曼树,其序列化将如下所示:
'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()我将永远有两个问题之一
两者都打破了我的反序列化。 我需要移除管道,而忽略斜线。
如果可能的话,我想知道如何使用re.split()来做到这一点,尽管我开始认为它只能用re.findall()
修改 澄清:拆分数据不应该有空字符串。
答案 0 :(得分:4)
我需要移除管道,而忽略斜杠。
如果可能的话,我想知道如何用re.split()来做到这一点 我开始认为只有re.findall()
才有可能
理论上不可能简单地使用 编辑 (澄清之后) Patrick Maupin在他的answer)中表现出色的方法。re.split()
,因为正如您所说,将发生以下任何一种情况:
理论上不可能匹配&#34; |
&#34;使用纯正则表达式解决方案的分隔符,以便使用Python的标准re package
拆分该字符。正如您所说,将发生以下任何一种情况:
原因是你需要向后断言以使奇数次转义失败,同时不消耗匹配的字符。但是,lookbehind assertions必须是python中的固定宽度(并且在大多数正则表达式中)。
<强>备选方案:强>
以下列表侧重于纯正则表达式解决方案,它们实际上可以匹配分隔符。它们基于使用不同的策略来生成树,或者使用不同的正则表达式进行解析。
使用后缀表示法转义:
'|' to '|\'
'\' to '\\'
使用不能成为符号一部分的分支分隔符(因此不需要对其进行转义)。
symbol 1{next}symbol 2{next}...
调用允许resetting the match的正则表达式库(例如regex package by Matthew Barnett或PCRE中的\K
)。 Demo
(*SKIP)(*FAIL)
(也在PCRE中实现)。 Demo。定义可能位于分隔符之前的最大反斜杠数。 Demo
regex = r'(?<!(?<!\\)\\)(?<!(?<!\\)\\\\\\)(?<!(?<!\\)\\\\\\\\\\)(?<!(?<!\\)\\\\\\\\\\\\\\)[|]'
# Up to 8 preceding backslashes
不是拆分,而是匹配每个令牌。这是匹配(或断言)前面的转义,同时将它们包含在python中作为令牌的一部分的唯一方法。
<强>代码:强>
regex = r'(?:[^|\\]+|\\.)+'
data = '|1 \\|2 \\\\|3 \\\\\\|4 \\\\\\\\|5 \\\\\\|\\\\|6'
result = re.findall(regex, data)
print (result)
这将匹配除|
或\
以外的任何字符,并且它还会匹配后面的任何字符后面的反斜杠。
<强>输出:强>
['1 \\|2 \\\\', '3 \\\\\\|4 \\\\\\\\', '5 \\\\\\|\\\\', '6']
如果您还想包含空标记,则需要使用捕获组并循环每个匹配。这是为了保证如果最后一场比赛以&#34; |
&#34;它将被视为一个空令牌。否则,将无法区分a|b
和a|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']
修改强>
上述解决方案的重点是提供一个明确的例子,说明如何使用正则表达式解决这个问题。目标字符串和结果都不会被解析。然而,正如帕特里克·莫平(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']
答案 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']