2011-01-27 92 views
12

讓我們這個n層的深層結構,例如:LINQ遞歸函數?

public class SomeItem 
{ 
    public Guid ID { get;set; } 
    public string Name { get; set; } 
    public bool HasChildren { get;set; } 
    public IEnumerable<SomeItem> Children { get; set; } 
} 

如果我希望得到的ID(結構中的任意位置)的特定項目是有一些LINQ上帝,我可以用它來輕鬆搞定它一個單獨的聲明或我必須使用一些遞歸函數如下:

private SomeItem GetSomeItem(IEnumerable<SomeItem> items, Guid ID) 
    { 
     foreach (var item in items) 
     { 
      if (item.ID == ID) 
      { 
       return item; 
      } 
      else if (item.HasChildren) 
      { 
       return GetSomeItem(item.Children, ID); 
      } 
     } 
     return null; 
    } 
+0

你確實需要HasChildren屬性嗎? – Amnon 2011-01-27 08:50:06

+0

不是真的,只是一個幫手,所以它讀得更好:) - Children.Count> 0 – 2011-01-27 08:54:11

+1

@The_Butcher Children.Any():) – felickz 2015-04-24 17:39:49

回答

28

LINQ並不真的「做」很好的遞歸。你的解決方案似乎是適當的 - 雖然我不確定HasChildren是否真的需要......爲什麼不爲一個沒有孩子的項目使用空列表?

另一種方法是編寫一個DescendantsAndSelf方法,該方法將返回所有的後代(包括物品本身),類似這樣;

// Warning: potentially expensive! 
public IEnumerable<SomeItem> DescendantsAndSelf() 
{ 
    yield return this; 
    foreach (var item in Children.SelectMany(x => x.DescendantsAndSelf())) 
    { 
     yield return item; 
    } 
} 

但是,如果樹很深,結果是效率非常低,因爲每個項目需要「通過」其祖先的所有迭代器。韋斯代爾有blogged about this,顯示更有效的實施。無論如何,如果你有這樣一個方法(但是它的實現),你可以使用普通的「where」子句來找到一個項目(或First/FirstOrDefault等)。

+0

是不是迭代器的數量生成的節點的總數,而不是樹深度? (後代和自己被稱爲每個節點)。 – Amnon 2011-01-27 08:56:25

1

雖然有一些擴展方法可以在LINQ中啓用遞歸(可能看起來像你的函數),但是沒有提供任何開箱即用的功能。

這些擴展方法的示例可以找到herehere

我會說你的功能沒問題。

3

希望這有助於

public static IEnumerable<Control> GetAllChildControls(this Control parent) 
{ 
    foreach (Control child in parent.Controls) 
    { 
    yield return child; 

    if (child.HasChildren) 
    { 
     foreach (Control grandChild in child.GetAllChildControls()) 
     yield return grandChild; 
    } 
    } 
} 
3

這裏是一個沒有遞歸。這樣可以避免通過幾層迭代器的代價,所以我認爲它的效率和它們一樣高。

public static IEnumerable<T> IterateTree<T>(this T root, Func<T, IEnumerable<T>> childrenF) 
    { 
     var q = new List<T>() { root }; 
     while (q.Any()) 
     { 
      var c = q[0]; 
      q.RemoveAt(0); 
      q.AddRange(childrenF(c) ?? Enumerable.Empty<T>()); 
      yield return c; 
     } 
    } 

調用,像這樣:

  var subtree = root.IterateTree(x => x. Children).ToList(); 
2

要記住你不需要使用LINQ,或默認盡一切努力遞歸是很重要的。當你使用數據結構時,有一些有趣的選項。以下是一個簡單的展平功能,以防任何人感興趣。

public static IEnumerable<SomeItem> Flatten(IEnumerable<SomeItem> items) 
    { 
     if (items == null || items.Count() == 0) return new List<SomeItem>(); 

     var result = new List<SomeItem>(); 
     var q = new Queue<SomeItem>(collection: items); 

     while (q.Count > 0) 
     { 
      var item = q.Dequeue(); 
      result.Add(item); 

      if (item?.Children?.Count() > 0) 
       foreach (var child in item.Children) 
        q.Enqueue(child); 
     } 

     return result; 
    }