我试图使用Python来改变一些使用re模块的文本字符串(即re.sub)。但是,我认为我的问题适用于其他具有正则表达式实现的语言。
我有许多表示树状数据结构的字符串。他们看起来像这样:
(A,B)-C-D
A-B-(C,D)
A-(B,C,D-(E,F,G,H,I))
每个字母代表一个分支或边缘。括号中的字母表示进入或离开另一个分支的分支。
每个地方都有一个'普通'元组值(一个只用逗号分隔的单个字母组成的元组),我想取该元组的前缀(X-)或后缀(-X)并将其应用于每个元组元组中的值。
在此转换下,上述字符串将变为
(A-C,B-C)-D
A-(B-C,B-D)
A-(B,C,(D-E,D-F,D-G,D-H,D-I))
重复应用该方法最终会产生
(A-C-D,B-C-D)
(A-B-C,A-B-D)
(A-B,A-C,A-D-E,A-D-F,A-D-G,A-D-H,A-D-I)
这些元组中的字符串然后表示从树开始到树叶结束的树的路径。
非常感谢使用正则表达式(或其他方法)完成此任务的任何帮助。
答案 0 :(得分:3)
您无法使用正则表达式执行此操作,因为您必须处理嵌套结构。相反,您可以使用pyparsing's nestedExpr
答案 1 :(得分:2)
您要描述的问题是在图表中枚举路径。
您描述了三个图表
A B
\ /
C
|
D
A
|
B
/ \
C D
A
/ | \
B C D
// | \\
E F G H I
并为每个想要枚举路径的人。这涉及在任意嵌套结构上分配值。如果这可以用正则表达式来完成,而且我不确定它是否可以,我相信,它必须在几次传递中完成。
我对你的问题的感觉是,最好通过将字符串解析为图形结构然后枚举路径来解决。如果您不想在物理上构建图形,则可以在用户提供的操作中为解析器生成器生成字符串。
基于正则表达式的解决方案必须知道如何处理这两个
(A,B)-C
和
(A,B,C,D,E,F,G,H)-I
您可以将这些字符串与
匹配\([A-Z](,[A-Z])*\)-[A-Z]
但如果没有一些逻辑,你会如何“分配”所有子匹配?既然你需要这个逻辑,你也可以在真正的图形结构上执行它。您也可以在字符串本身上执行此操作,但最好在解析器生成器的支持下执行此操作,该生成器可以处理无上下文或上下文相关的结构。
答案 2 :(得分:1)
在发表我的评论后,参考pyparsing的invRegex示例,我看了一下你的输入,看起来你可以把它解释为中缀表示法,用','和' - '作为二元运算符。 Pyparsing有一个名为operatorPrecedence
的帮助方法,它根据运算符的优先级解析表达式,并在括号中进行分组。 (这比使用nestedExpr
辅助方法更加智能,它与匹配分组符号的表达式匹配。)所以这是使用operatorPrecedence
的解析器的入门版本:
data = """\
(A,B)-C-D
A-B-(C,D)
A-(B,C,D-(E,F,G,H,I))""".splitlines()
from pyparsing import alphas, oneOf, operatorPrecedence, opAssoc
node = oneOf(list(alphas))
graphExpr = operatorPrecedence(node,
[
('-', 2, opAssoc.LEFT),
(',', 2, opAssoc.LEFT),
])
for d in data:
print graphExpr.parseString(d).asList()
Pyparsing实际上返回一个ParseResults类型的复杂结构,它支持将已解析的标记作为列表中的元素,dict中的项或对象中的属性进行访问。通过调用asList
,我们只需以简单的列表形式获取元素。
上述输出显示我们看起来正确:
[[['A', ',', 'B'], '-', 'C', '-', 'D']]
[['A', '-', 'B', '-', ['C', ',', 'D']]]
[['A', '-', ['B', ',', 'C', ',', ['D', '-', ['E', ',', 'F', ',', 'G', ',', 'H', ',', 'I']]]]]
Pyparsing还允许您将回调或parse actions
附加到单个表达式,以便在分析时调用。例如,此解析操作将分析时转换为整数:
def toInt(tokens):
return int(tokens[0])
integer = Word(nums).setParseAction(toInt)
当在ParseResults中返回值时,它已经被转换为整数。
也可以将类指定为解析操作,并将ParseResults对象传递给类的__init__
方法,并返回结果对象。我们可以通过在每个运算符的描述符元组中添加解析操作作为第4个元素来在operatorPrecedence中指定解析操作。
这是二元运算符的基类:
class BinOp(object):
def __init__(self, tokens):
self.tokens = tokens
def __str__(self):
return self.__class__.__name__ + str(self.tokens[0][::2])
__repr__ = __str__
从这个基类中,我们可以派生出2个子类,每个子类对应一个运算符-
和,
:
class Path(BinOp):
pass
class Branch(BinOp):
pass
并将它们添加到operatorPrecedence中的运算符定义元组:
node = oneOf(list(alphas))
graphExpr = operatorPrecedence(node,
[
('-', 2, opAssoc.LEFT, Path),
(',', 2, opAssoc.LEFT, Branch),
])
for d in data:
print graphExpr.parseString(d).asList()
这为我们提供了每个输入字符串的对象的嵌套结构:
[Path[Branch['A', 'B'], 'C', 'D']]
[Path['A', 'B', Branch['C', 'D']]]
[Path['A', Branch['B', 'C', Path['D', Branch['E', 'F', 'G', 'H', 'I']]]]]
从这个结构生成的路径留作OP的练习。 (pyparsing正则表达式逆变器使用繁琐的生成器来做到这一点 - 希望一些简单的递归就足够了。)