2010-08-27 15 views
11

Jestem już zaznajomiony z Linq, ale nie mam pojęcia o metodach rozszerzenia Mam nadzieję, że ktoś może mi pomóc.Metoda rozszerzenia Linq, jak znaleźć dziecko w kolekcji rekursywnie

Więc mam ten kod hierarchiczną kolekcja pseudo IE:

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

I mam listę produktów Lista produktów.

Czy istnieje sposób, w jaki mogę znaleźć produkt w tej kolekcji przez id z metodą rozszerzenia? Innymi słowy potrzebuję jednego przedmiotu gdzieś wewnątrz hierarchii.

+0

chodziło Ci o: productsList.Where (x => x.Id == yourId) ;? –

+0

Lub productsList.FirstOrDefault (x => x.Id == yourId) ;? Zwraca pojedynczy obiekt, null, jeśli nie znaleziono pasującego obiektu. –

+0

Nie Chodzi mi o to, że muszę wyglądać zarówno ProductList, jak i ProductList-> Product-> Children To jest mój problem, mogę to zrobić z metodą rekursywną, ale zastanawiałem się, czy istnieje możliwość zrobienia tego z linq-extension. – sushiBite

Odpowiedz

17

Oto ogólny rozwiązanie, które będzie zwarcie przechodzenie hierarchii raz zostanie znaleziony.

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

Aby użyć go w przypadku:

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

Witam Wprowadziłem jedną niewielką zmianę do tego kodu i to działa :) Zmieniłem ten , jeśli (próba (próba, domyślna (T))) próba powrotu; ! Do if (equals (próba = null) próba powrotu;.! To działa jak czar Dziękuję wszystkim za pomoc – sushiBite

+1

@sushiBite myślę, że powinno być rzeczywiście 'if (equals (próba, domyślny (T))) zwraca próbę; 'ponieważ domyślna wartość' T' może nie być 'null' (jeśli' T' jest typem wartości) – Jay

+1

ahh, tak dzięki – sushiBite

8

można spłaszczyć strukturę drzewa przy użyciu tej metody rozszerzenie:

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

Zastosowanie:

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

Zauważ, że nie jest to prawdopodobnie bardzo szybko. Jeśli wielokrotnie trzeba znaleźć produkt przez ID, utwórz słownika:

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

var product42 = dict[42]; 
+0

Fajnie, lubię tę metodę spłaszczania. Jeśli się nie mylę, będzie to pierwsza iteracja (edytuj: jestem w błędzie, nie jest to pierwsza kwestia, pytanie nadal jest aktualne). Czy to oznacza, że ​​jeśli produkt jest pierwszą pozycją na liście i używasz Pierwszego zamiast Pojedynczego, to nie Spłaszczy on całej hierarchii? Czy opóźnione wykonanie linq pomoże tutaj? – Bubblewrap

+0

To wygląda na dobre rozwiązanie, ale ignoruje możliwość, że lista dzieci może mieć wartość "null". – Gabe

+0

@Bubblewrap: Masz rację. Jeśli użyjesz 'First' wówczas, dzięki opóźnionemu wykonaniu,' Flatten' będzie spłaszczać tyle, ile potrzeba. – dtb

-1

jeśli chcesz „sub-iterate” i znaleźć dziecko w wykazie produktów:

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

Można użyj istniejącej metody rozszerzenia SelectMany. SelectMany można użyć do "spłaszczenia" hierarchii dwupoziomowej.

Oto doskonałe wyjaśnienie SelectMany: http://team.interknowlogy.com/blogs/danhanan/archive/2008/10/10/use-linq-s-selectmany-method-to-quot-flatten-quot-collections.aspx

składni chciałby tak:

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

Cóż, to jest dobre, ale w hierarchii i na poziomie x wielu poziomów dla każdego produktu. , więc produkt A może mieć 3 dzieci, 5 wnucząt i 100 wnuków, , a produkt może mieć tylko 1 dziecko i nie ma wielkiego dziecka, nie wiem, jak mogę to wiedzieć. jeśli dobrze rozumiem to, musiałbym użyć SelectMany() dla każdego poziomu hierarchii? – sushiBite

+0

Możesz połączyć SelectMany razem tak daleko, jak to konieczne, aby uzyskać żądany poziom. –

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

Alternatywnym rozwiązaniem wykorzystaniem zwrotu w celu optymalizacji wyliczeń potrzebne.

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

Następnie można znaleźć dopasowanie wywołując coś FirstOrDefault:

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

Ja tylko refaktoringu rozwiązanie DTB, aby uczynić go bardziej rodzajowe. Wypróbuj tę metodę rozszerzenia:

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

I można go używać tak:

productList.Flatten(x => x.Children).Where(x => x.ID == id); 
Powiązane problemy