1

I have a table (let's say DataTable for this example) which has hierarchy records ( id,ParentId,IsEmergency) :

enter image description here

ParentId=0 means root.

So we have this :

0
|
+--- 4706606
|
+--- 4706605
|
+--- 4666762
        |
        +--- 4668461
               |
               +--- 4706607 

I'm after displaying only the those records who has parentId=0

But I want to add another logical column which states "this parent has IsEmergency=true in one of it's childs/sub childs

So I'm after

id         parentId        hasEmergencyInOneOfItsChild
-------------------------------------------------------
4706606    0                false
4706605    0                false
4666762    0                true

Question:

How can I achieve it via recursive linq ?

For convenience I created this Datatable :

   DataTable dt = new DataTable("myTable");
   dt.Columns.Add("id", typeof (int));
   dt.Columns.Add("parentId", typeof (int));
   dt.Columns.Add("isEmergency", typeof (bool));

   DataRow row = dt.NewRow();
   row["id"] =4706606;
   row["parentId"] = 0;
   row["isEmergency"] =false;
   dt.Rows.Add(row);

       row = dt.NewRow();
   row["id"] =4706605;
   row["parentId"] = 0;
   row["isEmergency"] = false;
   dt.Rows.Add(row);

       row = dt.NewRow();
   row["id"] =4666762;
   row["parentId"] = 0;
   row["isEmergency"] =false;
   dt.Rows.Add(row);

       row = dt.NewRow();
   row["id"] =4668461;
   row["parentId"] = 4666762;
   row["isEmergency"] = false;
   dt.Rows.Add(row);

        row = dt.NewRow();
   row["id"] =4706607;
   row["parentId"] = 4668461;
   row["isEmergency"] = true;
   dt.Rows.Add(row);

I've tried something but it is pretty nasty and not efficient

Royi Namir
  • 144,742
  • 138
  • 468
  • 792
  • You're looking for a Linq query that will retrieve the data from where? What is the actual data source? – haim770 Mar 15 '15 at 15:10
  • @haim770 No. I'm talking about Linq In Memory for an already existing data source (in memory- datatable). there is no SQL involved here. – Royi Namir Mar 15 '15 at 15:11
  • @haim770 it's like a forum. each question has a parent question. – Royi Namir Mar 15 '15 at 15:13
  • Did you look at this answer http://stackoverflow.com/questions/4814242/linq-recursion-function – CheGueVerra Mar 15 '15 at 15:14
  • @haim770, run his code in LINQPad and you will have the datasource – CheGueVerra Mar 15 '15 at 15:15
  • 4
    "I've tried something but it is pretty nasty and not efficient" -- what did you try? Did it work? In what _specific_ way is it different from what you want? Please provide [a good, _minimal_, _complete_ code example](http://stackoverflow.com/help/mcve) showing what you've got so far. – Peter Duniho Mar 15 '15 at 15:19
  • @PeterDuniho No it didnt work. I went to my other question regarding [recursive linq](http://stackoverflow.com/questions/21086502/recursive-linq-and-this) but couldn't adopt it to work – Royi Namir Mar 15 '15 at 15:28
  • This isn't a LINQ problem. LINQ isn't the right instrument here. Standard coding is. And a Dictionary<> probably. As you have written it, you don't really have a tree-like structure. You only have a bunch of records with an id. – xanatos Mar 15 '15 at 15:28
  • @xanatos I didn't say that Linq is a problem here. It's jsut that I wanted to do it via linq - hence the tag – Royi Namir Mar 15 '15 at 15:30
  • @RoyiNamir And I'm telling you that LINQ isn't the right instrument. – xanatos Mar 15 '15 at 15:30
  • @closers - did I really asked : "why isn't the code working ?" – Royi Namir Mar 15 '15 at 15:31
  • @xanatos Okay. I'll wait to see if there's answers , if not , i'll delete and do it manually. :-) – Royi Namir Mar 15 '15 at 15:32
  • @RoyiNamir - it's not that you've asked "why isn't the code working?", it's because you've stated: "I've tried something but it is pretty nasty and not efficient", yet have not shown what you've tried. – Metro Smurf Mar 15 '15 at 15:51

2 Answers2

3

One approach is to define a data structure that holds the relevant properties and a recursive property to determine whether it is in an isEmergency hierarchy:

internal class Node
{
    internal string Id { get; set; }
    internal IEnumerable<Node> Children { get; set; } 
    internal bool IsEmergency { get; set; }

    internal bool IsEmergencyHierarchy
    {
        get { return IsEmergency || 
                     (Children != null && Children.Any(n => n.IsEmergencyHierarchy)); }
    }
}

Then you can construct the tree like this:

// convert rows to nodes
var nodes = dt.Rows.Cast<DataRow>().Select(r => new Node
{
    Id = r["id"].ToString(),
    ParentId = r["parentId"].ToString(),
    IsEmergency = (r["isEmergency"] as bool? == true)
}).ToList();

// group and index by parent id
var grouped = nodes.GroupBy(n => n.ParentId).ToDictionary(g => g.Key);

// match up child nodes
foreach (var n in nodes)
{
    n.Children = grouped.ContainsKey(n.Id) 
        ? (IEnumerable<Node>)grouped[n.Id] 
        : new Node[0];
}

// select top nodes
var top = grouped.ContainsKey("0") 
    ? (IEnumerable<Node>)grouped["0"] 
    : new Node[0];

You can check the result like this:

foreach (var t in top)
{
    Console.WriteLine("{0} - {1}", t.Id, t.IsEmergencyHierarchy);
}

Output:

4706606 - False
4706605 - False
4666762 - True
JLRishe
  • 99,490
  • 19
  • 131
  • 169
1

Given your data:

var lookup = dt.Rows.Cast<DataRow>().Select(x => new
{
    id = (int)x["id"],
    parentId = (int)x["parentId"],
    isEmergency = (bool)x["isEmergency"],
}).ToLookup(x => x.parentId);

I build a lookup "sorted" by parentid (let's say it's a like a dictionary but with multiple values for each key)

// Taken from http://stackoverflow.com/a/21086662/613130
public static IEnumerable<T> SelectSelfDescendents<T>(IEnumerable<T> source, Func<T, IEnumerable<T>> selector)
{
    foreach (var item in source)
    {
        yield return item;

        foreach (T item2 in SelectSelfDescendents(selector(item), selector))
            yield return item2;
    }
}

A recursive function to return recursively the input collection plus the descendents

var roots = lookup[0].Select(x => new
{
    x.id,
    // x.parentId // useless, it's 0
    isEmergency = SelectSelfDescendents(new[] { x }, y => lookup[y.id]).Any(y => y.isEmergency),
    childs = SelectSelfDescendents(new[] { x }, y => lookup[y.id]).ToArray()
}).ToArray();

A little Linq to handle everything :-)

lookup[0]

We start with the "root" elements (the elements that have parentId == 0)

This one

.SelectSelfDescendents(new[] { x }, y => lookup[y.id])

given an ienumerable returns the ienumerable plus all its recursive childrens.

new[] { x }

Transforms the "current" root element in a collection of a single element.

y => lookup[y.id]

This is a descendent selector: given an element it finds in the lookup all the childs that have y.id as a parent (because the lookup is by parentId)

So in the end the SelectSelfDescendents will return new[] { x } concatenated with all its "descendents"

.Any(y => y.isEmergency)

if any isEmergency is true returns true

Now, as you noticed, I'm recalculating SelectSelfDescendants twice (once for isEmergency and once for childs)... In truth childs was a property I had added to "debug" the recursive enumeration, so it can be removed if it is not necessary. If you want to keep it, you can use the let keyword (or "expand" the let keyword as you've shown you did in chat... it is the same thing). Note that I "cache" the SelectSelfDescendents with a .ToArray() (as you did). Without it, it would still be evaluated twice, because it returns an IEnumerable<>

var roots = (from x in lookup[0]
             let childs = SelectSelfDescendents(new[] { x }, y => lookup[y.id]).ToArray()
             select new
             {
                 x.id,
                 // x.parentId // useless, it's 0
                 isEmergency = childs.Any(y => y.isEmergency),
                 childs 
             }).ToArray();
xanatos
  • 109,618
  • 12
  • 197
  • 280