2010-08-27 5 views
11

Je connais déjà Linq mais j'ai peu de compréhension des méthodes d'extension J'espère que quelqu'un pourra m'aider.Méthode d'extension de Linq, comment trouver un enfant dans la collection récursive

Je cette collection hiérarchique code pseudo-à-dire:

class Product 
    prop name 
    prop type 
    prop id 
    prop List<Product> children 

Et j'ai une liste des produits Liste des produits.

Y a-t-il un moyen de rechercher un produit dans cette collection par le id avec une méthode d'extension? En d'autres termes, j'ai besoin d'un élément quelque part dans la hiérarchie.

+0

Vous voulez dire: productsList.Where (x => x.Id == YourID) ;? –

+0

Ou productsList.FirstOrDefault (x => x.Id == yourId) ;? Cela renvoie un seul objet, null si aucun objet correspondant n'est trouvé. –

+0

Non, je veux dire que j'ai besoin de regarder à la fois la ProductsList et ProductList-> Product-> Enfants C'est mon problème, je peux le faire avec la méthode récursive, mais je me demandais s'il y a une possibilité le faire avec linq-extension. – sushiBite

Répondre

17

Voici une solution générique qui traversal court-circuit de la hiérarchie une fois se trouve un match.

public static class MyExtensions 
{ 
    public static T FirstOrDefaultFromMany<T>(
     this IEnumerable<T> source, Func<T, IEnumerable<T>> childrenSelector, 
     Predicate<T> condition) 
    { 
     // return default if no items 
     if(source == null || !source.Any()) return default(T); 

     // return result if found and stop traversing hierarchy 
     var attempt = source.FirstOrDefault(t => condition(t)); 
     if(!Equals(attempt,default(T))) return attempt; 

     // recursively call this function on lower levels of the 
     // hierarchy until a match is found or the hierarchy is exhausted 
     return source.SelectMany(childrenSelector) 
      .FirstOrDefaultFromMany(childrenSelector, condition); 
    } 
} 

Pour l'utiliser dans votre cas:

var matchingProduct = products.FirstOrDefaultFromMany(p => p.children, p => p.Id == 27); 
+0

Salut j'ai fait une modification mineure à ce code et cela fonctionne :) J'ai changé ce si (égal (tentative, par défaut (T))) tentative de retour; ! À si (Equals (tentative = null) tentative de retour;.! Il fonctionne comme un charme Merci à tous pour votre aide – sushiBite

+1

@sushiBite Je pense qu'il devrait en fait être 'si (Equals (tentative, par défaut (T))) return tentative; 'parce que la valeur par défaut de' T' pourrait ne pas être 'null' (si' T' est un type de valeur) – Jay

+1

ahh, oui merci – sushiBite

8

Vous pouvez aplatir la structure de votre arbre en utilisant cette méthode d'extension:

static IEnumerable<Product> Flatten(this IEnumerable<Product> source) 
{ 
    return source.Concat(source.SelectMany(p => p.Children.Flatten())); 
} 

Utilisation:

var product42 = products.Flatten().Single(p => p.Id == 42); 

Notez que ceci est probablement pas très rapide. Si vous avez besoin à plusieurs reprises pour trouver un produit par id, créez un dictionnaire:

var dict = products.Flatten().ToDictionary(p => p.Id); 

var product42 = dict[42]; 
+0

Bien, j'aime cette méthode Flatten. Si je ne me trompe pas, ça va itérer la largeur d'abord (modifier: je me trompe, ce n'est pas la première fois, la question est toujours pertinente). Cela signifie-t-il que si le produit est le premier élément de la liste et que vous utilisez First au lieu de Single, cela n'affaiblira pas toute la hiérarchie? L'exécution retardée de linq aidera-t-elle ici? – Bubblewrap

+0

Cela ressemble à une bonne solution, mais il ignore la possibilité que la liste des enfants puisse être 'null'. – Gabe

+0

@Bubblewrap: Vous avez raison. Si vous utilisez 'First', alors, grâce à une exécution retardée,' Flatten' n'affectera que ce qui est nécessaire. – dtb

-1

Si vous voulez « sous-itérer » et trouver un enfant dans une liste de produits:

List<Product> 
    Product 
     Child 
     Child 
     Child 
     Child 
    Product 
     Child 
     Child *find this one 
     Child 

Vous pouvez utilisez la méthode d'extension SelectMany existante. SelectMany peut être utilisé pour "aplatir" une hiérarchie à deux niveaux.

Voici une grande explication de SelectMany: http://team.interknowlogy.com/blogs/danhanan/archive/2008/10/10/use-linq-s-selectmany-method-to-quot-flatten-quot-collections.aspx

Votre syntaxe aimerait comme ceci:

List<Product> p = GetProducts(); //Get a list of products 
var child = from c in p.SelectMany(p => p.Children).Where(c => c.Id == yourID); 
+0

Eh bien, c'est bon, mais la hiérarchie et être x plusieurs niveaux profonds pour chaque produit donc le produit A peut avoir 3 enfants 5 petits-enfants et 100 petits-enfants et le produit peut peut-être avoir seulement 1 enfant et pas grand-enfant, il y a aucun moyen pour moi de savoir. Si je comprends bien cela, je devrais utiliser SelectMany() pour chaque niveau de la hiérarchie? – sushiBite

+0

Vous pouvez enchaîner SelectMany autant que nécessaire pour atteindre le niveau souhaité. –

0
static IEnumerable<Product> FindProductById(this IEnumerable<Product> source, int id) 
{ 
    return source.FirstOrDefault(product => product.Id = id) ?? source.SelectMany(product => product.Children).FindProductById(id); 
} 
0

Une solution alternative en utilisant le rendement pour optimiser les énumérations nécessaires.

public static IEnumerable<T> SelectManyRecursive<T>(
    this IEnumerable<T> source, 
    Func<T, IEnumerable<T>> childrenSelector) 
{ 
    if (source == null) 
     throw new ArgumentNullException("source"); 

    foreach (var i in source) 
    { 
     yield return i; 
     var children = childrenSelector(i); 
     if (children != null) 
     { 
      foreach (var child in SelectManyRecursive(children, childrenSelector)) 
      { 
       yield return child; 
      } 
     } 
    } 
} 

Ensuite, vous pouvez trouver une correspondance en appelant quelque chose comme FirstOrDefault:

var match = People.SelectManyRecursive(c => c.Children) 
         .FirstOrDefault(x => x.Id == 5); 
1

Je suis juste refactoring la solution de DTB pour le rendre plus générique. Essayez cette méthode d'extension:

public static IEnumerable<T> Flatten<T, R>(this IEnumerable<T> source, Func<T, R> recursion) where R : IEnumerable<T> 
{ 
    return source.SelectMany(x => (recursion(x) != null && recursion(x).Any()) ? recursion(x).Flatten(recursion) : null) 
       .Where(x => x != null); 
} 

Et vous pouvez l'utiliser comme ceci:

productList.Flatten(x => x.Children).Where(x => x.ID == id); 
Questions connexes