1

I've got a Type that we'll call Foo that can hold a collection of children Foo objects. Foo is Disposable, so when ever a child is disposed of, it will then add itself to the parent's Children collection.

An example usage of this looks like:

using (var a = AddChild(Root, "a"))
{
    using (var a1 = AddChild(a, "a1"))
    {
        using (var a1a = AddChild(a1, "a1a"))
        {

        }
    }

In this example a1a is only added to a1 when it is disposed, and not before. What I am having difficulty in figuring out is a clean way of writing a GetAllFoos method that returns all of the objects in a flattened list, in a FILO order.

In this case, would I just recursively iterate over each child, or is there some fancy LINQ I can use to try and consolidate these collections? I'm using this to take performance measurement snapshots through-out the app, and it's possible that we would call GetAllMeasurements in some cases during a profile so the performance of the method call is important.

This is a complete example app that shows what the expected results would look like. I have to support both FIFO and FILO. I've got a FIFO implementation working but I'm not sure on the best way to handle this inversely for FILO.

using System;
using System.Collections.Generic;
using System.Linq;

namespace FILO_Example
{
    public class Foo : IDisposable
    {
        internal Foo parent;

        public Foo(Foo parent = null)
        {
            this.parent = parent;
        }

        public string Name { get; set; }
        public List<Foo> Children { get; } = new List<Foo>();

        public void Dispose() => this.parent.Children.Add(this);
    }

    class Program
    {
        public static Foo Root { get; } = new Foo { Name = "Root" };

        static void Main(string[] args)
        {
            // level 1
            using (var a = AddChild(Root, "a"))
            {
                using (var a1 = AddChild(a, "a1"))
                {
                    using (var a1a = AddChild(a1, "a1a"))
                    {

                    }
                }

                using (var a2 = AddChild(a, "a2"))
                {

                }
            }

            using (var b = AddChild(Root, "b"))
            {
                using (var b1 = AddChild(b, "b1"))
                {

                }
            }

            List<Foo> allFoos = GetAllFoosFILO().ToList();

            Console.WriteLine(allFoos[0]); // Should be b1
            Console.WriteLine(allFoos[1]); // Should be b
            Console.WriteLine(allFoos[2]); // Should be a2
            Console.WriteLine(allFoos[3]); // Should be a1a
            Console.WriteLine(allFoos[4]); // Should be a1
            Console.WriteLine(allFoos[5]); // Should be a
        }

        static IEnumerable<Foo> GetAllFoosFILO()
        {
            return new List<Foo>();
        }

        static IEnumerable<Foo> GetAllFoosFIFO()
        {
            var fooStack = new Stack<Foo>();
            fooStack.Push(Root);
            while (fooStack.Count > 0)
            {
                Foo currentFoo = fooStack.Pop();
                yield return currentFoo;

                // If we have children, add them in reverse order so that it's a First-In-First-Out stack
                // then the while loop will yield each child element.
                if (currentFoo.Children.Count > 0)
                {
                    List<Foo> fooChildren = currentFoo.Children;
                    for (int currentIndex = fooChildren.Count - 1; currentIndex >= 0; currentIndex--)
                    {
                        fooStack.Push(fooChildren[currentIndex]);
                    }
                }
            }
        }

        static Foo AddChild(Foo parent, string name)
        {
            var child = new Foo(parent) { Name = name };
            return child;
        }
    }
}
Johnathon Sullinger
  • 7,097
  • 5
  • 37
  • 102
  • If I understand correctly, by FILO you mean pre-order DFS. Then what is FIFO - the reverse? – Ivan Stoev Oct 18 '16 at 19:27
  • Sorry, I'm not sure I understand what DFS is totally (i just read a bit on it prior to posting this reply). I just know that FILO means I return the last element that was added, first while moving backwards. I understand FIFO to be returning the first element that was added, first and working forward. Since I need to handle traversing the collections in two different orders, which is determined after the fact, using a Stack isn't viable because it is only FILO and not FIFO. – Johnathon Sullinger Oct 18 '16 at 19:36
  • DFS stands for [Depth First Search](https://en.wikipedia.org/wiki/Depth-first_search). Because you basically have a tree. – Ivan Stoev Oct 18 '16 at 19:42
  • That's what I understood it to be. After re-reading that again i'm thinking I need to apply the `Reverse Post-Ordering` algorithm as that best fits my use-case here. – Johnathon Sullinger Oct 18 '16 at 19:51
  • 1
    I think that [this example](https://blogs.msdn.microsoft.com/daveremy/2010/03/16/non-recursive-post-order-depth-first-traversal-in-c/) is what I'm looking for, I'll work through this exercise and see if it solves my use-case. – Johnathon Sullinger Oct 18 '16 at 19:53

2 Answers2

2

As I mentioned in the comments, you have a tree structure. There is no fancy efficient standard LINQ solution, but you can utilize the quite efficient generic Traverse method form my answer to Enumerating Directories iteratively in "postorder":

public static class TreeHelper
{
    public static IEnumerable<T> Traverse<T>(T node, Func<T, IEnumerable<T>> childrenSelector, bool preOrder = true)
    {
        var stack = new Stack<IEnumerator<T>>();
        var e = Enumerable.Repeat(node, 1).GetEnumerator();
        try
        {
            while (true)
            {
                while (e.MoveNext())
                {
                    var item = e.Current;
                    var children = childrenSelector(item);
                    if (children == null)
                        yield return item;
                    else
                    {
                        if (preOrder) yield return item;
                        stack.Push(e);
                        e = children.GetEnumerator();
                    }
                }
                if (stack.Count == 0) break;
                e.Dispose();
                e = stack.Pop();
                if (!preOrder) yield return e.Current;
            }
        }
        finally
        {
            e.Dispose();
            while (stack.Count != 0) stack.Pop().Dispose();
        }
    }
}

With that helper, the GetAllFoosFIFO() is simple as that:

static IEnumerable<Foo> GetAllFoosFIFO()
{
    return TreeHelper.Traverse(Root, foo => foo.Children.Count > 0 ? foo.Children : null);
}

while for GetAllFoosFILO() you need to pass preorder = false and iterate Children in reverse order:

static IEnumerable<Foo> GetAllFoosFILO()
{
    return TreeHelper.Traverse(Root, foo => foo.Children.Count > 0 ?
        Enumerable.Range(0, foo.Children.Count)
        .Select(i => foo.Children[foo.Children.Count - 1 - i]) : null, false);
}
Community
  • 1
  • 1
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • Awesome, I'll plug this in and give it a shot. – Johnathon Sullinger Oct 18 '16 at 19:57
  • That worked for me with just a small tweak. I moved the `Enumerable.Range().Select()` logic into the `Traverse` method and handle it there when `preSort` is `false`. This let me use it as `TreeHelper.Traverse(Root, foo => foo.Children, false);`, simplifying my callsite a bit going forward. Thanks again! – Johnathon Sullinger Oct 18 '16 at 20:36
1

This should work:

private static IEnumerable<Foo> GetAllFoosFILO(Foo foo)
{
    foreach (var c in ((IEnumerable<Foo>)foo.Children).Reverse())
    {
        var cc = GetAllFoosFILO(c);
        foreach (var ccc in cc)
        {
            yield return ccc;
        }
        yield return c;
    }
}

The weird cast in first foreach loop is for preventing use of List<T>.Reverse instead of Enumerable.Reverse<TSource> extension method which helps us to traverse tree in so called FILO way.

Bonus

With some small touches you can write FIFO like:

private static IEnumerable<Foo> GetAllFoosFIFO(Foo foo)
{
    foreach (var c in foo.Children)
    {
        yield return c;

        var cc = GetAllFoosFIFO(c);
        foreach (var ccc in cc)
        {
            yield return ccc;
        }
    }
}
Mehmet Ataş
  • 11,081
  • 6
  • 51
  • 78