如何使用Linq

本文关键字:Linq 何使用 | 更新日期: 2023-09-27 18:27:21

我需要在树中搜索可能位于树中任何位置的数据。林克怎么能做到这一点?

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; }
    }
}

如何使用Linq

这是It'sNotALie.答案的扩展

public static class Linq
{
    public static IEnumerable<T> Flatten<T>(this T source, Func<T, IEnumerable<T>> 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<T>上工作来源:

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

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

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);
    }
}

简单:

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});
}

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

    IEnumerable<T> Flatten<T>(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<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;
      }
    }

然后你可以做一些事情,比如:家庭。压扁(n=>n.Children.Count>2)。其中(…)

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

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);
    }
}

好吧,我想方法是使用分层结构的技术:

  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);
        });
    }
    

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

    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);
        }
    }

Flatten2()看起来有点快,但它的运行速度很快。

ItsNotALie答案的一些进一步的变体。,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)));
}