How to flatten tree via LINQ?

后端 未结 14 2200
抹茶落季
抹茶落季 2020-11-22 04:56

So I have simple tree:

class MyNode
{
 public MyNode Parent;
 public IEnumerable Elements;
 int group = 1;
}

I have a I

相关标签:
14条回答
  • 2020-11-22 05:54

    I found some small issues with the answers given here:

    • What if the initial list of items is null?
    • What if there is a null value in the list of children?

    Built on the previous answers and came up with the following:

    public static class IEnumerableExtensions
    {
        public static IEnumerable<T> Flatten<T>(
            this IEnumerable<T> items, 
            Func<T, IEnumerable<T>> getChildren)
        {
            if (items == null)
                yield break;
    
            var stack = new Stack<T>(items);
            while (stack.Count > 0)
            {
                var current = stack.Pop();
                yield return current;
    
                if (current == null) continue;
    
                var children = getChildren(current);
                if (children == null) continue;
    
                foreach (var child in children)
                    stack.Push(child);
            }
        }
    }
    

    And the unit tests:

    [TestClass]
    public class IEnumerableExtensionsTests
    {
        [TestMethod]
        public void NullList()
        {
            IEnumerable<Test> items = null;
            var flattened = items.Flatten(i => i.Children);
            Assert.AreEqual(0, flattened.Count());
        }
        [TestMethod]
        public void EmptyList()
        {
            var items = new Test[0];
            var flattened = items.Flatten(i => i.Children);
            Assert.AreEqual(0, flattened.Count());
        }
        [TestMethod]
        public void OneItem()
        {
            var items = new[] { new Test() };
            var flattened = items.Flatten(i => i.Children);
            Assert.AreEqual(1, flattened.Count());
        }
        [TestMethod]
        public void OneItemWithChild()
        {
            var items = new[] { new Test { Id = 1, Children = new[] { new Test { Id = 2 } } } };
            var flattened = items.Flatten(i => i.Children);
            Assert.AreEqual(2, flattened.Count());
            Assert.IsTrue(flattened.Any(i => i.Id == 1));
            Assert.IsTrue(flattened.Any(i => i.Id == 2));
        }
        [TestMethod]
        public void OneItemWithNullChild()
        {
            var items = new[] { new Test { Id = 1, Children = new Test[] { null } } };
            var flattened = items.Flatten(i => i.Children);
            Assert.AreEqual(2, flattened.Count());
            Assert.IsTrue(flattened.Any(i => i.Id == 1));
            Assert.IsTrue(flattened.Any(i => i == null));
        }
        class Test
        {
            public int Id { get; set; }
            public IEnumerable<Test> Children { get; set; }
        }
    }
    
    0 讨论(0)
  • 2020-11-22 05:55

    Update:

    For people interested in level of nesting (depth). One of the good things about explicit enumerator stack implementation is that at any moment (and in particular when yielding the element) the stack.Count represents the currently processing depth. So taking this into account and utilizing the C#7.0 value tuples, we can simply change the method declaration as follows:

    public static IEnumerable<(T Item, int Level)> ExpandWithLevel<T>(
        this IEnumerable<T> source, Func<T, IEnumerable<T>> elementSelector)
    

    and yield statement:

    yield return (item, stack.Count);
    

    Then we can implement the original method by applying simple Select on the above:

    public static IEnumerable<T> Expand<T>(
        this IEnumerable<T> source, Func<T, IEnumerable<T>> elementSelector) =>
        source.ExpandWithLevel(elementSelector).Select(e => e.Item);
    

    Original:

    Surprisingly no one (even Eric) showed the "natural" iterative port of a recursive pre-order DFT, so here it is:

        public static IEnumerable<T> Expand<T>(
            this IEnumerable<T> source, Func<T, IEnumerable<T>> elementSelector)
        {
            var stack = new Stack<IEnumerator<T>>();
            var e = source.GetEnumerator();
            try
            {
                while (true)
                {
                    while (e.MoveNext())
                    {
                        var item = e.Current;
                        yield return item;
                        var elements = elementSelector(item);
                        if (elements == null) continue;
                        stack.Push(e);
                        e = elements.GetEnumerator();
                    }
                    if (stack.Count == 0) break;
                    e.Dispose();
                    e = stack.Pop();
                }
            }
            finally
            {
                e.Dispose();
                while (stack.Count != 0) stack.Pop().Dispose();
            }
        }
    
    0 讨论(0)
提交回复
热议问题