C# 如何使用Linq搜索分层数据
我需要在一棵树中搜索可能位于树中任何位置的数据。如何使用linq实现这一点C# 如何使用Linq搜索分层数据,c#,linq,C#,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)
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<Family> _children = new List<Family>();
public List<Family> Children {
get { return _children; }
}
}
类程序
{
静态void Main(字符串[]参数){
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);
//可以有1到n个级别的族。
//搜索所有子代、孙辈、曾孙辈等,查找“FamilyD”,并返回对象。
}
}
公营家庭{
公共字符串名称{get;set;}
列表_children=新列表();
公开儿童名单{
获取{return\u children;}
}
}
简单:
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<T> Flatten<T>(this T source, Func<T, IEnumerable<T>> selector)
{
return selector(source).SelectMany(c => Flatten(selector(c), selector))
.Concat(new[]{source});
}
familyRoot.Flatten(f=>f.Children);
//你可以用这个序列做任何你想做的事情。
//例如,您可以使用其中的Where和查找特定的族等。
IEnumerable展平(此T源,函数选择器)
{
返回选择器(源)。SelectMany(c=>Flant(选择器(c),选择器))
.Concat(新[]{source});
}
好吧,我想方法是使用层次结构的技术:
// 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<T> Flatten<T>(this T source)
{
foreach(var item in source) {
yield item;
foreach(var child in Flatten(item.Children)
yield child;
}
}
IEnumerable展平(此T源)
{
foreach(源中的var项){
收益项目;
foreach(展平中的变量子项(item.Children)
产子;
}
}
简单地调用:familyRoot.flatte(),其中(n=>n.Name==“Bob”)
一个小的替代方案可以让您快速忽略整个分支:
IEnumerable<T> Flatten<T>(this T source, Func<T, bool> predicate)
{
foreach(var item in source) {
if (predicate(item)) {
yield item;
foreach(var child in Flatten(item.Children)
yield child;
}
}
IEnumerable展平(此T源,Func谓词)
{
foreach(源中的var项){
if(谓词(项)){
收益项目;
foreach(展平中的变量子项(item.Children)
产子;
}
}
然后,您可以执行以下操作:family.flatte(n=>n.Children.Count>2)。其中(…)没有递归的另一个解决方案
var result = FamilyToEnumerable(familyRoot)
.Where(f => f.Name == "FamilyD");
IEnumerable<Family> FamilyToEnumerable(Family f)
{
Stack<Family> stack = new Stack<Family>();
stack.Push(f);
while (stack.Count > 0)
{
var family = stack.Pop();
yield return family;
foreach (var child in family.Children)
stack.Push(child);
}
}
var结果=FamilyToEnumerable(familyRoot)
其中(f=>f.Name==“FamilyD”);
IEnumerable族可枚举(族f)
{
堆栈=新堆栈();
堆栈推送(f);
而(stack.Count>0)
{
var family=stack.Pop();
收益家庭;
foreach(var-child-in-family.Children)
栈.推(子);
}
}
这是对的扩展
返回familyD
对象
您也可以在IEnumerable
源代码上使用它:
public static IEnumerable<T> Flatten<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> selector)
{
return source.SelectMany(x => Flatten(x, selector))
.Concat(source);
}
公共静态IEnumerable扁平化(此IEnumerable源,Func选择器)
{
返回source.SelectMany(x=>flant(x,选择器))
.Concat(来源);
}
我尝试了两种建议的代码,并使代码更加清晰:
public static IEnumerable<T> Flatten1<T>(this T source, Func<T, IEnumerable<T>> selector)
{
return selector(source).SelectMany(c => Flatten1(c, selector)).Concat(new[] { source });
}
public static IEnumerable<T> Flatten2<T>(this T source, Func<T, IEnumerable<T>> selector)
{
var stack = new Stack<T>();
stack.Push(source);
while (stack.Count > 0)
{
var current = stack.Pop();
yield return current;
foreach (var child in selector(current))
stack.Push(child);
}
}
公共静态IEnumerable 1(此T源,函数选择器)
{
返回选择器(source).SelectMany(c=>1(c,选择器)).Concat(new[]{source});
}
公共静态IEnumerable 2(此T源,函数选择器)
{
var stack=新堆栈();
堆栈推送(源);
而(stack.Count>0)
{
var current=stack.Pop();
产生回流电流;
foreach(选择器中的变量子项(当前))
栈.推(子);
}
}
Flatt2()似乎快了一点,但运行速度很快。我喜欢Kenneth Bo Christensen使用堆栈的答案,它工作得很好,易于阅读,而且速度很快(并且不使用递归)。 唯一令人不快的是,它颠倒了子项的顺序(因为堆栈是FIFO)。如果排序顺序对您来说不重要,那么就可以了。 如果是这样的话,可以在foreach循环中使用选择器(current)轻松实现排序。Reverse()
公共静态IEnumerable扁平化(此T源,函数选择器)
{
var stack=新堆栈();
堆栈推送(源);
而(stack.Count>0)
{
var current=stack.Pop();
产生回流电流;
foreach(选择器(当前).Reverse()中的变量子项)
栈.推(子);
}
}
关于它的答案的一些进一步的变体,如Talie,MarcinJuraszek和DamienG
首先,前两种方法给出了一个反直觉的顺序。要获得一个好的结果树遍历顺序,只需反转连接(将“源”放在第一位)
其次,如果您使用的是像EF这样昂贵的源代码,并且希望限制整个分支,那么Damien建议您注入谓词是一个很好的建议,并且仍然可以使用Linq来完成
最后,对于昂贵的源,使用注入选择器从每个节点预先选择感兴趣的字段也可能是好的
把所有这些放在一起:
public static IEnumerable<R> Flatten<T,R>(this T source, Func<T, IEnumerable<T>> children
, Func<T, R> selector
, Func<T, bool> 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)));
}
public static IEnumerable Flatten(此T源代码,Func子级
,Func选择器
,Func branchpredicate=null
) {
如果(children==null)抛出新ArgumentNullException(“children”);
如果(选择器==null)抛出新的ArgumentNullException(“选择器”);
var pred=分支预测???(src=>true);
if(children(source)==null)返回新的[]{selector(source)};
返回新[]{选择器(源)}
.Concat(儿童)(来源)
.Where(pred)
.SelectMany(c=>flant(c,children,selector,pred));
}
这看起来非常好,我正在尝试让它工作。但它告诉我“无法从用法中推断类型参数。请尝试显式指定类型参数。@GregHol
public static IEnumerable<T> Flatten1<T>(this T source, Func<T, IEnumerable<T>> selector)
{
return selector(source).SelectMany(c => Flatten1(c, selector)).Concat(new[] { source });
}
public static IEnumerable<T> Flatten2<T>(this T source, Func<T, IEnumerable<T>> selector)
{
var stack = new Stack<T>();
stack.Push(source);
while (stack.Count > 0)
{
var current = stack.Pop();
yield return current;
foreach (var child in selector(current))
stack.Push(child);
}
}
public static IEnumerable<T> Flatten<T>(this T source, Func<T, IEnumerable<T>> selector)
{
var stack = new Stack<T>();
stack.Push(source);
while (stack.Count > 0)
{
var current = stack.Pop();
yield return current;
foreach (var child in selector(current).Reverse())
stack.Push(child);
}
}
public static IEnumerable<R> Flatten<T,R>(this T source, Func<T, IEnumerable<T>> children
, Func<T, R> selector
, Func<T, bool> 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)));
}