1

I am trying to write a recursive lambda expression for going through this following structure.

NodeID ParentNodeID IsChecked
  1        null        false
  2         1          false
  3         1          false
  4         1          false
  5         1          false
  6         2          false
  7         2          false
  8         2          false
  9         2          false
 10         6          false
 11         6          false
 12         6          false
 13         3          false
 14         3          false
 15         13         false
 16         13         false

//Now i have a List<Int32> checkedNodes Which has 2,3 in it.
List<Int32> checkedNodes = new List<Int32>({2,3});
//I can write a lambda expression which will set these nodes checked value to true.
myList.Where(x => checkedNodes.Contains(x.NodeID)).ToList().ForEach(x => x.Checked = true);

I want to write my lambda expression to set checked for all children and sub children. please Help.

I am open to alternative solution as well if this is not possible.

6
  • Are you sure you want to use LINQ? You cannot do a recursive LINQ without a recursive function. Commented Jul 3, 2015 at 5:25
  • @YeldarKurmangaliyev I am open for all suggestions. Commented Jul 3, 2015 at 5:27
  • With he way the Linq is designed, probably you can not. I think your best option would be to write it using a normal loop. I guess that would be more readable and easier to write Commented Jul 3, 2015 at 5:28
  • What is Rights in your second query ? is it IsChecked ? Commented Jul 3, 2015 at 5:29
  • @SpiderCode you are right Commented Jul 3, 2015 at 5:46

4 Answers 4

3

You cannot build a recursive process with LINQ only.
You will need a recursive function.

That's, in my opinion, is an appropriate and elegant solution:

private static void Traverse(Node node)
{
    node.Checked = true; 
    _nodes.Where(x => x.ParentId == node.Id).ToList().ForEach(Traverse);
}

public static void Check(params int[] values)
{
    values.Select(item => _nodes.Single(x => x.Id == item)).ToList().ForEach(Traverse);
}

Traverse is a recursive function which checks the node and calls itself for all its child nodes.
Check is just a function which calls Traverse for every node by given IDs list.

For example, you can wrap it in a public static helper class and it will be convenient to use it:

public static class NodeRecursiveChecker
{
    private static List<Node> _nodes;
    private static void Traverse(Node node)
    {
        node.Checked = true; 
        _nodes.Where(x => x.ParentId == node.Id).ToList().ForEach(Traverse);
    }

    public static void CheckNodes(this List<Node> nodes, params int[] values)
    {
        _nodes = nodes;
        values.Select(item => _nodes.Single(x => x.Id == item)).ToList().ForEach(Traverse);
    }
}

Then, you can use it this way:

list.CheckNodes(6, 13); // Mark 6, 13 and their children
list.CheckNodes(1); // Mark everything (as 1 is a root in your case)

Here is the DotNetFiddle Demo.

P.S. Note, that I am using LINQ Single function which will throw an exception if there will be no such element in a collection. For example, if you call nodes.CheckNodes(11) and there will be no Node with Id = 11 - it will throw exception. Of course, you can add a check for existence in CheckNodes but it is redundant if you are sure that you will pass only existing Ids. That's why I haven't used it.

Sign up to request clarification or add additional context in comments.

Comments

2

Maybe this is what you're looking for:

Func<Node, bool> predicate = null;
predicate = x => checkedNodes.Contains(x.NodeId) || (x.Parent != null && predicate(x.Parent));

myList.Where(predicate).ToList().ForEach(x => x.IsChecked = true);

Sorry I don't know the declaration of your type containing NodeId, Parent and IsChecked. So I was guessing a little bit.

Maybe you can add an extension method for IEnumerable<T> to have ForEach and remove the ToList call that creates a copy of your collection.

Comments

1

You can write where clause something like this:

nodes.Where(node => 
        checkedNodes.Contains(node.Id) || 
        (node.ParentId != null && checkedNodes.Contains((int)node.ParentId))
        ).ToList().ForEach(each => { each.IsChecked = true; });

Have a look at complete demo example Here

4 Comments

This code is incorrect and works only for depths up to 2. For example, if we have a structure like 1-2-3-4-5-6-7-8-9 and pass "1" - it should check all nodes while your algorithm checks only 1 and 2. Here is the example based on your demo: dotnetfiddle.net/s8LGnk.
@YeldarKurmangaliyev: you mean in this case 2 should also be considered as a parent node ?
In the code sample which I have provided, 1 is a parent of 2; 2 is a parent of 3; 3 is a parent of 4; 4 is a parent of 5 etc. It is like a chain. So, if you check the first element, absolutely all elements should become checked :)
@YeldarKurmangaliyev: Aaah ok. Now I understood exact requirement. Thanks for the clarification :)
1

This is not Linq , but one way of doing this. With the way LInq is designed, your question should be impossible

List<int>  bag = new List<int>();
checkedNodes.ForEach(x=> bag.Add(x));

data.OrderBy(x=>x.ParentNodeId).ToList().ForEach(item=>{
    if(bag.Contains(item.ParentNodeId)){
        bag.Add(item.NodeId);
        item.IsChecked = true;
    }
});

This solution uses a bag to keep list of all parents that are already checked. It is not very efficient, but will work for small collections.

Note that I am sorting based on parent node id first, to reach parents before children. Also note that I am adding newly found parents to the bag.

4 Comments

@noob this will only get the direct children for current checked nodes, not the sub children.
It isn't impossible. Linq requests a predicate for Where. This predicate could be recursive and it could be a lambda - and of course a recursive lambda.
@VinayPratapSingh , please note I add any newly detected item to my bag. so it will find all nodes. I updated answer to explain this
@Verarind , I understand our solution, and I think that will also work. The efficiency might still not be very good

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.