扩展树状数据结构

时间:2011-09-07 16:04:00

标签: python tree graph-theory

我试图使用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)

这些元组中的字符串然后表示从树开始到树叶结束的树的路径。

非常感谢使用正则表达式(或其他方法)完成此任务的任何帮助。

3 个答案:

答案 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正则表达式逆变器使用繁琐的生成器来做到这一点 - 希望一些简单的递归就足够了。)