如何使用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); } }
嗯,我想方法是采用分层结构的技术:
- 你需要一个锚来制作
-
你需要递归部分
// 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))); }