Use Linq to flatten a nested list instead of a foreach loop

Viewed 2662

I have a recurring class - I've simplified it below.

public class SiloNode
{
    public string Key { get; private set; }
    public string Url { get; private set; }
    public List<SiloNode> Children { get; private set; }
}

Although, in theory, it could nest forever, the nodes will only ever go down two levels. So, a top-level node can have a child, but a child can't have a child.

I have a master list containing all of the top level nodes with their nested child nodes.

However, I need to get all nodes into a single flat list - node 1, followed by its children, followed by node 2 etc.

My knowledge in this area is limited, but I could do something like a foreach over the master list and create a new list, like this:

public IEnumerable<SiloNode> GetLinks(IEnumerable<SiloNode> masterList)
{
    var newList = new List<SiloNode>();

    foreach (var node in masterList)
    {
        newList.Add(node);
        newList.AddRange(node.Children);
    }

    return newList;
}

However, I know there's probably a much better way available but I just can't work out how to convert the foreach into a Linq statement that will do the same thing. In other words, select the parent and its children together.

Any help appreciated.

5 Answers

You can use SelectMany, just concat the single parent and it's children:

List<SiloNode> newList = masterList.SelectMany(n => new[]{ n }.Concat(n.Children)).ToList();

If you want to preserve the order and flatten more than one level of children I think something like this will work just fine. var nodes = GetLinks(masterList); or use it in a loop for(var node in GetLinks(masterList))

public IEnumerable<SiloNode> GetLinks(IEnumerable<SiloNode> masterList)
{
    foreach(var node in masterList) 
    {
        yield return node;
        foreach(var children in GetLinks(node.Children))
            yield return children;
    }
}

masterList contains already the parent node, and concat the child nodes

var nodes = masterList.Concat(masterList.SelectMany(x=> x.Children));

If there are only going to be two levels, something like that should do:

public IEnumerable<SiloNode> GetLinks(IEnumerable<SiloNode> masterList)
{
    return masterList.SelectMany(m => new SiloNode[] { m }.Concat(m.Children));
}

I've seen this answered before, but couldnt find the question. This does what you want without recursing really deep in the node structure:

public IEnumerable<SiloNode> Flatten(IEnumerable<SiloNode> nodes)
{
    var queue = new Queue<SiloNode>();

    foreach (var node in nodes)
    {
        queue.Enqueue(node);
    }         

    while (queue.Count > 0)
    {
        var node = queue.Dequeue();

        foreach (var child in node.Children)
        {
             queue.Enqueue(child);
        }

        yield return node;
    }
}

EDIT: this doesn't preserve the ordering you want, but you can use a stack instead of queue.

Related