HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Flatten a tree to a collection of nodes

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
nodescollectiontreeflatten

Problem

I have implemented simple tree class as follows:

public class MoveTreeNode
{
    public MoveTreeNode Parent
    {
        get;
        set;
    }

    private List children = new List();

    public IEnumerable Children
    {
        get
        {
            return children;
        }
    }

    public void Add(MoveTreeNode child)
    {
        child.Parent = this;
        children.Add(child);
    }

    private IEnumerable Flatten()
    {
        yield return this;

        foreach(var child in Children.Select(x=> x.Flatten()).SelectMany(x=>x))
        {
            yield return child;
        }
    }
}


This works, but I have concerns about the following line:

foreach(var child in Children.Select(x=> x.Flatten()).SelectMany(x=>x))

Since I'm using yield I'd prefer to gain some of the benefits of lazy evaluation but I fear that while each child of the root will be lazily evaluated, the childrens' children, etc will be evaluated at once when their parents are evaluated.

Is there some way I can refactor this to make the most of lazy evaluation, or am I better off leaving it this way?

Solution

I think you want to flatten each child in turn:

private IEnumerable Flatten()
{
    yield return this;

    foreach (var node in Children.SelectMany(child => child.Flatten()))
    {
        yield return node;
    }
}


That way you won't flatten the whole tree first.

Edit:

It's more difficult to talk about things so let's look at some code. SelectMany is roughly equal to:

public static IEnumerable SelectMany(this IEnumerable source, Func> selector)
{
    foreach (var item in source)
    {
        foreach (var subItem in selector(item))
        {
            yield return subItem;
        }
    } 
}


So as you can see, we will definitely get all of the nodes and if you stop iterating before the end (e.g. by calling .First()) you will only call Flatten() on a single child.

Code Snippets

private IEnumerable<MoveTreeNode> Flatten()
{
    yield return this;

    foreach (var node in Children.SelectMany(child => child.Flatten()))
    {
        yield return node;
    }
}
public static IEnumerable<TOut> SelectMany<T, TOut>(this IEnumerable<T> source, Func<T, IEnumerable<TOut>> selector)
{
    foreach (var item in source)
    {
        foreach (var subItem in selector(item))
        {
            yield return subItem;
        }
    } 
}

Context

StackExchange Code Review Q#87428, answer score: 6

Revisions (0)

No revisions yet.