简体   繁体   中英

How to get depth of hierarchical data with linq query?

I have a list of hierarchical data like this:

var list = new List<Data>(){some data...}

class Data
{
    public int number;
    public List<Data> info;
}

Note: Data in leaf of tree --> info = null

Example:

numbers are number property of Data class

   --1
      --11
   --2
      --21
      --22
      --23
      --24
   --3
      --31
      --32
          --321
          --322
   --4
      --41
      --42

How to know max depth of tree with linq query(Not Recursive method or for loop ) to list of data?

in this example max level is 3 for 321,322

Thanks.

LINQ and SQL operate on flat data structures; they are not designed for recursive data structures.

With LINQ to Entities, I believe you're out of luck. Store the depth of the subtree in each node and recursively update it whenever you insert/delete a node.

With LINQ to Objects, you could define a recursive extension method that returns all paths in a tree and take the length of the longest path:

var result = root.Paths().Max(path => path.Length);

where

public static IEnumerable<Data[]> Paths(this Data data)
{
    return Paths(data, new[] { data });
}

private static IEnumerable<Data[]> Paths(Data data, Data[] path)
{
    return new[] { path }.Concat((data.info ?? Enumerable.Empty<Data>())
    .SelectMany(child => Paths(child, path.Concat(new[] { child }).ToArray())));
}

All Linq operators use a loop in some way so it is not possible to solve with linq if the requirement is no loop.

It is possible without recursion. You just need a stack. Something like

public static IEnumerable<Tuple<int, T>> FlattenWithDepth<T>(T root, Func<T, IEnumerable<T>> children) {
    var stack = new Stack<Tuple<int, T>>();

    stack.Push(Tuple.Create(1, root));

    while (stack.Count > 0) {
        var node = stack.Pop();

        foreach (var child in children(node.Item2)) {
            stack.Push(Tuple.Create(node.Item1+1, child));
        }
        yield return node;
    }
}

Your linq query will then be

FlattenWithDepth(root, x => x.info ?? Enumerable.Empty<Data>()).Max(x => x.Item1);

(sorry don't have a compiler available to verify)

** Edit. Just saw that you had multiple roots **

list.SelectMany(y => FlattenWithDepth(y, x => x.info ?? Enumerable.Empty<Data>()))
    .Max(x => x.Item1)

The following would work:

internal static class ListDataExtension
{
    public static int MaxDepthOfTree(this List<Data> dataList)
    {
        return dataList.Max(data => data.MaxDepthOfTree);
    }
}
internal class Data
{
    public int number;
    public List<Data> info;

    public int MaxDepthOfTree
    {
        get 
        { 
            return GetDepth(1); 
        }
    }

    int GetDepth(int depth)
    {
        if (info == null)
            return depth;
        var maxChild = info.Max(x => x.GetDepth(depth));
        return maxChild + 1;
    }
}

Then just call:

var maxDepth = list.MaxDepthOfTree();

If you should use it in DB, I would suggest add additional column to your DB to save the depth of tree, there are some situation which depth changes:

  1. Adding new element, its depth is FatherDepth + 1.
  2. Delete: If this is cascade relation there is no problem with deleting, but if it's not cascade you should write a recursive query to update children's depth.

For finding depth just run a query to find the maximum depth value.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM