【问题标题】:Using FILO with nested collections将 FILO 与嵌套集合一起使用
【发布时间】:2017-02-28 04:55:06
【问题描述】:

我有一个类型,我们称之为Foo,它可以容纳一组子Foo 对象。 Foo 是一次性的,所以当一个孩子被处置时,它会将自己添加到父级的 Children 集合中。

这个例子的用法如下:

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

        }
    }

在此示例中,a1a 仅在处置时添加到 a1,而不是之前。我难以弄清楚的是编写GetAllFoos 方法的简洁方法,该方法以FILO 顺序返回扁平列表中的所有对象。

在这种情况下,我是否只是递归地遍历每个孩子,还是有一些花哨的 LINQ 可以用来尝试合并这些集合?我正在使用它在整个应用程序中拍摄性能测量快照,在某些情况下,我们可能会在配置文件期间调用 GetAllMeasurements,因此方法调用的性能很重要。

这是一个完整的示例应用程序,显示了预期的结果。我必须同时支持 FIFO 和 FILO。我有一个 FIFO 实现工作,但我不确定为 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;
        }
    }
}

【问题讨论】:

  • 如果我理解正确,FILO 是指预购 DFS。那么什么是先进先出——反过来呢?
  • 抱歉,我不确定我完全理解 DFS 是什么(我只是在发布此回复之前阅读了一些内容)。我只知道 FILO 意味着我返回最后添加的元素,首先是向后移动。我理解 FIFO 将首先返回添加的第一个元素并继续工作。由于我需要处理以两种不同的顺序遍历集合,这是事后确定的,因此使用 Stack 是不可行的,因为它只是 FILO 而不是 FIFO。
  • DFS 代表Depth First Search。因为你基本上有一棵树。
  • 这就是我的理解。再次阅读后,我想我需要应用Reverse Post-Ordering 算法,因为这最适合我的用例。
  • 我认为 this example 是我正在寻找的东西,我将完成这个练习,看看它是否能解决我的用例。

标签: c# linq sorting


【解决方案1】:

正如我在 cmets 中提到的,您有一个树形结构。没有花哨的高效标准 LINQ 解决方案,但您可以利用非常有效的通用 Traverse 方法形成我对 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();
        }
    }
}

有了这个助手,GetAllFoosFIFO() 就这么简单:

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

而对于GetAllFoosFILO(),您需要传递preorder = false 并以相反的顺序迭代Children

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);
}

【讨论】:

  • 太棒了,我会插上它并试一试。
  • 这对我来说只是一个小小的调整。我将Enumerable.Range().Select() 逻辑移到Traverse 方法中,并在preSortfalse 时处理它。这让我将其用作TreeHelper.Traverse(Root, foo =&gt; foo.Children, false);,简化了我的呼叫站点。再次感谢!
【解决方案2】:

这应该可行:

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;
    }
}

第一个 foreach 循环中的奇怪转换是为了防止使用 List&lt;T&gt;.Reverse 而不是 Enumerable.Reverse&lt;TSource&gt; 扩展方法,这有助于我们以所谓的 FILO 方式遍历树。

奖金

通过一些小改动,您可以编写 FIFO,如:

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;
        }
    }
}

【讨论】:

    猜你喜欢
    • 2012-07-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-18
    • 2017-01-24
    • 2013-10-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多