Royi Namir
Royi Namir

Reputation: 148624

Flat List<T> to structured List<T> via recursion in C#?

I have an existing class :

public class Product
{
 public int Id { get; set; }
 public int ParentId { get; set; }
 public string Info { get; set; }
}

And I also have a given List<Product>:

var Lst = new List<Product>();
Lst.Add(new Product{ Id=1,ParentId=0,Info="a"});
Lst.Add(new Product{ Id=2,ParentId=0,Info="a"});
Lst.Add(new Product{ Id=3,ParentId=0,Info="a"});
Lst.Add(new Product{ Id=60,ParentId=1,Info="a"});
Lst.Add(new Product{ Id=61,ParentId=1,Info="a"});
Lst.Add(new Product{ Id=62,ParentId=61,Info="a"});
Lst.Add(new Product{ Id=72,ParentId=61,Info="a"});
Lst.Add(new Product{ Id=90,ParentId=2,Info="a"});

Visualization :

1
|
+---60
|
+---61
     |
     +---62
     |
     +---72

2
|
+---90

3

As you can see , the List<> is flat. (all items are in the same level within the list. it's just that the id,parentId represents heirarchy)

Now - I need to create structural List<> so each item in List will be inside its parent object :

so I created an additional structure class which will hold this structure :

public class Node
{
 public Product Product { get; set; }
 public List<Node> LstNodes  { get; set; }
}

So now I can do :

List<Node> lstNodes = new List<Node>();

And initially I can add the root ones :

lstNodes=Lst.Where(product=>product.ParentId==0).Select(node=>new Node{Product=node}).ToList();

And now I can start recursion to insert items with their parents.

So Where is the problem ?

Question:

I want to avoid insertion of root elements first ( root is where ParentId=0).

Is there any way of doing this with one recursive method (including roots) ?

Desired result : 3 nodes in lstNodes where each has its children recursively.

Upvotes: 2

Views: 554

Answers (3)

3dGrabber
3dGrabber

Reputation: 5074

The solutions proposed so far work, but iterate over the entire collection once per node.
This makes them O(n2) and not suitable for large collections (databases, ...).
Here is a solution running in linear time:

var products = new List<Product>();
products.Add(...);
...

var nodes          = products.Select(p => new Node { Product = p }).ToList(); // 1
var nodeWithId     = nodes.ToDictionary(n => n.Product.Id);                   // 2
var parentChildren = nodes.Where(n => n.Product.ParentId != 0)
                          .ToLookup(n => nodeWithId[n.Product.ParentId]);     // 3

foreach (var pc in parentChildren)  // 4
{
    var parent   = pc.Key;
    var children = pc.ToList();

    parent.LstNodes = children;
}

Explanation:

  1. Create the nodes from the products.
  2. Create a dictionary that allows us to easily find a Node with a given id
  3. Create a lookup (dictionary with multiple values per key) that gives us the children of a given node.
  4. Iterate over the lookup an associate the children with their parents

Upvotes: 1

Euphoric
Euphoric

Reputation: 12849

Something like this?

List<Node> GetNodes(List<Product> lst, int parentId = 0)
{
    var childProducts = lst.Where(x=>x.ParentId == parentId);
    return childProducts
           .Select(x=> new Node { Product = x, LstNodes = GetNodes(lst, x.Id)}
           .ToList();
}

And purely generic version:

class Node<T>
{
    public T Item { get; set; }
    public List<Node<T>> LstNodes  { get; set; }
}

List<Node<T>> GetNodes<T>(List<T> lst, Func<T, int> idSelector, Func<T, int> parentIdSelector, int parentId = 0)
{
    var childProducts = lst.Where(x=>parentIdSelector(x) == parentId);
    return childProducts
           .Select(x=> new Node<T> { Item = x, LstNodes = GetNodes<T>(lst, idSelector,parentIdSelector, idSelector(x))})
           .ToList();
}

GetNodes(Lst,x=>x.Id,x=>x.ParentId, 0);

Upvotes: 4

Jeffrey Zhang
Jeffrey Zhang

Reputation: 438

You can try this:

    static List<Node> BuildTree(List<Product> lst, int topID) 
    {
        var tree = Enumerable.Empty<Node>().ToList();
        if (lst == null)
        {
            return tree;
        }

        foreach (var product in lst)
        {
            if (product.ParentId == topID)
            {
                Node node = new Node 
                {
                    Product = product,
                    LstNodes = BuildTree(lst.Where(p => p.ParentId == product.Id).ToList(), product.Id)
                };

                tree.Add(node);
            }
        }

        return tree;
    }

And you can call: List<Node> roots = BuildTree(Lst, 0);

Upvotes: 1

Related Questions