2010-08-27 35 views
11

Tôi đã quen với LINQ nhưng có ít sự hiểu biết về các phương pháp mở rộng Tôi hy vọng ai đó có thể giúp tôi.Phương pháp mở rộng LINQ, cách tìm trẻ em trong bộ sưu tập đệ quy

Vì vậy, tôi có thứ bậc mã bộ sưu giả nghĩa này:

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

Và tôi có một danh sách các sản phẩm Danh sách sản phẩm.

Có cách nào để tôi có thể tìm kiếm sản phẩm trong bộ sưu tập này theo số id bằng phương pháp tiện ích không? Nói cách khác, tôi cần một mục ở đâu đó trong hệ thống phân cấp.

+0

Bạn có nghĩa là: productsList.Where (x => x.Id == yourId) ;? –

+0

Hoặc các sản phẩmList.FirstOrDefault (x => x.Id == yourId) ;? Điều này trả về một đối tượng duy nhất, null nếu không tìm thấy đối tượng nào phù hợp. –

+0

Không có nghĩa là tôi cần phải xem cả ProductList và ProductList-> Product-> Children Đó là vấn đề của tôi, tôi có thể làm điều đó bằng phương pháp đệ quy, nhưng tôi tự hỏi liệu có khả năng làm điều đó với phần mở rộng linq hay không. – sushiBite

Trả lời

17

Dưới đây là một giải pháp chung chung mà sẽ ngắn mạch traversal của hệ thống phân cấp một khi trận đấu được tìm thấy.

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

Để sử dụng nó trong trường hợp của bạn:

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

Xin chào, tôi đã thực hiện một thay đổi nhỏ đối với mã này và hoạt động :) Tôi đã thay đổi nếu (Bằng (cố gắng, mặc định (T))) trả lại lần thử; đến nếu (Bằng (cố gắng! = null) trả lại lần thử, Nó hoạt động giống như một sự quyến rũ Cảm ơn tất cả sự giúp đỡ của bạn – sushiBite

+1

@sushiBite Tôi nghĩ nó thực sự là 'if (! Bằng (cố gắng, mặc định (T)))) return; 'vì giá trị mặc định của' T' có thể không phải là 'null' (nếu' T' là kiểu giá trị). – Jay

+1

ahh, yes thanks – sushiBite

8

Bạn có thể san bằng cấu trúc cây của bạn sử dụng phương pháp mở rộng này:

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

Cách sử dụng:

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

Lưu ý rằng điều này có lẽ không phải là rất nhanh. Nếu bạn liên tục cần phải tìm một sản phẩm của id, tạo ra một từ điển:

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

var product42 = dict[42]; 
+0

Tốt, tôi thích phương pháp Flatten đó. Nếu tôi không nhầm nó sẽ lặp lại bề rộng đầu tiên (chỉnh sửa: tôi nhầm, nó không breadthfirst, câu hỏi vẫn còn có liên quan mặc dù). Điều đó có nghĩa rằng nếu sản phẩm là mục đầu tiên trong danh sách, và bạn sử dụng đầu tiên thay vì đơn, nó sẽ không làm phẳng toàn bộ hệ thống phân cấp? Việc thực thi chậm trễ của LINQ có giúp ích gì ở đây không? – Bubblewrap

+0

Điều này trông giống như một giải pháp tốt, nhưng nó bỏ qua khả năng rằng danh sách trẻ em có thể là 'null'. – Gabe

+0

@Bubblewrap: Bạn nói đúng. Nếu bạn sử dụng 'Đầu tiên' thì, nhờ vào việc thực hiện bị trì hoãn,' Flatten' sẽ chỉ làm phẳng nhiều như cần thiết. – dtb

-1

Nếu bạn muốn "sub-lặp" và tìm thấy một đứa trẻ trong một danh sách các sản phẩm:

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

Bạn có thể sử dụng phương thức mở rộng SelectMany hiện có. SelectMany có thể được sử dụng để "làm phẳng" cấu trúc phân cấp hai cấp.

Dưới đây là một lời giải thích tuyệt vời của SelectMany: http://team.interknowlogy.com/blogs/danhanan/archive/2008/10/10/use-linq-s-selectmany-method-to-quot-flatten-quot-collections.aspx

cú pháp bạn muốn như thế này:

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

Vâng, đó là tốt, nhưng phân cấp và x nhiều cấp độ sâu cho mỗi sản phẩm để sản phẩm A có thể có 3 trẻ em 5 cháu và 100 grandchildren và sản phẩm có thể có thể chỉ có 1 trẻ em và không có con lớn, có không có cách nào để tôi biết. nếu tôi hiểu chính xác điều này, tôi sẽ phải sử dụng SelectMany() cho mỗi cấp của cấu trúc phân cấp? – sushiBite

+0

Bạn có thể chuỗi SelectMany với nhau đến mức cần thiết để đạt đến mức bạn muốn. –

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

Một giải pháp thay thế bằng cách sử dụng năng suất để tối ưu hóa các kiểu liệt kê cần thiết.

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

Sau đó, bạn có thể tìm thấy một trận đấu bằng cách gọi một cái gì đó giống như FirstOrDefault:

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

Tôi chỉ refactoring giải pháp DTB của để làm cho nó chung chung hơn. Hãy thử phương pháp mở rộng này:

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

Và bạn có thể sử dụng nó như thế này:

productList.Flatten(x => x.Children).Where(x => x.ID == id); 
Các vấn đề liên quan