解析文本以创建树数据结构

时间:2011-10-18 21:52:10

标签: c++ parsing data-structures tree

假设我正在从文件中读取一行:

{Parent{{ChildA}{ChildB}}}

更复杂的例子:

{Parent{{ChildA{ChildC}{ChildD}}{ChildB{ChildE}{ChildF}}}}

用于构造树的语法是什么。

{}括号内的任何名称都是一个节点,如果在该括号内还有其他节点(括号),那么这些节点就是子节点。

我能够使用计数器解析第一个特定示例,但只能找到节点的文本名称。我该如何解析这个,以便我可以确定哪些节点是彼此的子节点?我似乎无法围绕我将使用的代码。我有一种感觉,我会使用递归。

任何帮助或建议都将不胜感激。

首选C ++。

非常感谢。

6 个答案:

答案 0 :(得分:5)

您必须跟踪当前的嵌套。为此,您可以使用堆栈。

每次遇到{(后跟节点名称),您就知道这是新节点的开始。此新节点是当前节点的子节点。

每次遇到}时,您都知道当前节点已完成,这意味着您必须告诉程序当前节点现在已更改为当前节点的父节点。

示例:

{A{B{C}{D}{E}}{F{G}{H}}}  Stack:
^

{A{B{C}{D}{E}}{F{G}{H}}}  Stack: A // A is root
 ^

{A{B{C}{D}{E}}{F{G}{H}}}  Stack: A, B // B is child of A
   ^

{A{B{C}{D}{E}}{F{G}{H}}}  Stack: A, B, C // C is child of B
     ^

{A{B{C}{D}{E}}{F{G}{H}}}  Stack: A, B, // C has no child, C done
      ^

{A{B{C}{D}{E}}{F{G}{H}}}  Stack: A, B, D // D is child of B
        ^

{A{B{C}{D}{E}}{F{G}{H}}}  Stack: A, B, 
         ^

{A{B{C}{D}{E}}{F{G}{H}}}  Stack: A, B, E // E child of B
           ^

{A{B{C}{D}{E}}{F{G}{H}}}  Stack: A, B, 
            ^

{A{B{C}{D}{E}}{F{G}{H}}}  Stack: A, // B has no more children, B done
             ^

{A{B{C}{D}{E}}{F{G}{H}}}  Stack: A, F // F child of A
               ^

{A{B{C}{D}{E}}{F{G}{H}}}  Stack: A, F, G // G child of F
                 ^

{A{B{C}{D}{E}}{F{G}{H}}}  Stack: A, F, 
                  ^

{A{B{C}{D}{E}}{F{G}{H}}}  Stack: A, F, H
                    ^

{A{B{C}{D}{E}}{F{G}{H}}}  Stack: A, F, 
                     ^

{A{B{C}{D}{E}}{F{G}{H}}}  Stack: A, 
                      ^

{A{B{C}{D}{E}}{F{G}{H}}}  Stack: 
                       ^

DONE.

答案 1 :(得分:3)

如果是家庭作业,那么无论如何都不能使用答案来破坏乐趣:

使用Boost Spirit Qi的最小实现:

#include <boost/spirit/include/qi.hpp>
namespace qi = boost::spirit::qi;

typedef boost::make_recursive_variant<
    std::vector<boost::recursive_variant_>, 
    std::string>::type ast_t;

void dump(const ast_t&);

// adhoc parser rule:
static const qi::rule<std::string::iterator, ast_t()> node = 
    '{' >> *node >> '}' | +~qi::char_("{}");

int main()
{
     std::string input = "{Parent{{ChildA{ChildC}{ChildD}}{ChildB{ChildE}{ChildF}}}}";
     std::string::iterator f(input.begin()), l(input.end());

     ast_t tree;
     if (qi::parse(f, l, node, tree))
         dump(tree);
     else
         std::cerr << "Unparsed: " << std::string(f, l) << std::endl;
}

令人遗憾的是dump的实施几乎等同于代码量:)


它将打印:

{
    Parent
    {
        {
            ChildA
            {
                ChildC
            }
            {
                ChildD
            }
        }
        {
            ChildB
            {
                ChildE
            }
            {
                ChildF
            }
        }
    }
}

<子> 以下是dump(const ast_t&)

的定义
struct dump_visitor : boost::static_visitor<>
{
    dump_visitor(int indent=0) : _indent(indent) {}

    void operator()(const std::string& s) const { print(s); }

    template <class V>
        void operator()(const V& vec) const
    {
        print("{");
        for(typename V::const_iterator it=vec.begin(); it!=vec.end(); it++)
            boost::apply_visitor(dump_visitor(_indent+1), *it);
        print("}");
    }

  private:
    template <typename T> void print(const T& v) const 
      { std::cout << std::string(_indent*4, ' ') << v << std::endl; }
    int _indent;
};

void dump(const ast_t& tree)
{
    boost::apply_visitor(dump_visitor(), tree);
}

答案 2 :(得分:2)

由于它是家庭作业,我认为你必须手动实现解决方案,所以你可能想要在解析输入时使用Stack来保存数据。

每次看到{时,都会创建一个包含数据的新节点,然后将其推送到堆栈中。

每次看到}时,都会从堆栈中弹出最后一个节点,并将其添加到树形状中。

这种方法需要的另一件事是Node指针,我们称之为currentNode,这样我们就可以实现实际的层次结构。首先,currentNode将为空;第一次从堆栈弹出一个节点时,将该节点放入currentNode。否则,当您弹出一个值时,我们知道堆栈中的下一个节点都有子节点。

我会让你从那里开始使用它,但如果你需要更多,我会站在那里。

答案 3 :(得分:1)

你的语法相对简单。

根据您的示例,节点可以用两种不同的方式声明:

{nodename}

这是一个简单的

{nodename{childnodes}}

这是复杂的

现在把它变成一个更正式的语法我们首先写下组成部分:

"{" nodename "}"
"{" nodename "{" childnodes "}" "}"

然后我们可以定义语法(非终端大写)

Node :: =“{”Nodename“}”|          “{”Nodename“{”Childnodes“}”    Nodename :: =至少一个字母    Childnodes :: =一个或多个Node

转换成解析器的标准方法(假设您必须手动编写,因为它太小而你会正确地编写它)是编写一个可以解析每个非终端的方法(你看到的是什么) :== sign)的左边。

唯一棘手的问题是你必须编写Nodename函数来检查是否有“{”(在这种情况下节点有一个孩子)或“}”(在这种情况下它没有孩子) )节点名称结束后。

此外,我冒昧地不写下所有可能的ascii字符串作为Nodename。

答案 4 :(得分:0)

想象一下这就是这样的东西(尽管它与您正在阅读的文件是线性的,但只需尝试以这种方式进行可视化)

{
 Parent
  {
    {
     ChildA
       {
         ChildC
       }
       {
        ChildD
       } 
     }
     {
      ChildB
       {
         ChildE
       }
       {
         ChildF
       }
     }
   }
 }

所以现在更明显的是,每当你得到'{'你有一个孩子。所以,当你得到一个'{'产生一个孩子时,你会失明(递归但如果你读的线太长,那么我建议你去迭代),每当你遇到一个'}'移动一级时(到父母)。

我想你可能会有一个函数,用于向树中添加节点并将树向上移动一级。如果这就是你所拥有的,那么你需要做的就是将各个部分放在一起。

我希望这是有道理的。

答案 5 :(得分:0)

每次找到“{”然后将子项添加到父项时,每次找到“}”时,将当前树设置为父树。

    public class Tree 
    { 
       public Tree Parent { get; set; }
       public string Value { get; set; }
       public List<Tree> Children { get; set; }
    }

    public Tree Parsing()
    {
        string rawString = this._rawData;
        Tree parent = new Tree { Parent = null,Value = "",Children = new List<Tree>()};
        Tree child = parent;

         foreach (char c in rawString)
          {
             if (c == '{')
             {
                child = new Tree { Parent = child, Value = string.Empty, Children = new List<Tree>() };
                child.Parent.Children.Add(child);
             }
             else if (c == '}')
             {
               child = new Tree { Parent = child.Parent.Parent, Value = string.Empty, Children = new List<Tree>() };
               if (child.Parent != null)
               {
                   child.Parent.Children.Add(child);
                }
              }
             else
             {
                   child.Value += c;
             }
         }

          return parent;
 }

public void RenderTree(Tree tree, string level)
{
    if (tree.Value.Length > 0)
         Console.WriteLine(level + tree.Value);

    foreach (Tree t in tree.Children)
    {
        RenderTree(t, level + "  ");
    }
}