树木修剪表现不佳

时间:2015-09-29 20:28:34

标签: c# linq tree breadth-first-search

我做了我正在呼叫的TreePruner。其目的:给定从根级别节点列表开始的层次结构,返回新的层次结构,其中新的根节点是满足特定条件的最高级别节点。这是我的班级。

public class BreadthFirstPruner<TResource>
{
    private IEnumerable<TResource> originalList;
    private IEnumerable<TResource> prunedList;
    private Func<TResource, ICollection<TResource>> getChildren;

    public BreadthFirstPruner(IEnumerable<TResource> list, Func<TResource, ICollection<TResource>> getChildren)
    {
        this.originalList = list;
        this.getChildren = getChildren;
    }

    public IEnumerable<TResource> GetPrunedTree(Func<TResource,bool> condition)
    {
        this.prunedList = new List<TResource>();
        this.Prune(this.originalList, condition);
        return this.prunedList;
    }

    private void Prune(IEnumerable<TResource> list, Func<TResource,bool> condition)
    {
        if (list.Count() == 0)
        {
            return;
        }

        var included = list.Where(condition);
        this.prunedList = this.prunedList.Union(included);
        var excluded = list.Except(included);
        this.Prune(excluded.SelectMany(this.getChildren), condition);
    }
}

班级做了它应该做的事情,但它做得很慢,我无法弄清楚为什么。我已经在非常小的层次结构上使用它,其中完整的层次结构已经在内存中(因此不应该有linq-to-sql意外)。但无论我试图做出多么渴望或懒惰,实际评估linq表达式结果的第一行代码最终需要3-4秒才能执行。

以下是目前正在使用修剪器的代码:

Func<BusinessUnitLabel, ICollection<BusinessUnitLabel>> getChildren = l => l.Children;
var hierarchy = scope.ToList();
var pruner = new BreadthFirstPruner<BusinessUnitLabel>(hierarchy, getChildren);
Func<BusinessUnitLabel, bool> hasBusinessUnitsForUser = l =>
    l.BusinessUnits.SelectMany(bu => bu.Users.Select(u => u.IDGUID)).Contains(userId);
var labels = pruner.GetPrunedTree(hasBusinessUnitsForUser).ToList();

正如我之前所说,我执行此操作时所使用的数据集非常小。它只有几个级别,在大多数级别上只有一个节点。正如它当前编写的那样,当我调用Prune时,对list.Count()的第一次递归调用会发生缓慢,因为当第二级调用时正在评估层次结构(excluded.SelectMany(this.getChildren))。

但是,如果我添加.ToList这样的电话:

var included = list.Where(condition).ToList()

然后在那时会发生缓慢。

我需要做些什么才能让这件事变得更快?

更新

在有人提示我更仔细地重新评估我的情况之后,我意识到hasBusinessUnitsForUser中的那些关联并没有被迫加载。那就是问题。

1 个答案:

答案 0 :(得分:1)

这些调用都是懒惰执行的,结果没有缓存/具体化:

    var included = list.Where(condition);
    this.prunedList = this.prunedList.Union(included);
    var excluded = list.Except(included);

即使在此代码段included中运行两次。由于这是一个递归算法,因此可能会有更多的调用。

对可能多次执行的任何序列添加ToList调用。