Как я могу преобразовать список структуры текстового файла в рекурсивную коллекцию объектов? - PullRequest
7 голосов
/ 23 февраля 2010

Как мне преобразовать этот текстовый файл содержимое в рекурсивную коллекцию объектов , которую я могу связать с TreeView ? то есть я хочу получить коллекцию из 3 объектов , первый из которых называется country , в котором есть коллекция из трех дочерних объектов: 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;
        }
    }
}

Ответы [ 6 ]

2 голосов
/ 23 февраля 2010
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();
}

Это должно сработать. Я проверил это на вашем текстовом файле. Там могут быть некоторые ошибки. Проверьте это и скажите, работает ли он.

РЕДАКТИРОВАТЬ: На самом деле после дальнейшего тестирования оказывается, что это не работает, как ожидалось. Вам нужно добавить больше логики, чтобы это работало. Я оставляю это тебе.

РЕДАКТИРОВАТЬ: После тестирования кода немного больше, я пришел к версии, которая работает лучше. Я до сих пор не могу гарантировать, что это будет работать при любых обстоятельствах.

1 голос
/ 23 февраля 2010

Вы должны включить в свой OutlineObject список дочерних OutlineObject с. Таким образом, вы можете привязать дочернюю коллекцию в древовидных представлениях.

Посмотрите здесь для примера. Или здесь .


Для разбора вы должны поддерживать Stack<OutlineObject> ваших вложенных объектов. Когда вы прочитаете следующее 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); 
}
0 голосов
/ 28 марта 2015

Какое отличное решение! Это может сделать для удобной маленькой утилиты. Это прекрасно.

Я знаю, что прошло много времени с тех пор, как вы опубликовали это; Я не смог найти оригинал, но нашел копию в архиве здесь .

Я немного изменил его для краткости и перевел на 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

Node

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
0 голосов
/ 24 февраля 2010

Вот моя попытка, которая представляет собой комбинацию ваших первоначальных усилий плюс подход Диамандиева. Я также добавил рекурсивный метод Output (), который будет эффективно воспроизводить исходный файл ввода.

К сожалению, я не смог понять подход стека, но мне было бы интересно увидеть рабочий пример.

Обратите внимание, что это допускает только данный пример узлов, вложенных на 3 уровня глубиной. Более того, потребуется модификация чека else if ((oo.Indent - lastItem.Indent) < 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;
        }
    }
}
0 голосов
/ 23 февраля 2010

Композитный шаблон - это первое, что приходит мне в голову ...

0 голосов
/ 23 февраля 2010

Simple.

Создайте список объектов OutlineObject, по одному на каждый уровень, которые будут служить родителями.

Итак, алгоритм:

  1. Создать объект из линии
  2. Найти уровень отступа (который будет 0 для корневых объектов)
  3. Если список родителей содержит менее уровня + 1 количество элементов, добавляйте «нулевые» элементы до тех пор, пока их не будет достаточно (это означает, что для первого корневого объекта добавьте «нулевой» элемент, чтобы в нем было 1 элемент)
  4. Заменить элемент #level в этом списке новым объектом, который вы создали в 1. (поскольку список основан на 0, корневые объекты будут первыми)
  5. Если уровень> 0, добавить его в качестве дочернего для родителей [уровень-1], если уровень == 0, добавить его в качестве корневого объекта

Это должно дать вам вашу древовидную структуру. Вам нужно будет хранить дочерний список в каждом объекте.

Также обратите внимание, что приведенный выше список потребует дополнительной проверки ошибок, если вы хотите, чтобы он обрабатывал ошибки в файле, например так:

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

В этом случае последний дочерний элемент будет добавлен как дочерний элемент «child 1», а не «другого корня».

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...