5

Consider the following:

public class Box
{
    public BoxSize Size { get; set; }

    public IEnumerable<Box> Contents { get; set; }
}

Box FindBoxBySize(Box box, BoxSize size)
{
    Box _foundBox = null;

    Action<IEnumerable<Box>> _recurse = null;

    _recurse = new Action<IEnumerable<Box>>(boxes =>
    {
        foreach (var _box in boxes)
        {
            if (_box.Size == size)
            {
                _foundBox = _box;

                return;
            }

            if (_box.Contents != null) _recurse(_box.Contents);
        }
    });

    _recurse(box.Contents);

    return _foundBox;
}

Is there any way that FindBoxBySize() can be compacted using LINQ? Also: comments on my code are welcome. I don't do much recursion, so I might have missed something in my implementation.

4 Answers 4

5

I'd also take the extension method approach, but use an iterator method:

public static class BoxEx
{
    public static IEnumerable<Box> Flatten(this Box box)
    {
        yield return box;
        if (box.Contents != null)
        {
            foreach (var b in box.Contents.SelectMany(b2 => Flatten(b2)))
            {
                yield return b;
            }
        }
    }
}

Your FindBoxBySize method now becomes:

Box FindBoxBySize(Box box, BoxSize size)
{
    return (from b in box.Flatten()
            where b.Size == size
            select b).FirstOrDefault();
}

Your original calling code works without modification:

var small = FindBoxBySize(box, BoxSize.Small);
Sign up to request clarification or add additional context in comments.

Comments

2

Extension methods:

class Box
{
    public IEnumerable<Box> GetBoxes()
    {
        // avoid NullReferenceException
        var contents = this.Contents ?? Enumerable.Empty<Box>();

        // do the recursion
        return contents.SelectMany(b => b.GetBoxes()).Concat(contents);
    }

    public Box GetBox(int size)
    {
        return this.GetBoxes().FirstOrDefault(b => b.Size == size);
    }
}

Usage:

var box_with_box = new Box { Contents = new[] { new Box() { Size = 10 } } };
var box = new Box { Contents = new[] { box_with_box, box_with_box } };

Box box10 = box.GetBox(10);

6 Comments

I think it does. But notice my break-statement; I'd really like there to be as little "looping" as possible, so whenever a match is found, I'd like to terminate.
This is still wrong. It blows up on null, which is clearly important to the OP. Secondly, it never actually yields a real box.
No. When is a real box reference actually being yielded?
@Ani: Yea, I see. But I haven't examined it, really. Indeed I was rewriting my own extension method (for ASP.NET, recursion for Control.FindControl()) but with lack of attention :)
Also, it is not a good pratice to write an extension method to your own classes. Why to use an EM when you could make it a public instance method of Box class? Object Oriented concepts as inheritance should not be easily replaced by EM. I really like EM and wrote a lot of then. It is a very usefull technique to be used when you want to extend a Type of an external library (including .net framework itself) and don't want (or can't) to create a derived Type. But overuse this technique leads to a model that just isn't object oriented, what is usually undesirable. Greetings.
|
1

If you want to use LINQ, you could do something like this (untested):

public static IEnumerable<Box> GetBoxesRecursive(this Box box)
{
  if(box == null)
      throw new ArgumentNullException("box");

  var children = box.Contents ?? Enumerable.Empty<Box>();

                           // use lambda in C# 3.0      
  var recursiveChildren = children.SelectMany(GetBoxesRecursive);  

  return new[] { box }.Concat(recursiveChildren);                                    
}

...    

Box desiredBox = myBox.GetBoxesRecursive()
                      .FirstOrDefault(b => b.Size == desiredSize);

Comments

0

You could make your recursion a little clearer (in my eyes) by writing

Box FindBoxBySize(Box box, BoxSize size)
{
    if (box.Size == size)
        return box;

    foreach (var child in box.Contents)
    {
        var foundBox = FindBoxBySize(child, size);
        if(foundBox != null)
            return foundBox;
    }

    return null;
} 

As for LINQ: LINQ does not come with a good way to handle recursive data structures. Several different Extension methods to remedy that can be found by asking google for "LINQ recursion", but I am unsure if they add clarity in this case.

Comments

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.