拆分转义分隔符
splitting escaped delimiters
编辑:
改写问题以便更好地理解。
对于我正在使用霍夫曼压缩进行的项目,我需要序列化我的霍夫曼树。
以下文字
"bought tickets to ride down a giant spiraling water slide or run
through a play maze made from brightly painted plywood. All summer
long, the sounds of laughing"
将生成一个霍夫曼树,其序列化如下所示:
'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|$)/, "client-js" );</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()
才有可能
编辑
说明:分割后的数据不能有空串。
我有点不明白你想做什么。您只想拆分一串由 |
字符分隔的序列化数据?
>>> import re
>>> data = '|1 \|2 \\|3 \\\|4 \\\\|5 \\\|\\|6'
>>> re.split(r'\|', data)
['', '1 \', '2 \\', '3 \\\', '4 \\\\', '5 \\\', '\\', '6']
描述:
I need to remove the pipes, while ignoring the slashes.
If possible, I'd like to know how to do this with re.split() although
I'm starting to think it's only possible with re.findall()
单纯使用re.split()
理论上是不可能的,因为如你所说,会出现以下情况之一: EDIT (Patrick Maupin 在他的 中展示的出色方法后的澄清) 。
理论上不可能匹配“|
”定界符与纯正则表达式解决方案以便拆分在 Python 的标准 re package
那个角色上。如您所说,将出现以下任一情况:
- 斜杠将与管道一起删除。
- 斜线将包含在列表中它们自己的单元格中。
原因是您需要一个后向断言来使奇数次转义的匹配失败,同时不消耗匹配的字符。但是,lookbehind assertions 在 python 中必须是固定宽度的(在大多数正则表达式中)。
备选方案:
以下列表侧重于 纯 可以实际匹配定界符的正则表达式解决方案。它们基于使用不同的策略生成树,或使用不同的正则表达式风格进行解析。
使用后缀符号转义:
'|' to '|\'
'\' to '\'
使用不能成为符号一部分的分支定界符(因此不需要转义)。
symbol 1{next}symbol 2{next}...
调用允许 resetting the match (like \K
in regex package by Matthew Barnett or in PCRE). Demo.
的正则表达式库
- 导入 regex package and use regex control verbs
(*SKIP)(*FAIL)
(also implemented in PCRE). Demo。
- .net (lookbehinds allow variable-width subpatterns). Demo
中的代码
- 解析前反转字符串,解析后反转归一化。 Demo
定义分隔符前的反斜杠的最大数量。 Demo.
regex = r'(?<!(?<!\)\)(?<!(?<!\)\\\)(?<!(?<!\)\\\\\)(?<!(?<!\)\\\\\\\)[|]'
# Up to 8 preceding backslashes
解决方案 1:
- 假设没有空的symbols(tokens),或者空的symbols可以忽略。
不是拆分,而是匹配每个标记。这是匹配(或断言)前面的转义并将它们作为令牌的一部分包含在 python.
中的唯一方法
代码:
regex = r'(?:[^|\]+|\.)+'
data = '|1 \|2 \\|3 \\\|4 \\\\|5 \\\|\\|6'
result = re.findall(regex, data)
print (result)
这将匹配除 |
或 \
之外的任何字符,它还将匹配后跟任何字符的反斜杠。
输出:
['1 \|2 \\', '3 \\\|4 \\\\', '5 \\\|\\', '6']
解决方案 2:
如果您还想包含空标记,则需要使用捕获组并循环每个匹配项。这是为了保证如果最后一场比赛以“|
”结束,它将被视为空标记。否则就无法区分 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 在他的 , they lack on performance. That is why I am providing another solution that proves ~30% faster 中所展示的,而不是使用 split()
。上述解决方案中的主要问题是如何处理前导或尾随位置的空令牌。这可以通过 技巧 .
解决
最终解决方案:
为了避免检查是否有空标记,我们可以在 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']
我写了一个创建和组合几个小字符串的折磨测试——我认为它应该处理大多数极端情况。
Mariano 的 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)
编辑: 改写问题以便更好地理解。
对于我正在使用霍夫曼压缩进行的项目,我需要序列化我的霍夫曼树。
以下文字
"bought tickets to ride down a giant spiraling water slide or run through a play maze made from brightly painted plywood. All summer long, the sounds of laughing"
将生成一个霍夫曼树,其序列化如下所示:
'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|$)/, "client-js" );</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()
才有可能编辑 说明:分割后的数据不能有空串。
我有点不明白你想做什么。您只想拆分一串由 |
字符分隔的序列化数据?
>>> import re
>>> data = '|1 \|2 \\|3 \\\|4 \\\\|5 \\\|\\|6'
>>> re.split(r'\|', data)
['', '1 \', '2 \\', '3 \\\', '4 \\\\', '5 \\\', '\\', '6']
描述:
I need to remove the pipes, while ignoring the slashes.
If possible, I'd like to know how to do this with re.split() although I'm starting to think it's only possible with re.findall()
单纯使用 EDIT (Patrick Maupin 在他的 re.split()
理论上是不可能的,因为如你所说,会出现以下情况之一:
理论上不可能匹配“|
”定界符与纯正则表达式解决方案以便拆分在 Python 的标准 re package
那个角色上。如您所说,将出现以下任一情况:
- 斜杠将与管道一起删除。
- 斜线将包含在列表中它们自己的单元格中。
原因是您需要一个后向断言来使奇数次转义的匹配失败,同时不消耗匹配的字符。但是,lookbehind assertions 在 python 中必须是固定宽度的(在大多数正则表达式中)。
备选方案:
以下列表侧重于 纯 可以实际匹配定界符的正则表达式解决方案。它们基于使用不同的策略生成树,或使用不同的正则表达式风格进行解析。
使用后缀符号转义:
'|' to '|\' '\' to '\'
使用不能成为符号一部分的分支定界符(因此不需要转义)。
symbol 1{next}symbol 2{next}...
调用允许 resetting the match (like
\K
in regex package by Matthew Barnett or in PCRE). Demo. 的正则表达式库
- 导入 regex package and use regex control verbs
(*SKIP)(*FAIL)
(also implemented in PCRE). Demo。 - .net (lookbehinds allow variable-width subpatterns). Demo 中的代码
- 解析前反转字符串,解析后反转归一化。 Demo
定义分隔符前的反斜杠的最大数量。 Demo.
regex = r'(?<!(?<!\)\)(?<!(?<!\)\\\)(?<!(?<!\)\\\\\)(?<!(?<!\)\\\\\\\)[|]' # Up to 8 preceding backslashes
解决方案 1:
- 假设没有空的symbols(tokens),或者空的symbols可以忽略。
不是拆分,而是匹配每个标记。这是匹配(或断言)前面的转义并将它们作为令牌的一部分包含在 python.
中的唯一方法代码:
regex = r'(?:[^|\]+|\.)+'
data = '|1 \|2 \\|3 \\\|4 \\\\|5 \\\|\\|6'
result = re.findall(regex, data)
print (result)
这将匹配除 |
或 \
之外的任何字符,它还将匹配后跟任何字符的反斜杠。
输出:
['1 \|2 \\', '3 \\\|4 \\\\', '5 \\\|\\', '6']
解决方案 2:
如果您还想包含空标记,则需要使用捕获组并循环每个匹配项。这是为了保证如果最后一场比赛以“|
”结束,它将被视为空标记。否则就无法区分 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 在他的 split()
。上述解决方案中的主要问题是如何处理前导或尾随位置的空令牌。这可以通过 技巧 .
最终解决方案:
为了避免检查是否有空标记,我们可以在 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']
我写了一个创建和组合几个小字符串的折磨测试——我认为它应该处理大多数极端情况。
Mariano 的 finditer()
答案以优异的成绩通过了这个测试。但是,在我的机器上,它比使用 split()
.
但是,他有一个新的 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)