【问题标题】:Expanding a tree-like data structure扩展树状数据结构
【发布时间】:2011-09-10 11:45:10
【问题描述】:

我正在尝试使用 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)

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

任何使用正则表达式(或其他方法)完成此任务的帮助将不胜感激。

【问题讨论】:

    标签: python tree graph-theory


    【解决方案1】:

    你不能用正则表达式来做到这一点,因为你必须处理嵌套结构。相反,您可以使用pyparsing'snestedExpr

    【讨论】:

    • 感谢您的建议。我来看看 pyparsing。
    • 这与 pyparsing wiki (pyparsing.wikispaces.com) 的示例页面中包含的正则表达式反转示例非常相似。
    【解决方案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]
    

    但是你将如何在没有逻辑的情况下“分配”所有子匹配?由于无论如何您都需要此逻辑,因此您不妨在真实的图形结构上执行它。您也可以对字符串本身执行此操作,但最好在可以处理上下文无关或上下文相关结构的解析器生成器的支持下执行此操作。

    【讨论】:

    • 感谢您的见解和建议。我曾考虑将字符串解析为更传统的图形表示,但不清楚这是否比我建议的路线更容易。不过,我会试一试。
    【解决方案3】:

    在发表我对 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 类型的复杂结构,它支持将解析的标记作为列表中的元素、字典中的项目或对象中的属性来访问。通过调用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__ 方法并返回结果对象。我们可以在 operatorPrecedence 中指定解析操作,方法是在每个操作符的描述符元组中添加解析操作作为第 4 个元素。

    这是二元运算符的基类:

    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 正则表达式逆变器使用一堆生成器来执行此操作 - 希望一些简单的递归就足够了。)

    【讨论】:

    • 谢谢你的建议,保罗。这可能使解析更复杂的拓扑树字符串更可行。
    猜你喜欢
    • 2011-12-23
    • 2018-03-03
    • 1970-01-01
    • 2017-01-10
    • 2011-07-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多