C# 如何使用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)

我需要在一棵树中搜索可能位于树中任何位置的数据。如何使用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<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)));
    }