Разбор файла Python: сборка дерева из текстового файла - PullRequest
15 голосов
/ 20 мая 2011

У меня есть текстовый файл с отступом, который будет использоваться для построения дерева.Каждая строка представляет узел, а отступы представляют глубину, а также узел, дочерним узлом которого является текущий.

Например, файл может выглядеть как

ROOT
   Node1
      Node2
         Node3
            Node4
   Node5
   Node6

, что указывает на то, что 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, когда я возвращаюсь после каждого рекурсивного вызова.

Ответы [ 3 ]

17 голосов
/ 22 мая 2011

Если вы не настаиваете на рекурсии, это тоже работает:

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']
13 голосов
/ 20 мая 2011

Большая проблема - это «взгляд вперед», который, как мне кажется, вызвал уродство, о котором идет речь. Его можно немного укоротить:

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)

Поскольку мы говорим о рекурсии, я старался избегать любых глобальных переменных (source и last_line). Было бы более питонно включать их в какой-либо объект парсера.

8 голосов
/ 20 мая 2011

Я бы вообще не использовал рекурсию для чего-то подобного (Ладно, возможно, я бы сделал это, если бы кодировал это на языке вроде 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:
        rec(child, depth + 1)

print_tree(tree)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...