如何使用Linq搜索分层数据

我需要在树中搜索可能在树中任何位置的数据。 如何用linq完成?

class Program { static void Main(string[] args) { var familyRoot = new Family() {Name = "FamilyRoot"}; var familyB = new Family() {Name = "FamilyB"}; familyRoot.Children.Add(familyB); var familyC = new Family() {Name = "FamilyC"}; familyB.Children.Add(familyC); var familyD = new Family() {Name = "FamilyD"}; familyC.Children.Add(familyD); //There can be from 1 to n levels of families. //Search all children, grandchildren, great grandchildren etc, for "FamilyD" and return the object. } } public class Family { public string Name { get; set; } List _children = new List(); public List Children { get { return _children; } } } 

这是对于It'sNotALie.的扩展It'sNotALie. 的答案 。

 public static class Linq { public static IEnumerable Flatten(this T source, Func> selector) { return selector(source).SelectMany(c => Flatten(c, selector)) .Concat(new[] { source }); } } 

样本测试用法:

 var result = familyRoot.Flatten(x => x.Children).FirstOrDefault(x => x.Name == "FamilyD"); 

返回familyD对象。

您也可以在IEnumerable源上使用它:

 public static IEnumerable Flatten(this IEnumerable source, Func> selector) { return source.SelectMany(x => Flatten(x, selector)) .Concat(source); } 

没有递归的另一个解决方案……

 var result = FamilyToEnumerable(familyRoot) .Where(f => f.Name == "FamilyD"); IEnumerable FamilyToEnumerable(Family f) { Stack stack = new Stack(); stack.Push(f); while (stack.Count > 0) { var family = stack.Pop(); yield return family; foreach (var child in family.Children) stack.Push(child); } } 

简单:

 familyRoot.Flatten(f => f.Children); //you can do whatever you want with that sequence there. //for example you could use Where on it and find the specific families, etc. IEnumerable Flatten(this T source, Func> selector) { return selector(source).SelectMany(c => Flatten(selector(c), selector)) .Concat(new[]{source}); } 

我喜欢Kenneth Bo Christensen使用堆栈的答案,它很好用,它易于阅读且速度快(并且不使用递归)。 唯一令人不快的是它颠倒了子项的顺序(因为堆栈是FIFO)。 如果排序顺序对您无关紧要,那就没关系。 如果是这样,可以使用选择器(当前)轻松实现排序。 foreach循环中的Reverse() (其余代码与Kenneth的原始post相同)…

 public static IEnumerable Flatten(this T source, Func> selector) { var stack = new Stack(); stack.Push(source); while (stack.Count > 0) { var current = stack.Pop(); yield return current; foreach (var child in selector(current).Reverse()) stack.Push(child); } } 

嗯,我想方法是采用分层结构的技术:

  1. 你需要一个锚来制作
  2. 你需要递归部分

     // Anchor rootFamily.Children.ForEach(childFamily => { if (childFamily.Name.Contains(search)) { // Your logic here return; } SearchForChildren(childFamily); }); // Recursion public void SearchForChildren(Family childFamily) { childFamily.Children.ForEach(_childFamily => { if (_childFamily.Name.Contains(search)) { // Your logic here return; } SearchForChildren(_childFamily); }); } 

因此,最简单的选择是编写一个遍历层次结构并生成单个序列的函数。 然后,这将在LINQ操作开始时进行,例​​如

  IEnumerable Flatten(this T source) { foreach(var item in source) { yield item; foreach(var child in Flatten(item.Children) yield child; } } 

简单地调用:familyRoot.Flatten()。Where(n => n.Name ==“Bob”);

一个轻微的替代方案可以让您快速忽略整个分支:

  IEnumerable Flatten(this T source, Func predicate) { foreach(var item in source) { if (predicate(item)) { yield item; foreach(var child in Flatten(item.Children) yield child; } } 

然后你可以做以下事情:family.Flatten(n => n.Children.Count> 2)。所以(…)

我尝试了两个建议的代码,使代码更清晰:

  public static IEnumerable Flatten1(this T source, Func> selector) { return selector(source).SelectMany(c => Flatten1(c, selector)).Concat(new[] { source }); } public static IEnumerable Flatten2(this T source, Func> selector) { var stack = new Stack(); stack.Push(source); while (stack.Count > 0) { var current = stack.Pop(); yield return current; foreach (var child in selector(current)) stack.Push(child); } } 

Flatten2()似乎有点快,但它的运行速度很快。

关于ItsNotALie,MarcinJuraszek和DamienG答案的一些进一步变体。

首先,前两个给出了违反直觉的命令。 要获得结果的良好树遍历排序,只需反转连接(首先放置“源”)。

其次,如果您正在使用像EF这样的昂贵来源,并且您想要限制整个分支,Damien建议您注入谓词是一个很好的,并且仍然可以使用Linq完成。

最后,对于昂贵的源,使用注入的选择器从每个节点预先选择感兴趣的字段也可能是好的。

把所有这些放在一起:

 public static IEnumerable Flatten(this T source, Func> children , Func selector , Func branchpredicate = null ) { if (children == null) throw new ArgumentNullException("children"); if (selector == null) throw new ArgumentNullException("selector"); var pred = branchpredicate ?? (src => true); if (children(source) == null) return new[] { selector(source) }; return new[] { selector(source) } .Concat(children(source) .Where(pred) .SelectMany(c => Flatten(c, children, selector, pred))); }