【问题标题】:Parsing Indented Text File解析缩进文本文件
【发布时间】:2015-03-19 12:01:42
【问题描述】:

我遇到了一些问题,我需要将此文件解析为父子关系。链接是'--'所以'--'越多,这将表明它与下面的下一行之间的关系。

Item0
--Item1
----Property1
----Property2
----Item2
------Property1
------Property2
----Item3
----Item4
------Property1
------Property2
----Item5
--Item6
--Item7
----Property1
--End
End

我有这个类结构

public class Section
{
    public string text { get; set; }
    public List<Section> children { get; set; }
    public Section parent { get; set; }

    public Section(String text, Section parent)
    {
        this.text = text;
        this.children = new List<Section>();
        this.parent = parent;
    }

    public Section(String text)
    {
        this.text = text;
        this.children = new List<Section>();
        this.parent = null;
    }
}

我有这个递归循环结构

    public void ParseList(Section section, string line)
    {
        if (line.GetLeadingWhitespaceLength() > section.text.GetLeadingWhitespaceLength())
        {

        }
        if (line.GetLeadingWhitespaceLength() < section.text.GetLeadingWhitespaceLength())
        {

        }

        if (line.GetLeadingWhitespaceLength() == section.text.GetLeadingWhitespaceLength())
        {
            if (section.parent != null)
            {
                section.parent.children.Add(new Section(line));
            }
        }
    }

但我无法连接这些点。

【问题讨论】:

    标签: c# asp.net parsing text


    【解决方案1】:

    我意识到这是一个迟到的帖子,我提供的解决方案不是递归的,但这会从您的字符串中生成一个节点集合。为了使其递归,你需要的一切都应该在下面。

    要创建递归算法,您必须首先确定您的基本情况是什么,然后只需创建一个条件来覆盖所有可能的子句。

    在以下解决方案中,基本情况的一个示例是,字符串元素为 null 或为空,如果是,则返回结果。另一种选择是,前一个节点深度大于当前节点深度。如果是,则返回根节点并将当前节点分配为新根。根据您选择的解决方案将决定您如何获得最终结果。创建一个递归算法来完成这个任务可能是多余的,因为一个简单的循环和比较会让你得到相同的结果。无论您选择哪种方式,这都应该让您开始。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Threading.Tasks;
    
    namespace SampleTextParsing
    {
        class Program
        {
            /// <summary>
            /// String representing the hierarchy to be parsed into objects.
            /// </summary>
            static readonly string fileString = 
            @"Item0
            --Item1
            ----Property1
            ----Property2
            ----Item2
            ------Property1
            ------Property2
            ----Item3
            ----Item4
            ------Property1
            ------Property2
            ----Item5
            --Item6
            --Item7
            ----Property1
            --End
            End";
    
            static void Main(string[] args)
            {
                // Create a collection of nodes out of the string.
                Queue<BaseNode> nodes = Parse();
    
                // Display the results to the user.
                Console.WriteLine("Element string\r\n-------------------------------");
                Console.WriteLine(fileString.Replace(' ', '\r'));
    
                Console.WriteLine("\r\nTotals\r\n-------------------------------");
                DisplayTotals(nodes);
    
                Console.WriteLine("\r\nHierarchy\r\n-------------------------------");
                while (nodes.Count > 0)
                {
                    DisplayRelationships(nodes.Dequeue());
                }
    
                Console.ReadLine();
            }
    
            /// <summary>
            /// Parses the hierarchy string into a collection of objects.
            /// </summary>
            /// <returns>A collection of BaseNode objects</returns>
            static Queue<BaseNode> Parse()
            {
                BaseNode root = null;       // Keeps track of the top most parent (Eg. In this case, item0 or End
                BaseNode current = null;    // Keeps track of the node to compare against.
                BaseNode previous = null;   // Keeps track of the previously seen node for comparison.
                Queue<BaseNode> queue = new Queue<BaseNode>();    // Contains a queue of nodes to be returned as the result.
    
                // Split the string into it's elements by using the carriage return and line feed.  
                // You can add a white-space character as a third delimiter just in case neither of the other two exist in the string. (eg. Inline)
                string[] elements = fileString.Split(new char[] {'\n', '\r'}, StringSplitOptions.RemoveEmptyEntries);
    
                // Iterate through every string element and create a node object out of it, while setting it's parent relationship to the previous node.
                foreach (var element in elements)
                {
                    // Check if a root node has been determined (eg. top most parent).  If not, assign it as the root and set it as the current node.
                    if (root == null)
                    {
                        root = GetAsElementNode(element);
                        current = root;
                    }
                    // The root has already been determined and set as the current node.  So now we check to see what it's relationship is to the 
                    // previous node. (eg. Child to parent)
                    else
                    {
                        // Assign the current node as previous, so that we have something to compare against. (eg. Previous to Current)
                        previous = current;
    
                        // Create a node out of the string element.
                        current = GetAsElementNode(element);
    
                        // We use the depth (eg. integer representing how deep into the hierarchy we are, where 0 is the root, and 2 is the first child
                        // (This is determined by the number of dashes prefixing the element. eg. Item0 -> --Item1)) to determine the relationship. 
                        // First, lets check to see if the previous node is the parent of the current node.
                        if (current.Depth > previous.Depth)
                        {
                            // It is, so assign the previous node as being the parent of the current node.
                            current.Parent = previous;
                        }
                        // The previous node is not the parent, so now lets check to see if the previous node is a sibling of the current node. 
                        // (eg. Do they share the same parent?)
                        else if (current.Depth == previous.Depth)
                        {
                            // They do, so get the previous node's parent, and assign it as the current node's parent as well.
                            current.Parent = previous.Parent;
                        }
                        // The current node is not the parent (eg. lower hierarchy), nor is it the sibling (eg. same hierarchy) of the previous node.  
                        // So it must be higher in the hierarchy. (eg. It's depth is less than the previous node's depth.)
                        else
                        {
                            // So now we must determine what the previous sibling node was and assign it as the current node's parent temporarily
                            BaseNode previousSibling = queue.FirstOrDefault(sibling => sibling.Depth == current.Depth);
                            current.Parent = previousSibling;
    
                            // The only time that the pervious sibling should be null is if the sibling is a root node. (eg. Item0 or End)
                            if (previousSibling == null)
                            {
                                current.Parent = null;
                            }
                            // The previous sibling has a parent, so we will the parent of the current node to match it's sibling.
                            else
                            {
                                current.Parent = previousSibling.Parent;
                            }
                        }
                    }
    
                    // We now add the node to the queue that will be returned as the result.
                    queue.Enqueue(current);
                }
    
                return queue;
            }
    
            /// <summary>
            /// Simply outputs to console, the name of the node and it's relationship to the previous node if any.
            /// </summary>
            /// <param name="node">The node to output the name of.</param>
            private static void DisplayRelationships(BaseNode node)
            {
                string output = string.Empty;
                if (node.Parent == null)
                {
                    output = string.Format("{0} is a root node.", node.Name);
                }
                else
                {
                    output = string.Format("{0} is a child of {1}.", node.Name, node.Parent.Name);
                }
    
                Console.WriteLine(output);
            }
    
            /// <summary>
            /// Displays the total counts of each relationship.  The numbers appear slightly off because the clauses are not 
            /// taking into account that a root node has no parent but can have children.  So Item0 and End are excluded from the count
            /// but included in the root count.  The values are right otherwise.
            /// </summary>
            /// <param name="nodes">A queue of nodes to iterate through.</param>
            private static void DisplayTotals(Queue<BaseNode> nodes)
            {
                var totalRoot = nodes.Where(node => node.Parent == null).Count();
                var totalChildren = nodes.Where(node => node.Parent != null).Count();
                var totalChildless = nodes
                    .Where(node => node.Parent != null)
                    .Join(
                        nodes.Where(
                        node => (node.Parent != null)), 
                            parent => parent.Name, 
                            child => child.Parent.Name, 
                            (parent, child) => new { child })
                            .Count();
    
    
                Console.WriteLine("{0} root nodes.", totalRoot);
                Console.WriteLine("{0} child nodes.", totalChildren);
                Console.WriteLine("{0} nodes without children.", totalChildless);
                Console.WriteLine("{0} parent nodes.", totalChildren - totalChildless);
            }
    
            /// <summary>
            /// Creates a node object from it's string equivalent.
            /// </summary>
            /// <param name="element">The parsed string element from the hierarchy string.</param>
            /// <returns></returns>
            static BaseNode GetAsElementNode(string element)
            {
                // Use some regex to parse the starting portion of the string.  You can also use substring to accomplish the same thing.
                string elementName = Regex.Match(element, "[a-zA-Z0-9]+").Value;
                string link = Regex.Match(element, "-+").Value;
    
                // Return a new node with an element name and depth initialized.
                return new Node(elementName, link.Length);
            }
        }
    
        /// <summary>
        /// A node object which inherits from BaseNode.
        /// </summary>
        public class Node : BaseNode
        {
            public Node()
            {
            }
    
            /// <summary>
            /// Overloaded constructor which accepts a string element and the depth
            /// </summary>
            /// <param name="elementName">The element as a string</param>
            /// <param name="depth">The depth of the element determined by the number of dashes prefixing the element string.</param>
            public Node(string elementName, int depth)
                : base(elementName, depth)
            {
            }
        }
    
        /// <summary>
        /// A base node which implements the INode interface. 
        /// </summary>
        public abstract class BaseNode : INode
        {
            public string Name { get; set; }        // The name of the node parsed from the string element.
            public int Depth { get; set; }          // The depth in the hierarchy determined by the number of dashes (eg. Item0 -> --Item1)
            public BaseNode Parent { get; set; }    // The parent of this node.
    
            public BaseNode()
            {
            }
    
            /// <summary>
            /// Overloaded constructor which accepts a string element and the depth.
            /// </summary>
            /// <param name="elementName">The element as a string.</param>
            /// <param name="depth">The depth of the element determined by the number of dashes prefixing the element string.</param>
            public BaseNode(string elementName, int depth)
                : this()
            {
                this.Name = elementName;
                this.Depth = depth;
            }
        }
    
        /// <summary>
        /// The interface that is implemented by the BaseNode base class.
        /// (For this scenario, this is a bit overkill but I figured, if I'm going to propose a solution, to do it right!)
        /// </summary>
        public interface INode
        {
            string Name { get; set; }        // The name of the node parsed from the string element.
            int Depth { get; set; }          // The depth in the hierarchy determined by the number of dashes (eg. Item0 -> --Item1)
            BaseNode Parent { get; set; }    // The parent of this node.
        }
    }
    

    输出如下:

    Element string
    -------------------------------
    Item0
    --Item1
    ----Property1
    ----Property2
    ----Item2
    ------Property1
    ------Property2
    ----Item3
    ----Item4
    ------Property1
    ------Property2
    ----Item5
    --Item6
    --Item7
    ----Property1
    --End
    End
    
    Totals
    -------------------------------
    2 root nodes.
    15 child nodes.
    11 nodes without children.
    4 parent nodes.
    
    Hierarchy
    -------------------------------
    Item0 is a root node.
    Item1 is a child of Item0.
    Property1 is a child of Item1.
    Property2 is a child of Item1.
    Item2 is a child of Item1.
    Property1 is a child of Item2.
    Property2 is a child of Item2.
    Item3 is a child of Item1.
    Item4 is a child of Item1.
    Property1 is a child of Item4.
    Property2 is a child of Item4.
    Item5 is a child of Item1.
    Item6 is a child of Item0.
    Item7 is a child of Item0.
    Property1 is a child of Item7.
    End is a child of Item0.
    End is a root node.
    

    【讨论】:

      【解决方案2】:

      首先想到的是将其转换为更易于使用的 XML。我不认为递归在这里是有序的(我在你的ParseList 中看不到任何递归)。 我会按顺序阅读每一行,从开头元素&lt;Item0&gt; 开始,然后将其添加到堆栈中。 阅读下一行,如果它与堆栈顶部 previous line 具有相同数量的破折号,则关闭该元素,否则继续。当您确实找到相同数量的破折号时,弹出堆栈并关闭元素。 反正就是这样……

      【讨论】:

        猜你喜欢
        • 2014-03-11
        • 2013-10-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-11-20
        • 1970-01-01
        • 1970-01-01
        • 2011-03-22
        相关资源
        最近更新 更多