8

Consider this hierarchy

                     A
                     |
        --------------------------
        |                        |
        B                        C
        |                        |
--------|--------            ---------
|       |       |            |       |
D       E       F            G       H
|       |                    |       |
|   ---------             -------    |
|   |       |             |     |    |
I   J       K             L     M    N

Every object has a Parent property and an Items collection for the choldren, so for instance, E has a parent of B, N of H, etc. A has a value of null for the parent. B.Items contains D-F, etc.

What is a LINQ statement that I can sort these by their level? I don't care about the sort order within a level (i.e. the order of D-H doesn't matter, but they have to come after B and C which have to come after A.

Only way I can think is two separate linq statements:

  1. Run an aggregate over this, calculating and storing the levels as you go
  2. Run a second LINQ query over the results ordering by Level.

B is easy of course. It's A that I'm struggling with. I can do it procedurally of course, but I have to think this can be reduced to a LINQ statement.

Mark A. Donohoe
  • 28,442
  • 25
  • 137
  • 286

4 Answers4

8

You didn't specify what's the target of your query, so there are multiple correct answers:

  1. LINQ to SQL or LINQ to Entities - support for recursive queries doesn't exist, so you'd either had to load the data into memory and perform LINQ to Objects query, or use a Stored Procedure in the database (most likely using Common Table Expression). You could also prepare a VIEW in the database and map it to your EF model.

  2. LINQ to Objects is better suited to the job, but IMO you're still best with a simple method that calculated depth:

    public static int GetDepth(Item item)
    {
        int d = 0;
        while ((item = item.Parent) != null)
            d++;
        return d;
    }
    

    and the query is super simple later on

    var results = from item in data
                  let depth = GetDepth(item)
                  orderby depth descending
                  select item;
    

    It would be easy to write just one LINQ to Objects query if your data structure was different, and Parent had links to all the children. In that case querying the data is easier, because each item has a collection of dependant items, and LINQ works well with collections, in oposite to single items, like your Parent property. I wrote a blogpost about querying hierarchy of objects using LINQ a while ago, you might find it interesting.

MarcinJuraszek
  • 124,003
  • 15
  • 196
  • 263
  • Re *It would be easy to write just one LINQ to Objects query if your data structure was different* and your blog post - it's easy to write just one LINQ to Objects query for *anything* if you include "writing your own extension method" in that... – Rawling Aug 04 '15 at 07:17
  • The items do know their children as well. Each has a parent, and a collection of children called Items. And when a person specifies LINQ, not Linq-to-xxx, you should always assume they just mean System.LINQ over objects. :) – Mark A. Donohoe Aug 04 '15 at 07:18
  • Neat solution, but it could be enhanced if you BFS the graph **just once**, attaching each node it's depth, and then using a simple `OrderBy(p=> p.Depth)`. – shay__ Aug 04 '15 at 07:20
  • The graph is constantly changing. Nodes are being added and removed all the time. The problem is the nodes can come in out of order, so that's why I need this function. I can guarantee the lower nodes get added first. – Mark A. Donohoe Aug 04 '15 at 07:30
2

Although my friends have posted some good answers, I'm wiling to provide another answer. If it's feasible to change the node structure for better performance you can set the depth property for every node once and then sort them based on the depth property.

public class Node
{
    public Node()
    {
        this.Items = new List<Node>();
        this.Parent = null;
        this.Depth = 0;
    }
    public Node Parent { get; set; }
    public List<Node> Items { get; set; }
    public int Depth { get; set; }
}

public void SetDepths(Node node, int depth)
{
    node.Depth = depth;
    foreach (var child in node.Items)
        SetDepths(child, depth + 1);
}
public void Sort(List<Node> nodes)
{
    SetDepths(nodes.Single(x => x.Parent == null), 1);
    nodes = nodes.OrderBy(x => x.Depth).ToList();
}

The recursive function should be called like :

SetDepths(rootNode, 1);

which here it's called in the sort method or you van choose any other places to do that.

Mohammad Chamanpara
  • 2,049
  • 1
  • 15
  • 23
0

There are a few methods to solve this problem.

First, you can implement method, f.e. AsFlattenEnumerable. Imaging, you have the class Tree<T> that have node datas of type T.

Declare class Flatten<T>:

public Flatten<T>
{
    public T Data { get; }

    public int Level { get; }
}

Then implement AsFlattenEnumerable:

public class Tree<T>
{
. . .
    public IEnumerable<Flatten<T>> AsFlattenEnumerable()
    {
        . . .
    }

. . .
}

Now you can sort nodes with simple the LINQ query:

var ordereNodes = from node in tree.AsFlattenEnumerable()
                  // F.e. first Level, then A, then B
                  order node by new { node.Level, node.Data.A, node.Data.B };

Second, C# compiler allows you to declare your own methods for the order by clause (and the OrderBy method, of course).

Imaging, you have a Tree<T> class that implements a ITreeEnumerable<T> interface (there's similar hierarchical interface in System.Web assembly).

Then you could define your own extension methods like Where and OrderBy:

public static ITreeEnumerable<TSource> OrderBy<TSource, TKey>(this ITreeEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
    . . .
}

Now you can sort your enumerables with LINQ:

var orderedNodes = from node in tree // tree implements ITreeEnumerable<T>
                   // First Level (by default), then custom ordering by A and B
                   order by new { node.A, node.B };
. . .
var enumerableNodes = orderedNodes.AsEnumerable();

The C# compiler converts this expression:

var result = from i in obj
             order i by i.Foo;

to this:

var result = obj.OrderBy(i => i.Key);

The obj variable can by of any type, not necessarily IEnumerable. The type of obj should have OrderBy method or you can implement extension method.

Mark Shevchenko
  • 7,937
  • 1
  • 25
  • 29
0

Shameless plug - you could add my Nuget package that I have been working on called Treenumerable:

https://www.nuget.org/packages/Treenumerable

https://github.com/jasonmcboyd/Treenumerable

You do have to implement an ITreeWalker interface that knows how to traverse your tree (two methods). Once you do that then you can simply do this:

// Pseudocode
TreeWalker walker = new TreeWalker();
IEnumerable<YourType> levelOrder = walker.LevelOrderTraversal(A);

You also get a dozen (more if you count overloads) or so other methods for enumerating/querying tree structures. The current version - 1.2.0 - has good test coverage and seems to be quite stable.

Community
  • 1
  • 1
Jason Boyd
  • 6,839
  • 4
  • 29
  • 47