【问题标题】:Parsing Text to Make a Tree Data Structure解析文本以创建树形数据结构
【发布时间】:2011-10-18 21:52:10
【问题描述】:

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

{Parent{{ChildA}{ChildB}}}

更复杂的例子:

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

构造树的语法是什么。

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

我可以使用计数器解析第一个特定示例,但只能找到节点的文本名称。我怎样才能解析这个,以便我可以确定哪些节点是彼此的孩子?我似乎无法将我的想法包裹在我将使用的代码上。我觉得我会使用递归。

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

首选 C++。

非常感谢。

【问题讨论】:

  • 这看起来像是一个简单的上下文无关文法,因此您可以使用任意数量的标准工具来为此创建词法分析器和解析器。
  • 怎么样?对不起,我对此完全陌生。
  • @LearningPython : 你是 C++ 新手,还是比较熟悉这门语言?
  • @ildjarn 我熟悉基础知识(我可以读入一个文件,什么都不能读),但我之前从未将任何东西解析成树形数据结构。我熟悉 Python 等其他语言的数据结构,但不熟悉这种递归或解析。
  • @Mooing Duck 是的,我不希望它为我完成,但任何能将我推向正确方向的建议将不胜感激。

标签: c++ parsing data-structures tree


【解决方案1】:

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

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

每次遇到},就知道当前节点已经结束了,也就是说你要告诉你的程序当前节点现在变成了当前节点的父节点。

例子:

{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.

【讨论】:

    【解决方案2】:

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

    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&amp;)的定义:

    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);
    }
    

    【讨论】:

      【解决方案3】:

      由于这是家庭作业,我假设您必须手动实施解决方案,因此您可能希望在解析输入时使用堆栈来保存数据。

      每次看到{ 时,您都会创建一个带有数据的新节点,并将其压入堆栈。

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

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

      我会让你从那里带着它跑,但如果你需要更多,我会站在一旁。

      【讨论】:

      • 谢谢 Vorapsak。快速问题:您将如何处理知道哪些节点是其他节点的子节点?
      • 更新帖子,提供更多有用信息。
      【解决方案4】:

      你的语法比较简单。

      根据您的示例,可以通过以下两种不同方式之一声明节点:

      {nodename}
      

      这是最简单的一个

      {nodename{childnodes}}
      

      这是复杂的

      现在为了将其转化为更正式的语法,我们首先编写组成部分:

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

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

      节点 ::= "{" 节点名 "}" | "{" 节点名 "{" 子节点 "}" 节点名 ::= 至少一个字母 子节点 ::= 一个或多个节点

      将 than 转换为解析器的标准方法(假设您必须手动编写它,因为它很小,所以您会这样做)是编写一个可以解析每个非终结符的方法(您在:== 符号的左侧)。

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

      我还冒昧地没有写下所有可能的 ascii 字符串作为节点名。

      【讨论】:

        【解决方案5】:

        想象它是这样的(即使它与您正在读取的文件是线性的,只需尝试以这种方式可视化它)

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

        所以现在更明显的是,每当您获得“{”时,您就有了一个孩子。所以盲目,每当你得到一个'{'时都会产生一个孩子(递归但如果你正在阅读的行太长,那么我建议你通过迭代进行)并且每当你遇到'}'时向上移动一个级别(给父母)。

        我想你应该有一个函数来向树中添加一个节点并将你的树向上移动一层。如果这就是你所拥有的,那么你需要做的就是把这些碎片放在一起。

        我希望这有点道理。

        【讨论】:

          【解决方案6】:

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

              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 + "  ");
              }
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-03-19
            • 2014-04-23
            • 1970-01-01
            • 2015-07-26
            • 2019-04-14
            • 1970-01-01
            相关资源
            最近更新 更多