0

I have a List, with a custom class which also has a list with the same custom class, that can store a file and folder tree.

An example structure is:

Root Directory

  • Directory
    • Directory
    • Directory
      • Directory
        • File
  • Directory
    • Directory
    • File
  • Directory
    • File

Because the list will represent a folder tree later, it will not be of the same structure every time.

So my question is how can I dynamically loop through such variable loop?

These are my classes where I store the tree:

public class Tree
    {
        public int? Id { get; set; }
        public string Text { get; set; }
        protected List<Tree> _children;
        protected Tree _parent;

        public Tree()
        {
            Text = string.Empty;
        }

        public Tree Parent { get { return _parent; } }
        public Tree Root { get { return _parent == null ? this : _parent.Root; } }
        public int Depth { get { return this.Ancestors().Count() - 1; } }

        public IEnumerable<Tree> Children
        {
            get { return _children == null ? Enumerable.Empty<Tree>() : _children.ToArray(); }
        }

        public override string ToString()
        {
            return Text;
        }

        public void Add(Tree child)
        {
            if (child == null)
                throw new ArgumentNullException();
            if (child._parent != null)
                throw new InvalidOperationException("A tree node must be removed from its parent before adding as child.");
            if (this.Ancestors().Contains(child))
                throw new InvalidOperationException("A tree cannot be a cyclic graph.");
            if (_children == null)
            {
                _children = new List<Tree>();
            }
            Debug.Assert(!_children.Contains(child), "At this point, the node is definately not a child");
            child._parent = this;
            _children.Add(child);
        }

        public bool Remove(Tree child)
        {
            if (child == null)
                throw new ArgumentNullException();
            if (child._parent != this)
                return false;
            Debug.Assert(_children.Contains(child), "At this point, the node is definately a child");
            child._parent = null;
            _children.Remove(child);
            if (!_children.Any())
                _children = null;
            return true;
        }
    }

    public static class TreeBuilder
    {
        public static Tree BuildTree(IEnumerable<TreeNode> nodes)
        {
            if (nodes == null) return new Tree();
            var nodeList = nodes.ToList();
            var tree = FindTreeRoot(nodeList);
            BuildTree(tree, nodeList);
            return tree;
        }

        private static void BuildTree(Tree tree, IList<TreeNode> descendants)
        {
            var children = descendants.Where(node => node.parent == tree.Id).ToArray();
            foreach (var child in children)
            {
                var branch = Map(child);
                tree.Add(branch);
                descendants.Remove(child);
            }
            foreach (var branch in tree.Children)
            {
                BuildTree(branch, descendants);
            }
        }

        private static Tree FindTreeRoot(IList<TreeNode> nodes)
        {
            var rootNodes = nodes.Where(node => node.parent == null);
            if (rootNodes.Count() != 1) return new Tree();
            var rootNode = rootNodes.Single();
            nodes.Remove(rootNode);
            return Map(rootNode);
        }

        private static Tree Map(TreeNode node)
        {
            return new Tree
            {
                Id = node.id,
                Text = node.text,
            };
        }
    }

    public class TreeNode
    {
        public int id { get; set; }
        public string text { get; set; }
        public int? parent { get; set; }
    }

    public static class TreeExtensions
    {
        public static IEnumerable<Tree> Descendants(this Tree value)
        {
            // a descendant is the node self and any descendant of the children
            if (value == null) yield break;
            yield return value;
            // depth-first pre-order tree walker
            foreach (var child in value.Children)
                foreach (var descendantOfChild in child.Descendants())
                {
                    yield return descendantOfChild;
                }
        }

        public static IEnumerable<Tree> Ancestors(this Tree value)
        {
            // an ancestor is the node self and any ancestor of the parent
            var ancestor = value;
            // post-order tree walker
            while (ancestor != null)
            {
                yield return ancestor;
                ancestor = ancestor.Parent;
            }
        }
    }

It is from this post: Codereview link

At the end I would like to map it to a list which will graphically display it. It works like the following scheme:

foreach (var children in _builtTree.Children)
{
    Node child = new MyNode(children.Text);
    _node.Nodes.Add(child);
}

To manually display the method I would have to add about 500 nested foreach loops which cannot be a good solution:

foreach (var children in _builtTree.Children)
{
    Node child = new MyNode(children.Text);
    _node.Nodes.Add(child);

    foreach (var children2 in childen.Children)
    {
        Node child2 = new MyNode(children2.Text);
        _node.Nodes.Add(child2);

        foreach (var children3 in childen2.Children)
        {
            Node child3 = new MyNode(children3.Text);
            _node.Nodes.Add(child3);
        }
    }
}

I hope it is understandable what I am writing. If not, feel free to leave a comment.

Steven2105
  • 540
  • 5
  • 20
  • Does this answer your question? [How to flatten tree via LINQ?](https://stackoverflow.com/questions/11830174/how-to-flatten-tree-via-linq) – Sinatr Apr 15 '21 at 13:47
  • You need a recursive method with two parameters. The parent TreeView and the parent Tree (that contains the folder directories). Then use code similar to what you have posted. – jdweng Apr 15 '21 at 13:58

2 Answers2

1

It's not clear for me what do you exactly want to implement. Howvere, you can try starting from Breadth First Search (BFS) for a graph (let's implement a general case):

 public static IEnumerable<V> Bfs<V, N>(IEnumerable<N> roots, 
                                        Func<N, IEnumerable<N>> children,
                                        Func<N, IEnumerable<V>> values) {
   if (null == roots)
     throw new ArgumentNullException(nameof(roots));
   if (null == children)
     throw new ArgumentNullException(nameof(children));
   if (null == values)
     throw new ArgumentNullException(nameof(values));

   Queue<N> agenda = new Queue<N>(roots);
   HashSet<N> completed = new HashSet<N>();  

   while (agenda.Count > 0) {
     N root = agenda.Dequeue();

     if (null == root || !completed.Add(root))
       continue;

     foreach (V value in values(root))
       yield return value;

     foreach (N child in children(root))
       agenda.Enqueue(child);  
   }   
 }

Here

  • roots - roots to start with (put new MyRoot[] {myRoot} if you have just one root)
  • children returns children of a given node (sub directories for given directory)
  • values provides values which should be returned for each node (files for given directory)

Now, if you want to enumerate files:

var files = Bfs<MyFile, MyDir>(new MyDir[] {rootDirectory},
                               dir => dir.EnumerateFiles(),
                               dir => dir.SubDirectories());

To enumerate dirs:

var files = Bfs<MyDir, MyDir>(new MyDir[] {rootDirectory},
                               dir => dir.SubDirectories(),
                               dir => dir.SubDirectories());
Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
0

You could use a recursive method:

public void GetNodes(Tree tree)
{
    _node.Nodes.Add(tree.Text);
    foreach (var child in tree.Children)
        GetNodes(child);
}
Johnathan Barclay
  • 18,599
  • 1
  • 22
  • 35
Steve Harris
  • 5,014
  • 1
  • 10
  • 25