【问题标题】:How can I convert a text-file outline list into a recursive collection of objects?如何将文本文件大纲列表转换为对象的递归集合?
【发布时间】:2011-01-20 03:53:58
【问题描述】:

如何将此文本文件内容转换为对象的递归集合,以便绑定到TreeView?即我想以 3 个对象 的集合结束,第一个名为 countries 的对象包含三个子对象的集合:france德国意大利等等...

回答:感谢所有为此提供帮助的人,这是我成功将此文本大纲解析为 XAML 树的代码:http://tanguay.info/web/index.php?pg=codeExamples&id=358

countries
-france
--paris
--bordeaux
-germany
-italy
subjects
-math
--algebra
--calculus
-science
--chemistry
--biology
other
-this
-that

下面的代码是据我所知,但它没有正确处理父母的多个孩子。

using System;
using System.Collections.Generic;
using System.Text;

namespace TestRecursive2342
{
    class Program
    {
        static void Main(string[] args)
        {
            List<OutlineObject> outlineObjects = new List<OutlineObject>();

            //convert file contents to object collection
            List<string> lines = Helpers.GetFileAsLines();
            Stack<OutlineObject> stack = new Stack<OutlineObject>();
            foreach (var line in lines)
            {
                OutlineObject oo = new OutlineObject(line);

                if (stack.Count > 0)
                {
                    OutlineObject topObject = stack.Peek();
                    if (topObject.Indent < oo.Indent)
                    {
                        topObject.OutlineObjects.Add(oo);
                        stack.Push(oo);
                    }
                    else
                    {
                        stack.Pop();
                        stack.Push(oo);                        
                    }

                }
                else
                {
                    stack.Push(oo);
                }

                if(oo.Indent == 0)
                    outlineObjects.Add(oo);
            }

            outlineObjects.ForEach(oo => Console.WriteLine(oo.Line));

            Console.ReadLine();
        }
    }

    public class OutlineObject
    {
        public List<OutlineObject> OutlineObjects { get; set; }
        public string Line { get; set; }
        public int Indent { get; set; }

        public OutlineObject(string rawLine)
        {
            OutlineObjects = new List<OutlineObject>();
            Indent = rawLine.CountPrecedingDashes();
            Line = rawLine.Trim(new char[] { '-', ' ', '\t' });
        }
    }

    public static class Helpers
    {
        public static List<string> GetFileAsLines()
        {
            return new List<string> {
                "countries",
                "-france",
                "--paris",
                "--bordeaux",
                "-germany",
                "-italy",
                "subjects",
                "-math",
                "--algebra",
                "--calculus",
                "-science",
                "--chemistry",
                "--biology",
                "other",
                "-this",
                "-that"};
        }

        public static int CountPrecedingDashes(this string line)
        {
            int tabs = 0;
            StringBuilder sb = new StringBuilder();
            foreach (var c in line)
            {
                if (c == '-')
                    tabs++;
                else
                    break;
            }
            return tabs;
        }
    }
}

【问题讨论】:

    标签: c# recursion treeview design-patterns


    【解决方案1】:

    多么棒的解决方案!这可以成为一个方便的小实用程序。很完美。

    我知道你已经有一段时间没有发布这个了;我无法找到原件,但我找到了存档 here 的副本。

    为了简洁起见,我对其进行了一些修改,并将其翻译成 VB.NET 以供可能感兴趣的人使用。

    这是最终结果:

    主要

    Module Main
      Sub Main()
    
        With New Test
          .Render()
        End With
    
        Console.WriteLine()
        Console.Write("Press any key to exit...")
        Console.ReadKey()
      End Sub
    End Module
    

    测试

    Public Class Test
      Private ReadOnly Tree As Tree
    
      Public Sub New()
        Me.Tree = New Tree(Me.Text, "-", 1)
      End Sub
    
    
    
      Public Sub Render()
        Me.Render(Me.Tree.Nodes)
      End Sub
    
    
    
      Public Sub Render(Nodes As List(Of Node))
        Nodes.ForEach(Sub(Node As Node)
                        Console.WriteLine("{0}{1}", Space(Node.Level), Node.Text)
    
                        Me.Render(Node.Nodes)
                      End Sub)
      End Sub
    
    
    
      Private ReadOnly Property Text As String
        Get
          Return _
            "TEST DATA" & vbCrLf &
            "countries" & vbCrLf &
            "-france" & vbCrLf &
            "--paris" & vbCrLf &
            "--bordeaux" & vbCrLf &
            "-germany" & vbCrLf &
            "--hamburg" & vbCrLf &
            "--berlin" & vbCrLf &
            "--hannover" & vbCrLf &
            "--munich" & vbCrLf &
            "-italy" & vbCrLf &
            "subjects" & vbCrLf &
            "-math" & vbCrLf &
            "--algebra" & vbCrLf &
            "--calculus" & vbCrLf &
            "-science" & vbCrLf &
            "--chemistry" & vbCrLf &
            "--biology" & vbCrLf &
            "other" & vbCrLf &
            "-this" & vbCrLf &
            "-that"
        End Get
      End Property
    End Class
    

    Public Class Tree
      Private Level As Integer
    
      Public Sub New(Text As String, LevelIndicator As String)
        Me.New(Text, LevelIndicator, 0)
      End Sub
    
    
    
      Public Sub New(Text As String, LevelIndicator As String, StartingIndex As Integer)
        Me.Load(Text, LevelIndicator, StartingIndex)
      End Sub
    
    
    
      Public ReadOnly Property Nodes As List(Of Node)
        Get
          Return _Nodes
        End Get
      End Property
      Private ReadOnly _Nodes As New List(Of Node)
    
    
    
      Private Sub Load(Text As String, LevelIndicator As String, StartingIndex As Integer)
        Dim iLevel As Integer
        Dim oParents As Stack(Of Node)
        Dim oNode As Node
    
        oParents = New Stack(Of Node)
    
        Text.ToLines(StartingIndex).ForEach(Sub(Line As String)
                                              oNode = New Node(Line, LevelIndicator)
    
                                              If oNode.Level = 0 Then ' Root '
                                                Me.Nodes.Add(oNode)
                                                oParents.Push(oNode)
                                                Me.Level = 0
    
                                              ElseIf oNode.Level - Me.Level > 1 Then ' Skipped generation(s) '
                                                Throw New FormatException("The outline structure is invalid.")
    
                                              ElseIf oNode.Level = Me.Level Then ' Sibling '
                                                oParents.Pop()
                                                Me.Level = oParents.SetNode(oNode, Me.Level)
    
                                              ElseIf oNode.Level - Me.Level = 1 Then ' Child '
                                                Me.Level = oParents.SetNode(oNode, Me.Level + 1)
    
                                              ElseIf oNode.Level < Me.Level Then ' Walk back up the stack '
                                                For iLevel = 0 To Me.Level - oNode.Level
                                                  oParents.Pop()
                                                Next
    
                                                Me.Level = oParents.SetNode(oNode, oNode.Level)
    
                                              End If
                                            End Sub)
      End Sub
    End Class
    

    节点

    Public Class Node
      Public Sub New(Line As String, LevelIndicator As String)
        _Level = Line.PrefixCount(LevelIndicator)
        _Text = Line.StripPrefix(LevelIndicator)
      End Sub
    
    
    
      Public ReadOnly Property Nodes As List(Of Node)
        Get
          Return _Nodes
        End Get
      End Property
      Private ReadOnly _Nodes As New List(Of Node)
    
    
    
      Public ReadOnly Property Level As Integer
        Get
          Return _Level
        End Get
      End Property
      Private ReadOnly _Level As Integer
    
    
    
      Public ReadOnly Property Text As String
        Get
          Return _Text
        End Get
      End Property
      Private ReadOnly _Text As String
    End Class
    

    扩展

    Public Module Extensions
      <Extension>
      Public Function PrefixCount(Text As String, Prefix As String) As Integer
        Dim iIndex As Integer
    
        PrefixCount = 0
    
        Do While Text.StartsWith(Prefix)
          iIndex = Text.IndexOf(Prefix)
    
          If iIndex = -1 Then
            Exit Do
          Else
            Text = Text.Substring(iIndex + Prefix.Length)
            PrefixCount += 1
          End If
        Loop
      End Function
    
    
    
      <Extension>
      Public Function StripPrefix(Text As String, Prefix As String) As String
        StripPrefix = Text
    
        Do While StripPrefix.StartsWith(Prefix)
          StripPrefix = StripPrefix.Substring(Prefix.Length)
        Loop
      End Function
    
    
    
      <Extension>
      Public Function ToLines(Text As String, StartingIndex As Integer) As List(Of String)
        Return Split(Text, vbCrLf).Where(Function(Line As String)
                                           Return Line.IsNullOrWhiteSpace = False
                                         End Function).Skip(StartingIndex).ToList
      End Function
    
    
    
      <Extension>
      Public Function SetNode(Parents As Stack(Of Node), Node As Node, Level As Integer) As Integer
        Parents.Peek.Nodes.Add(Node)
        Parents.Push(Node)
    
        Return Level
      End Function
    
    
    
      <Extension>
      Public Function ToFormat(Template As String, ParamArray Values As Object()) As String
        Return String.Format(Template, Values)
      End Function
    End Module
    

    【讨论】:

      【解决方案2】:

      这是我的尝试,它结合了您最初的努力和 diamandiev 的方法。我还添加了一个递归的 Output() 方法,它可以有效地重现原始输入文件。

      很遗憾,我无法完全理解堆栈方法,但有兴趣查看一个可行的示例。

      请注意,这只允许您给定的节点示例嵌套 3 层深。除此之外,还需要修改else if ((oo.Indent - lastItem.Indent) &lt; 0) 检查。

      using System;
      using System.Collections.Generic;
      using System.Text;
      
      namespace TestRecursive2342
      {
          class Program
          {
              static void Main(string[] args)
              {
                  List<OutlineObject> outlineObjects = new List<OutlineObject>();
      
                  //convert file contents to object collection 
                  List<string> lines = Helpers.GetFileAsLines();
      
                  OutlineObject lastItem = new OutlineObject();
                  bool processOk = true;
      
                  foreach (var line in lines)
                  {
                      OutlineObject oo = new OutlineObject(line);
      
                      if (lastItem.Indent != -1)
                      {
                          if (oo.Indent == 0 && lastItem.Indent != 0)
                          {
                              // we've got a new root node item, so add the last item's top level parent to the list
                              while (lastItem.parent != null)
                                  lastItem = lastItem.parent;
      
                              outlineObjects.Add(lastItem);
                          }
                          else if ((oo.Indent - lastItem.Indent) == 1)
                          {
                              // new item is one level lower than the last item
                              oo.parent = lastItem;
                              lastItem.OutlineObjects.Add(oo);
                          }
                          else if (oo.Indent == lastItem.Indent)
                          {
                              // new item is at the same level as the last item
                              oo.parent = lastItem.parent;
                              lastItem.parent.OutlineObjects.Add(oo);
                          }
                          else if ((oo.Indent - lastItem.Indent) < 0)
                          {
                              // new item is above the last item, but not a root node
                              // NB: this only allows for an item to be two levels above the last item
                              oo.parent = lastItem.parent.parent;
                              lastItem.parent.parent.OutlineObjects.Add(oo);
                          }
                          else if ((oo.Indent - lastItem.Indent) > 1)
                          {
                              // missing node check
                              Console.WriteLine("ERROR: missing node in input file between \"{0}\" and \"{1}\"", lastItem.Line, oo.Line);
                              processOk = false;
                              break;
                          }
                      }
      
                      lastItem = oo;
                  }
      
                  if (processOk)
                  {
                      // flush the last item
                      while (lastItem.parent != null)
                          lastItem = lastItem.parent;
      
                      outlineObjects.Add(lastItem);
      
                      outlineObjects.ForEach(oo => oo.Output());
                  }
      
                  Console.ReadLine();
              }
          }
      
          public class OutlineObject
          {
              public OutlineObject parent { get; set; }
              public List<OutlineObject> OutlineObjects { get; set; }
      
              public string Line { get; set; }
              public int Indent { get; set; }
      
              public void Output()
              {
                  StringBuilder sb = new StringBuilder();
                  sb.Append('-', this.Indent);
                  sb.Append(this.Line);
      
                  Console.WriteLine(sb);
      
                  foreach (OutlineObject oChild in this.OutlineObjects)
                  {
                      oChild.Output();
                  }
              }
      
              public OutlineObject()
              {
                  parent = null;
                  OutlineObjects = new List<OutlineObject>();
                  Line = "";
                  Indent = -1;
              }
      
              public OutlineObject(string rawLine)
              {
                  OutlineObjects = new List<OutlineObject>();
                  Indent = rawLine.CountPrecedingDashes();
                  Line = rawLine.Trim(new char[] { '-', ' ', '\t' });
              }
          }
      
          public static class Helpers
          {
              public static List<string> GetFileAsLines()
              {
                  return new List<string> { 
                      "countries", 
                      "-france", 
                      "--paris", 
                      "--bordeaux", 
                      "-germany", 
                      "-italy", 
                      "subjects", 
                      "-math", 
                      "--algebra", 
                      "--calculus", 
                      "-science", 
                      "--chemistry", 
                      "--biology", 
                      "other", 
                      "-this", 
                      "-that"};
              }
      
              public static int CountPrecedingDashes(this string line)
              {
                  int tabs = 0;
      
                  foreach (var c in line)
                  {
                      if (c == '-')
                          tabs++;
                      else
                          break;
                  }
                  return tabs;
              }
          }
      }
      

      【讨论】:

        【解决方案3】:

        你应该让你的OutlineObject 包含一个子OutlineObjects 的列表。这样您就可以在树视图中绑定到子集合。

        here 为例。或here


        对于解析,您应该维护嵌套对象的Stack&lt;OutlineObject&gt;。 当您阅读下一个OutlineObject 时,请查看堆栈中最后一个OutlineObject 的深度。如果您的级别更高,则将自己添加为该OutlineObject 的子级,并将您的OutlineObject 推入堆栈。如果您的级别相同,则删除该 OutlineObject 并改为推送您的对象。如果您的级别更高,则删除顶部堆栈OutlineObject,然后重复检查。


        关于您要添加的更改

        if (topObject.Indent < oo.Indent) 
        { 
          topObject.OutlineObjects.Add(oo); 
          stack.Push(oo); 
        } 
        else 
        { 
          stack.Pop(); 
          stack.Push(oo); 
        }
        

        ...当新对象的级别小于堆栈顶部的级别时,此代码不会检查这种情况。你需要:

        ...
        else if (topObject.Indent == oo.Indent) 
        { 
          stack.Pop(); 
          stack.Push(oo); 
        } 
        else 
        { 
          while (stack.Top().Indent >= oo.Indent) 
            stack.Pop(); 
          stack.Push(oo); 
        }
        

        【讨论】:

        • yes OutlineObject 有 "public List OutlineObjects { get; set; }" 我已经手动构建了一组递归 OutlineObjects 并将它们成功绑定到 TreeView,但是我想要现在要做的是将文本列表转换为该递归集合。
        • 太棒了,这让我走得更远,我在上面发布了我的代码,它现在有三个根孩子,但由于某种原因,更深的孩子/父母关系不正确,会继续努力的,谢谢。
        • 考虑跟踪添加的最后一个节点。然后,您可以将其与当前节点进行比较,以查看新节点是否是另一个子子节点。如果您愿意,您可以重复使用堆栈来执行此跟踪。
        • 您的代码不会检查新对象的级别小于堆栈顶部级别的情况。
        • 你有:if (topObject.Indent &lt; oo.Indent) { topObject.OutlineObjects.Add(oo); stack.Push(oo); } else { Pop(); stack.Push(oo); }。但是你需要... else if if (topObject.Indent == oo.Indent) { stack.Pop(); stack.Push(oo); } else { while (stack.Top().Indent &gt;= oo.Indent) stack.Pop(); stack.Push(oo); }
        【解决方案4】:
        public class Item
        {
            public string Name;
            public Item Parent;
        }
        
        List<Item> Collection = new List<Item>();
        
        public void Main()
        {
            var DataSource = data.InnerText;
        
            StreamReader Reader = new StreamReader(MapPath("_test2.txt"));
            int LastLevel = 0;
        
            while (Reader.EndOfStream == false) {
                var line = Reader.ReadLine();
                var Level = line.Where((System.Object c) => c == "-").Count;
                Item LastItem = default(Item);
        
                if (Collection.Count != 0) {
                    LastItem = Collection.Last();
                }
        
                if (Level == 0) {
                    Collection.Add(new Item { Name = line });
                    LastLevel = 0;
                }
                else if (Level - LastLevel == 1) {
                    Collection.Add(new Item { Name = line, Parent = LastItem });
                    LastLevel += 1;
                }
                else if (Level == LastLevel) {
                    Collection.Add(new Item { Name = line, Parent = LastItem.Parent });
                }
                else if (Level < LastLevel) {
                    var LevelDiff = LastLevel - Level;
                    Item Parent = LastItem;
        
                    for (i = 0; i <= LevelDiff; i++) {
                        Parent = Parent.Parent;
                    }
        
                    LastLevel = Level;
                    Collection.Add(new Item { Name = line, Parent = Parent });
                }
            }
        
            Reader.Close();
        }
        

        这应该可以解决问题。我在您的文本文件上对其进行了测试。可能有一些错误。测试它并判断它是否有效。

        编辑:实际上经过进一步测试后发现这并没有按预期工作。您需要添加更多逻辑才能使其正常工作。我把它留给你。

        编辑:在对代码进行了更多测试之后,我得到了一个更好的版本。我仍然不能保证它在所有情况下都能正常工作。

        【讨论】:

        【解决方案5】:

        复合模式是我首先想到的......

        【讨论】:

          【解决方案6】:

          简单。

          创建一个 OutlineObject 对象列表,每个级别一个,这些对象将作为父对象。

          所以,算法:

          1. 从行创建对象
          2. 查找缩进级别(根对象为 0)
          3. 如果父级列表的元素个数小于level+1,则添加“null”元素直到足够(这意味着对于第一个根对象,添加“null”元素使其具有1个元素)
          4. 用您在 1 中创建的新对象替换该列表中的元素 #level。(由于列表是从 0 开始的,根对象将是第一个)
          5. 如果 level > 0,则将其作为子对象添加到 parents[level-1],如果 level == 0,则将其作为根对象添加

          这应该给你你的树结构。您需要在每个对象中保留一个子列表。

          另请注意,如果您希望上面的列表处理文件中的错误,则需要额外的错误检查,如下所示:

          root
          -child 1
          --child 2
          another root
          --child 3 (note that we skipped a level)
          

          在这种情况下,最后一个子节点将被添加为“child 1”的子节点,而不是“另一个根”的子节点。

          【讨论】:

          • 对,在我的代码中,我正在执行第 1 步和第 2 步,但在第 3 步中,“父母列表”是什么意思,我在每个对象中保留了一个子列表,您的意思是还要跟踪父母名单吗?
          • 不,在读取过程中,您只需保留每个级别的父级列表。每当您遇到新的根(级别 0)对象时,您都会替换该列表中的对象。每当您遇到一个新的 1 级对象时,您就使用该列表中的 0 级对象作为其父对象。
          猜你喜欢
          • 2015-01-07
          • 1970-01-01
          • 2016-01-11
          • 2015-10-20
          • 1970-01-01
          • 2021-01-28
          • 2019-11-20
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多