2010-11-23 16 views
5

considerar lo siguiente:LINQ y recursividad

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; 
} 

¿Hay alguna manera de que FindBoxBySize() se puede compactar usando LINQ? Además: los comentarios sobre mi código son bienvenidos. No hago mucha recursión, así que podría haberme perdido algo en mi implementación.

Respuesta

5

También me tomo el enfoque método de extensión, pero el uso de un método iterador:

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; 
      } 
     } 
    } 
} 

Su método FindBoxBySize ahora se convierte en:

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

Su código de llamada original, funciona sin modificación:

var small = FindBoxBySize(box, BoxSize.Small); 
2

Los métodos de extensión:

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); 
    } 
} 

Uso:

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); 
+0

Creo que sí. Pero fíjate en mi declaración de ruptura; Realmente me gustaría que haya tan poco "bucle" como sea posible, de modo que cada vez que se encuentre una coincidencia, me gustaría terminar. – roosteronacid

+0

Esto sigue siendo incorrecto. Explota en 'nulo', que es claramente importante para el OP. En segundo lugar, en realidad nunca produce una 'caja' real. – Ani

+0

No. ¿Cuándo se produce realmente una referencia de cuadro * real *? – Ani

1

Si desea utilizar LINQ, que podría hacer algo como esto (no probado):

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); 
0

usted podría hacer su recursión un poco más clara (en mis ojos) escribiendo

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; 
} 

En cuanto a LINQ: LINQ no viene con una buena manera de manejar estructuras de datos recursivas. Varios métodos de extensión diferentes para remediar que se pueden encontrar pidiendo a Google "recursividad LINQ", pero no estoy seguro si agregan claridad en este caso.