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

Posted

技术标签:

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

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

ParentChildAChildB

更复杂的例子:

ParentChildAChildCChildDChildBChildEChildF

构造树的语法是什么。

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

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

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

首选 C++。

非常感谢。

【问题讨论】:

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

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

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

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

例子:

ABCDEFGH  Stack:
^

ABCDEFGH  Stack: A // A is root
 ^

ABCDEFGH  Stack: A, B // B is child of A
   ^

ABCDEFGH  Stack: A, B, C // C is child of B
     ^

ABCDEFGH  Stack: A, B, // C has no child, C done
      ^

ABCDEFGH  Stack: A, B, D // D is child of B
        ^

ABCDEFGH  Stack: A, B, 
         ^

ABCDEFGH  Stack: A, B, E // E child of B
           ^

ABCDEFGH  Stack: A, B, 
            ^

ABCDEFGH  Stack: A, // B has no more children, B done
             ^

ABCDEFGH  Stack: A, F // F child of A
               ^

ABCDEFGH  Stack: A, F, G // G child of F
                 ^

ABCDEFGH  Stack: A, F, 
                  ^

ABCDEFGH  Stack: A, F, H
                    ^

ABCDEFGH  Stack: A, F, 
                     ^

ABCDEFGH  Stack: A, 
                      ^

ABCDEFGH  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 = "ParentChildAChildCChildDChildBChildEChildF";
     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

这是最简单的一个

nodenamechildnodes

这是复杂的

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

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

【讨论】:

以上是关于解析文本以创建树形数据结构的主要内容,如果未能解决你的问题,请参考以下文章

java解析xml的几种方式哪种最好?

Web中树形数据(层级关系数据)的实现—以行政区树为例

2 XML 以及XML解析

Java解析省市县树形结构工具类

如何解析 HAR 文件以提取文本内容?

爬虫基础篇-BeautifulSoup解析