【问题标题】:Python file parsing: Build tree from text filePython文件解析:从文本文件构建树
【发布时间】:2011-08-29 21:04:35
【问题描述】:

我有一个用于构建树的缩进文本文件。每行代表一个节点,缩进代表深度以及当前节点是其子节点。

例如,一个文件可能看起来像

根 节点1 节点2 节点3 节点4 节点5 节点6

表示ROOT包含三个孩子:1、5、6,Node1有一个孩子:2,Node2有一个孩子:3,以此类推

我提出了一个递归算法并对其进行了编程并且它可以工作,但它有点难看,特别是非常粗略地处理上面的示例(从节点 4 到节点 5 时)

它使用“缩进计数”作为递归的基础,所以如果缩进数 = 当前深度 + 1,我会更深一层。但这意味着当我读到缩进较少的一行时,我必须一次返回一个级别,每次都检查深度。

这就是我所拥有的

def _recurse_tree(node, parent, depth):
    tabs = 0
    
    while node:
        tabs = node.count("\t")
        if tabs == depth:
            print "%s: %s" %(parent.strip(), node.strip())
        elif tabs == depth + 1:
            node = _recurse_tree(node, prev, depth+1)
            tabs = node.count("\t")
            
            #check if we have to surface some more
            if tabs == depth:
                print "%s: %s" %(parent.strip(), node.strip())
            else:
                return node
        else:
            return node
        
        prev = node
        node = inFile.readline().rstrip()
        
inFile = open("test.txt")
root = inFile.readline().rstrip()
node = inFile.readline().rstrip()
_recurse_tree(node, root, 1)

现在我只是打印出节点以验证父节点对于每一行是否正确,但也许有更简洁的方法来做到这一点?尤其是当我从每个递归调用返回时 elif 块中的情况。

【问题讨论】:

  • 您将不得不编写一堆代码来正确解析和验证。可以用xml吗?它的基础结构是一棵树。
  • 很遗憾,没有,因为这更像是一个递归练习。我认为这种问题会很常见。
  • 这可能是一个家庭作业问题吗?如果是这样,添加作业标签是礼仪。
  • 不,个人兴趣。好久没做递归了。
  • 如果是这样,这真的不是 Python 特定的。更多的是一般算法的思考。

标签: python algorithm recursion tree


【解决方案1】:

我根本不会对这样的事情使用递归(好吧,如果我用像 Scheme 这样的语言来编码,也许我会这样做,但这里是 Python)。递归非常适合遍历形状像树的数据,在这种情况下,与普通循环相比,它会大大简化您的设计。

但是,这里不是这种情况。您的数据确实代表一棵树,但它是按顺序格式化的,即它是一个简单的行序列。这样的数据最容易用一个简单的循环来处理,尽管如果你愿意的话,你可以通过将它分成三个不同的层来使设计更通用:顺序读取器(它将标签解析为深度级别的规范),树插入器(通过跟踪插入树中的最后一个节点,将节点插入到特定深度级别的树中)和树本身:

import re

# *** Tree representation ***
class Node(object):
    def __init__(self, title):
        self.title = title
        self.parent = None
        self.children = []

    def add(self, child):
        self.children.append(child)
        child.parent = self

# *** Node insertion logic ***
class Inserter(object):
    def __init__(self, node, depth = 0):
        self.node = node
        self.depth = depth

    def __call__(self, title, depth):
        newNode = Node(title)
        if (depth > self.depth):
            self.node.add(newNode)
            self.depth = depth
        elif (depth == self.depth):
            self.node.parent.add(newNode)
        else:
            parent = self.node.parent
            for i in xrange(0, self.depth - depth):
                parent = parent.parent
            parent.add(newNode)
            self.depth = depth

        self.node = newNode

# *** File iteration logic ***
with open(r'tree.txt', 'r') as f:    
    tree = Node(f.readline().rstrip('\n'))
    inserter = Inserter(tree)

    for line in f:
        line = line.rstrip('\n')
        # note there's a bug with your original tab parsing code:
        # it would count all tabs in the string, not just the ones
        # at the beginning
        tabs = re.match('\t*', line).group(0).count('\t')
        title = line[tabs:]
        inserter(title, tabs)

当我必须在将代码粘贴到此处之前对其进行测试时,我编写了一个非常简单的函数来将我读取的树漂亮地打印到内存中。对于这个函数,最自然的当然是使用递归,因为现在树确实是用树数据表示的:

def print_tree(node, depth = 0):
    print '%s%s' % ('  ' * depth, node.title)
    for child in node.children:
        print_tree(child, depth + 1)

print_tree(tree)

【讨论】:

    【解决方案2】:

    最大的问题是我认为导致问题丑陋的“前瞻”。可以稍微缩短:

    def _recurse_tree(parent, depth, source):
        last_line = source.readline().rstrip()
        while last_line:
            tabs = last_line.count('\t')
            if tabs < depth:
                break
            node = last_line.strip()
            if tabs >= depth:
                if parent is not None:
                    print "%s: %s" %(parent, node)
                last_line = _recurse_tree(node, tabs+1, source)
        return last_line
    
    inFile = open("test.txt")
    _recurse_tree(None, 0, inFile)
    

    由于我们在谈论递归,所以我尽量避免使用任何全局变量(sourcelast_line)。让它们成为某个解析器对象的成员会更加pythonic。

    【讨论】:

    • @martineau:你说得对,我的意思是在函数内部用source 替换inFile,现在解决了。
    • 在我看来,last_line 参数始终以None 的形式传入——所以它可能只是一个初始值为source.readline().rstrip() 的局部变量,在@987654329 之前设置@ 循环(并检查它被 None 删除)。
    • @martineau:再次正确,进行了相应的编辑。当我写它的时候,我正在修修补补,不确定每个递归/返回是否对应于移动到下一行输入。既然我提到这是一个“缩短”的版本,我想我最好把所有的空气都挤出来吧?
    【解决方案3】:

    如果你不坚持递归,这也可以:

    from itertools import takewhile
    
    is_tab = '\t'.__eq__
    
    def build_tree(lines):
        lines = iter(lines)
        stack = []
        for line in lines:
            indent = len(list(takewhile(is_tab, line)))
            stack[indent:] = [line.lstrip()]
            print stack
    
    source = '''ROOT
    \tNode1
    \t\tNode2
    \t\t\tNode3
    \t\t\t\tNode4
    \tNode5
    \tNode6'''
    
    build_tree(source.split('\n'))
    

    结果:

    ['ROOT']
    ['ROOT', 'Node1']
    ['ROOT', 'Node1', 'Node2']
    ['ROOT', 'Node1', 'Node2', 'Node3']
    ['ROOT', 'Node1', 'Node2', 'Node3', 'Node4']
    ['ROOT', 'Node5']
    ['ROOT', 'Node6']
    

    【讨论】:

    • 伟大的风格。尤其是is_tab 定义。
    • 没有takewhile(更快——我认为——更干净):for line in lines: body = line.lstrip('\t'); level = len(line) - len(body); stack[level:] = (body,)
    • 这太干净了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-10
    • 2014-04-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多