簡體   English   中英

在python中遞歸定義多分支嵌套樹

[英]defining multi branching nested tree in python recursively

我正在嘗試在Python中實現一些基本的遞歸結構,但沒有取得成功。 我有一棵以嵌套列表形式表示的樹,如下所示:

ex = ['A',
      ['A1',
       ['A11', 'tag'],
       ['A12', 'tag'],
       ['A13',
        ['A131', 'tag'],
        ['A132',
         ['A1321', 'tag'],
         ['A1322', 'tag']]]],
      ['A2', 'tag'],
      ['A3',
       ['A31',
        ['A311', 'tag'],
        ['A312', 'tag']],
       ['A32', 'tag'],
       ['A33',
        ['A331',
         ['A3311', 'tag'],
         ['A3312', 'tag']]],
       ['A34', 'tag'],
       ['A35',
        ['A351', 'tag'],
        ['A352',
         ['A3521', 'tag'],
         ['A3522', 'tag']]]],
      ['A4', 'tag']]

並且我定義了一個Node類,該類允許指定標簽'A', 'A1', ...並添加子代。 可以通過注意children不是列表來檢索終端節點。

class Node(object):
    def __init__(self, tag, parent=None, children=[]):
        self.tag = tag
        self.parent = parent
        self.children = children
        self.is_terminal = False if isinstance(children, list) else True

    def add_child(self, node):
        if not isinstance(node, Node):
            raise ValueError("Cannot append node of type: [%s]" % type(node))
        if self.is_terminal:
            raise ValueError("Cannot append node to terminal")
        else:
            self.children.append(node)

現在,我無法實現一種功能,該功能會將基於列表的樹遞歸轉換為基於節點的樹:

tree = Node(tag='A', 
            children=[Node(tag='A1',
                           children=[Node(tag='A11',
                                          children='tag'),
                                     Node(tag='A12',
                                          children='tag'),
                                     ...]),
                      ...])

到目前為止,這是我基於以下想法的嘗試:在嵌套列表中的每個位置,我們可能都有一個終端節點(在這種情況下,我們只是將其添加到根節點),或者是一個非終端節點,在這種情況下,我們提取了各自的根標簽並遞歸遍歷子級。 當列表為空時,我們將控制權返回給調用者。 我的感覺是編碼風格可能不是最適合Python,但我想更具體地了解我所缺少的內容。

def is_terminal(e):
    return len(e) == 2 and type(e[0]) == str and type(e[1]) == str

def from_list(lst, root):
    lst = list(lst) # avoid mutating input list
    if not lst:
        return
    for e in lst:
        if is_terminal(e):
            tag, children = e
            print "terminal", tag, "with root", root.tag
            root.add_child(Node(tag=tag, children=children, parent=root))
        else:
            e = list(e)
            tag, children = e.pop(0), e
            print "non terminal", tag, "with root", root.tag
            root = Node(tag=tag, parent=root)
            from_list(children, root=root)

它有很多問題。 例如,它放寬了對最高根'A'跟蹤-即A2A1作為根。 它還將樹擴展為具有16個子節點的節點,每個終端節點一個,然后進行無限遞歸。 我將不勝感激任何提示。

我終於找到了問題,結果部分是算法中的遺漏點,部分是對Python列表工作方式的誤解。

該算法無法跟蹤最高根,因為我沒有在else語句中添加子根。 此新版本解決了該問題。

else:
    e = list(e)
    tag, children = e.pop(0), e
    print "non terminal", tag, "with root", root.tag
    subroot = Node(tag=tag, parent=root)
    root.add_child(subroot) #
    from_list(children, root=subroot)

展平的問題實際上是我在Node類定義中使用[]作為默認參數的事實。 正如解釋在這里 ,默認的空列表正在創建只有第一個函數調用(或類實例化在這種情況下),而不是每次函數調用時。 因此,根的子項列表將被添加到所有子子項中(並因此產生扁平化效果),並且每次修改根子項列表時,所有子子項的子項列表都會被修改-因此無限遞歸。

事實證明,這更多的是Python陷阱,而不是算法定義問題。

為了記錄在案,代碼的完整更正版本:

class Node(object):
    def __init__(self, tag, parent=None, children=None):
        self.tag = tag
        self.parent = parent
        self.children = [] if not children else children
        self.is_terminal = False if isinstance(self.children, list) else True

    def add_child(self, node):
        if not isinstance(node, Node):
            raise ValueError("Cannot append node of type: [%s]" % type(node))
        if self.is_terminal:
            raise ValueError("Cannot append node to terminal")
        else:
            self.children.append(node)

def is_terminal(e):
    return len(e) == 2 and type(e[0]) == str and type(e[1]) == str

def from_list(lst, root):
    lst = list(lst)
    if not lst:
        return
    for e in lst:
        if is_terminal(e):
            tag, children = e
            print "terminal", tag, "with root", root.tag
            root.add_child(Node(tag=tag, children=children, parent=root))
        else:
            e = list(e)
            tag, children = e.pop(0), e
            print "non terminal", tag, "with root", root.tag
            newroot = Node(tag=tag, parent=root)
            root.add_child(newroot)
            from_list(children, root=newroot)

這是您的稱呼方式:

root = Node(tag=ex[0])
from_list(ex[1:], root=root)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM