2010-08-27 11 views
11

Ich bin bereits vertraut mit Linq, aber habe wenig Verständnis für Erweiterungsmethoden Ich hoffe, dass jemand mir helfen kann. SoLinq Erweiterung Methode, wie Kind in Sammlung rekursiv zu finden

Ich habe diese hierarchische Sammlung Pseudo-Code, dh:

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

Und ich habe eine Liste der Produkte Liste Produkte.

Gibt es eine Möglichkeit, Produkt in dieser Sammlung von der ID mit einer Erweiterungsmethode zu suchen? Mit anderen Worten, ich brauche einen Gegenstand irgendwo in der Hierarchie.

+0

Sie meinen: productsList.Where (x => x.Id == yourId) ;? –

+0

Oder productsList.FirstOrDefault (x => x.Id == yourId) ;? Dies gibt ein einzelnes Objekt null zurück, wenn kein übereinstimmendes Objekt gefunden wird. –

+0

Nein ich meine Ich muss sowohl die ProductsList und ProductList-> Product-> Children Das ist mein Problem, ich kann es mit rekursive Methode tun, aber ich frage mich, ob es eine Möglichkeit gibt, tun Sie es mit linq-Erweiterung. – sushiBite

Antwort

17

Dies ist eine generische Lösung, die Kurzschlussdurchlauf der Hierarchie wird, sobald eine Übereinstimmung gefunden wird.

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

Um es in Ihrem Fall zu verwenden:

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

Hallo Ich habe eine kleine Änderung an diesem Code und das funktioniert :) Ich änderte diese if (Equals (Versuch, Standard (T))) Rückkehr Versuch; ! Zu if (Equals (Versuch = null) return Versuch;.! Es ist wie ein Zauber Vielen Dank für Ihre Hilfe funktioniert – sushiBite

+1

@sushiBite ich denke, es sollte eigentlich 'if (Equals (Versuch, default (T))) return versuch; 'weil der Standardwert von' T' nicht 'null' sein kann (wenn' T' ein Werttyp ist) – Jay

+1

ahh, ja danke – sushiBite

8

Sie können Ihre Baumstruktur mit dieser Erweiterung Methode glätten:

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

Verbrauch:

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

Beachten Sie, dass dies wahrscheinlich nicht sehr schnell ist. Wenn Sie wiederholt ein Produkt von ID finden müssen, erstellen Sie ein Wörterbuch:

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

var product42 = dict[42]; 
+0

Schön, ich mag diese Flatten-Methode. Wenn ich mich nicht irre, wird es zuerst in der Breite iterieren (edit: ich irre mich, es ist nicht zuerst, aber die Frage ist immer noch relevant). Bedeutet das, dass wenn das Produkt das erste Element in der Liste ist und Sie First anstelle von Single verwenden, dass die gesamte Hierarchie nicht abgeflacht wird? Wird Linqs verzögerte Hinrichtung hier helfen? – Bubblewrap

+0

Das sieht nach einer guten Lösung aus, aber es ignoriert die Möglichkeit, dass die Liste der Kinder "null" ist. – Gabe

+0

@Bubblewrap: Du hast Recht. Wenn Sie 'First' verwenden, wird' Flatten' dank der verzögerten Ausführung nur so viel abgeflacht wie nötig. – dtb

-1

Wenn Sie auf „sub-iterate“ wollen und ein Kind in einer Liste der Produkte finden:

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

können Sie Verwenden Sie die vorhandene Erweiterungsmethode . SelectMany kann verwendet werden, um eine zweistufige Hierarchie zu "glätten".

Hier ist eine große Erklärung von Select: http://team.interknowlogy.com/blogs/danhanan/archive/2008/10/10/use-linq-s-selectmany-method-to-quot-flatten-quot-collections.aspx

Ihre Syntax wie folgt möchte:

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

Nun, das ist gut, aber die Hierarchie und sein x viele Ebenen tief für jedes Produkt so Produkt A kann 3 Kinder 5 Enkelkinder und 100 Enkel haben und Produkt sein kann vielleicht nur 1 Kind und kein Enkel, gibt es Keine Möglichkeit für mich zu wissen. Wenn ich das richtig verstehe, müsste ich SelectMany() für jede Ebene der Hierarchie verwenden? – sushiBite

+0

Sie können SelectMany so weit wie nötig verketten, um die gewünschte Stufe zu erreichen. –

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

Eine alternative Lösung, um die Ausbeute unter Verwendung erforderlich, um die Aufzählungen zu optimieren.

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

Dann können Sie ein Spiel finden, indem Sie so etwas wie FirstOrDefault Aufruf:

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

Ich bin nur dtb Lösung Refactoring es allgemeinere zu machen. Versuchen Sie, diese Erweiterung Methode:

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

Und Sie es wie folgt verwenden können:

productList.Flatten(x => x.Children).Where(x => x.ID == id); 
Verwandte Themen