How to search Hierarchical Data with Linq

前端 未结 8 660
心在旅途
心在旅途 2020-12-03 11:43

I need to search a tree for data that could be anywhere in the tree. How can this be done with linq?

class Program
{
    static void Main(string[] args) {

         


        
相关标签:
8条回答
  • 2020-12-03 12:01

    Well, I guess the way is to go with the technique of working with hierarchical structures:

    1. You need an anchor to make
    2. You need the recursion part

      // 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);
          });
      }
      
    0 讨论(0)
  • 2020-12-03 12:04

    That's an extension to It'sNotALie.s answer.

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

    Sample test usage:

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

    Returns familyD object.

    You can make it work on IEnumerable<T> source too:

    public static IEnumerable<T> Flatten<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> selector)
    {
        return source.SelectMany(x => Flatten(x, selector))
            .Concat(source);
    }
    
    0 讨论(0)
  • 2020-12-03 12:09

    So, the simplest option is to write a function that traverses your hierarchy and produces a single sequence. This then goes at the start of your LINQ operations, e.g.

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

    To call simply: familyRoot.Flatten().Where(n => n.Name == "Bob");

    A slight alternative would give you a way to quickly ignore a whole branch:

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

    Then you could do things like: family.Flatten(n => n.Children.Count > 2).Where(...)

    0 讨论(0)
  • 2020-12-03 12:12

    Simple:

    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});
    }
    
    0 讨论(0)
  • 2020-12-03 12:13

    Some further variants on the answers of It'sNotALie., MarcinJuraszek and DamienG.

    First, the former two give a counterintuitive ordering. To get a nice tree-traversal ordering to the results, just invert the concatenation (put the "source" first).

    Second, if you are working with an expensive source like EF, and you want to limit entire branches, Damien's suggestion that you inject the predicate is a good one and can still be done with Linq.

    Finally, for an expensive source it may also be good to pre-select the fields of interest from each node with an injected selector.

    Putting all these together:

    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)));
    }
    
    0 讨论(0)
  • 2020-12-03 12:15

    Another solution without recursion...

    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);
        }
    }
    
    0 讨论(0)
提交回复
热议问题