我使用递归方法遍历项目树并将其子项添加到平面集合中:
public class Thing
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
}
void Main()
{
var sampleData = new List<Thing>
{
new Thing { Id = 1, Name = "root1", ParentId = null },
new Thing { Id = 2, Name = "2", ParentId = 1 },
new Thing { Id = 3, Name = "3", ParentId = 1 },
new Thing { Id = 4, Name = "4", ParentId = 2 },
new Thing { Id = 5, Name = "5", ParentId = 2 },
new Thing { Id = 6, Name = "6", ParentId = 2 },
new Thing { Id = 7, Name = "7", ParentId = 6 },
new Thing { Id = 8, Name = "8", ParentId = 7 },
new Thing { Id = 9, Name = "9", ParentId = 8 },
new Thing { Id = 10, Name = "10", ParentId = 9 },
new Thing { Id = 11, Name = "11", ParentId = 10 },
new Thing { Id = 12, Name = "12", ParentId = 11 },
new Thing { Id = 13, Name = "13", ParentId = 12 },
new Thing { Id = 14, Name = "14", ParentId = 13 },
new Thing { Id = 15, Name = "root15", ParentId = null }
};
var subThings = new HashSet<Thing>();
var stopWatch = new Stopwatch();
stopWatch.Start();
//AddSubThings(subThings, sampleData, new List<int> { 1 });
AddSubThingsUsingList(subThings, sampleData, new List<int> { 1 });
stopWatch.Elapsed.Dump();
subThings.Dump();
}
private void AddSubThings(HashSet<Thing> resultThings, IEnumerable<Thing> sourceThings, IEnumerable<int> parentIds)
{
if (!sourceThings.Any() || !parentIds.Any())
{
return;
}
var subThings = sourceThings.Where(st => st.ParentId.HasValue && parentIds.Contains(st.ParentId.Value));
resultThings.UnionWith(subThings);
AddSubThings(resultThings, sourceThings.Except(subThings), subThings.Select(st => st.Id));
}
private void AddSubThingsUsingList(HashSet<Thing> resultThings, List<Thing> sourceThings, List<int> parentIds)
{
if (!sourceThings.Any() || !parentIds.Any())
{
return;
}
var subThings = sourceThings.Where(st => st.ParentId.HasValue && parentIds.Contains(st.ParentId.Value));
resultThings.UnionWith(subThings);
AddSubThingsUsingList(resultThings, sourceThings.Except(subThings).ToList(), subThings.Select(st => st.Id).ToList());
}
当我使用AddSubThings
方法时,处理大约需要90秒。但是,如果我使用AddSubThingsUsingList
方法,它甚至不需要一秒钟。这是为什么?
答案 0 :(得分:3)
问题是因为您从subThings
创建sourceThings
就像这样
var subThings = sourceThings.Where(
st => st.ParentId.HasValue && parentIds.Contains(st.ParentId.Value));
然后将以下sourceThings
传递给递归调用。
sourceThings.Except(subThings)
相当于
sourceThings.Except(
sourceThings.Where(
st => st.ParentId.HasValue && parentIds.Contains(st.ParentId.Value)))
迭代时的查询必须两次迭代原始列表。对于每个递归调用,查询将建立并且需要迭代原始列表2 ^ n次,其中n是递归级别。并且您的查询正在由Any
和HashSet.UnionWith
调用进行迭代,这意味着它更像是2 ^(n + 1)。
另一个在传递查询之前立即迭代查询,从而避免了这个加倍的问题。
您可以将以下内容传递给sourceThings
的递归调用,以使其更快,因为它不会在每次递归调用时将原始列表的所需迭代加倍。
sourceThings.Where(st => !st.ParentId.HasValue || !parentIds.Contains(st.ParentId.Value))
答案 1 :(得分:0)
确定。这有点复杂。
IEnumerable上的操作是惰性的,即在您需要结果之前不会执行它们。现在,当您将sourceThins
和subThings
传递给AddSubThings
时,您还没有发送物化的集合,您所做的就是您已经定义了如何从原List
s。
现在,当方法以递归方式调用自身时,它会对收到的数据添加更多过滤和选择。
当您致电Any()
时,将调用所有这些选择和过滤层。
另一方面,对于List
s,在调用Where
,Except
和Select
之后,事情就会实现,因为您致电ToList
}。