2010-07-27 12 views
4

J'ai la méthode suivante qui fonctionne bien, sauf que l'instruction de rupture de rendement ne sort que de l'énumérateur actuel. Je comprends pourquoi c'est le cas, mais je dessine un blanc sur la façon de propager la rupture de rendement à travers la pile récursive.Comment puis-je sortir des boucles IEnumerable IEnumerable <T> en utilisant la rupture de rendement?

private static IEnumerable<Node> FindChildrenById(IEnumerable nodes, string parentText) { 
     var en = nodes.GetEnumerator(); 
     var targetFound = false; 
     while (en.MoveNext()) { 
      var node = en.Current as Node; 
      if (node != null) 
      { 
       if (node.Parent == null && string.IsNullOrEmpty(parentText)) 
       { 
        //Returns the top level nodes if an empty parentIdis entered 
        targetFound = true; 
        yield return node; 
       } 
       else if (node.Parent != null && node.Parent.Text == parentText) 
       { 
        //returns the nodes belonging to the parent 
        yield return node; 
       } 
       else 
       { 
        //Recurse into the children to see whether one of these is the node to find 
        foreach (var nd in FindChildrenById(node.Nodes, parentText)) 
        { 
         yield return nd; 
        } 
       } 
      } 
     } 
     if (targetFound) 
     { 
      yield break; 
     } 
    } 

Alors, quand j'ai les nœuds suivants et passe "Top 2 a" comme parentText ...

Top 1 
    Top 1 a 
    Top 1 b 
Top 2 
    Top 2 a 
     Top 2 aa 
     Top 2 ab 
     Top 2 ac 
    Top 2 b 
Top 3 
    Top 3 a 
    Top 3 b 
Top 4 

... puis-je obtenir le résultat:

Top 2 aa 
Top 2 ab 
Top 2 ac 

C'est le résultat correct, cependant, lorsque je parcours mon code, la boucle la plus extérieure continue à traiter les 3 premiers et 4 premiers. Comment puis-je sortir de cette boucle externe?

Répondre

2

Si je suis bien votre code, je suppose que le code ci-dessous résoudra votre problème

private static IEnumerable<Node> FindChildrenById(IEnumerable nodes, string parentText) 
    { 
     var result = 
       (from node in nodes 
       where (node.Parent == null && string.IsNullOrEmpty(parentText)) 
         || (node.Parent != null && node.Parent.Text == parentText) 
       select node).TakeWhile(node => !(node.Parent == null && string.IsNullOrEmpty(parentText))); 
     return result; 
    } 

Il est construit sur deux méthodes d'extension (voir ci-dessous) et s Hould seulement itérer jusqu'à ce que votre cible trouvée critères sont respectés

public static class IEnumerablExtensions 
     { 
      //Will iterate the graph in depth first order 
      public static IEnumerable<TResult> Select<TResult>(this IEnumerable collection, Func<Node, TResult> selector) 
      { 
       foreach (var obj in collection) 
       { 
        var node = obj as Node; 
        if (node != null) 
        { 
         yield return selector(node); 
         foreach (var n in node.Nodes.Select(selector)) 
         { 
          yield return n; 
         } 
        } 
       } 
      } 

     public static IEnumerable<Node> Where(this IEnumerable collection, Predicate<Node> pred) 
     { 
      foreach (var node in collection.Select(x => x)) //iterate the list in graph first order 
      { 
       if (pred(node)) 
        yield return node; 
      } 
     } 
    } 

EDIT: Il y avait une erreur dans la méthode Select dans l'affichage d'origine (il n'a pas itérer les enfants des enfants) qui est maintenant corrigé

+0

Merci @Rune FS. Je vais essayer cela et rendre compte. –

1
//Returns the top level nodes if an empty parentIdis entered 
targetFound = true; 
yield return node; 
yield break; 

Cela fonctionnera-t-il pour vous?

Mise à jour:

Je lui ai donné un peu plus la pensée. Cela pourrait être difficile avec la récursivité. Vous devrez garder une variable d'état pour sortir de toutes les boucles.

Si C# avait une récursion de queue, je suggérerais de convertir le code en CPS.

Vous pouvez toujours écrire dans MSIL :)

+0

Je te jure que je te vois * partout *, en général je te pose des questions/réponds juste avant que j'y sois.Effrayant: D –

+0

@Rei Miyasaka: Pas tout le temps, mais j'ai quelques rafales de 'fun' :) – leppie

1

Je suppose que la fonction est en fait le nom FindChildrenById, sinon je ne vois pas récursivité en cours. Dans cette hypothèse, vous pouvez soit utiliser des exceptions (que je vous déconseille vivement), soit renvoyer un KeyValuePair<bool, IEnumerable<Node>> où la partie bool sera utilisée pour signaler une sortie précoce de la chaîne. Ensuite, au niveau de l'API, affichez une méthode wrapper qui renvoie simplement la pièce IEnumerable<Node> et lève la pièce bool.

Voici un exemple, compte tenu de la classe Node:

public class Node 
{ 
    List<Node> children; 
    public string Text { get; set; } 
    public List<Node> Children { get { return children ?? (children = new List<Node>()); } } 
} 

Vous pouvez parcourir et raccourci comme ceci:

public class NodeTraverser 
{ 
    private static KeyValuePair<bool, IEnumerable<Node>> GetChildrenById(string text, Node node) 
    { 
     if(node.Text == text) 
     { 
      return new KeyValuePair<bool,IEnumerable<Node>>(true, node.Children); 
     } 
     foreach(var child in node.Children) 
     { 
      var result = GetChildrenById(text, child); 
      if(result.Key) 
      { 
       return result; // early out 
      } 
     } 
     return new KeyValuePair<bool,IEnumerable<Node>>(false, Enumerable.Empty<Node>()); 
    } 

    public static IEnumerable<Node> FindChildrenbyId(string text, Node root) 
    { 
     return GetChildrenById(text, root).Value; 
    } 
} 
+0

Oui, désolé j'ai corrigé un petit bug dans mon code mais j'ai collé la version précédente dans la question. Merci. Je ne pense pas que je peux retourner un KeyValuePair et toujours utiliser l'instruction yield. Il doit se produire dans une méthode qui renvoie IEnumerable

+0

J'ai ajouté un exemple de ce que je veux dire .. l'énoncé de rendement n'est pas strictement nécessaire, n'est-ce pas? – corvuscorax

+0

Oui, dans ce cas. Je fais face à un grand nombre de nœuds qui sont convertis et manipulés. En utilisant le rendement de rendement et la rupture de rendement, je suis capable de sortir de la boucle sans convertir ou manipuler le reste une fois que j'ai trouvé ce que je cherche. Votre exemple nécessite que la collection entière soit préchargée, convertie et manipulée avant que tout traitement ne se produise. Mais merci quand même. –