2012-01-12 45 views
7

我有一個question using these same examples - 這個問題是關於一個不同的問題。 鑑於以下類:如何從嵌套類獲取平展列表列表<T>?

[XmlRoot] 
    public class Family { 

     [XmlElement] 
     public List<Person> Person; 
    } 

    public class Person { 

     [XmlAttribute("member")] 
     public MemberType Member { get; set; } 

     [XmlAttribute("id")] 
     public int Id { get; set; } 

     [XmlElement] 
     public string Surname { get; set; } 

     [XmlElement] 
     public string Forename { get; set; } 

     [XmlElement("Person")] 
     public List<Person> People; 
    } 

    public enum MemberType { 
     Father, 
     Mother, 
     Son, 
     Daughter 
    } 

如果Family具有被定義爲這樣的方法:

public IEnumerable<Person> Find (Func<Person, bool> predicate) { 
    // how do I get SelectMany to flatten the list? 
    foreach (var p in family.Person.SelectMany(p => p)) { 
     if(predicate(p)) { 
      yield return p; 
     } 
    } 
} 

我需要能夠超過Person扁平列表來執行謂詞。在上面的例子中,SelectMany沒有像我希望的那樣將列表弄平。上面實際上不會編譯,因爲推斷的類型不能確定。

我怎樣才能讓Family.Person集合成爲一個扁平的Person列表?

+0

你嘗試'P => p.People'? – jvstech 2012-01-12 16:28:53

+0

如果你的數據結構中有循環,你可以使用下面的解決方案:http://stackoverflow.com/questions/141467/recursive-list-flattening/24747394#answer-24747394 – Aidin 2014-07-14 23:03:55

回答

5
public IEnumerable<Person> Find(IEnumerable<Person> input, Func<Person, bool> predicate) { 
    return input.Select(p => 
     { 
      var thisLevel = new List<Person>(); 
      if(predicate(p)) 
       thisLevel.Add(p); 

      return thisLevel.Union(Find(p.People ?? new List<Person>(), predicate)); 
     } 
    ).SelectMany(p => p); 
} 
+0

+1用於發現遞歸數據結構,這個問題並沒有讓這一點變得足夠明顯:) – MattDavey 2012-01-13 09:46:05

+2

我推薦Concat over Union,因爲沒有必要篩選交叉集合重複項。 – 2012-01-13 13:28:35

2

family.Person已經是一個展平的名單;無需撥打SelectMany就可以了。

foreach (var p in family.Person) { 
    if(predicate(p)) { 
     yield return p; 
    } 
} 

此外,您還可以更簡單地做:

public IEnumerable<Person> Find (Func<Person, bool> predicate) { 
    return family.Person.Where(predicate); 
} 
+0

注意到Person有一個嵌套的*列表 *。它不是**扁平列表,它是遞歸數據結構。 – MattDavey 2012-01-13 09:44:57

+1

@Matt - 我完全沒有看到 - 特別是考慮到OP的'family.Person.SelectMany(p => p)原始代碼'我想編輯修復,但是看起來Petr已經把它從公園裏敲了出來 – 2012-01-13 15:40:17

3

你並不需要同時SelectManyyield return - 你需要一個或另一個:

public IEnumerable<Person> Find (Func<Person, bool> predicate) { 
    foreach (var p in family.Person) { 
     if(predicate(p)) { 
      yield return p; 
     } 
    } 
} 

OR

public IEnumerable<Person> Find (Func<Person, bool> predicate) { 
    return family.Person.Where(p => predicate(p)); // Can be written simply as Where(predicate) 
} 
+1

*'p =>謂詞(p)'*可以簡化爲*'謂詞'* - atm您正在用另一個包裝一個* Func *。 – MattDavey 2012-01-12 16:26:00

+1

@MattDavey我添加了一個包裝來從表達式中移除「魔術」。我添加了一條評論專欄,說它可以被簡化。只是在查看一個沒有'=>'的Where子句可能會讓LINQ經驗相對較少的人感到困惑。 – dasblinkenlight 2012-01-12 16:26:51

+0

是的,我認爲這是一個相關的事情:) – MattDavey 2012-01-12 16:38:53

6

據我所知,最簡單的方法是使用助手。

private List<Person> FlattenTree(Person person) 
    { 
     var accumulator = new List<Person>(); 
     FlattenPersonHelper(person, accumulator); 

     return accumulator; 
    } 


    private void FlattenPersonHelper(Person person, List<Person> accumulator) 
    { 
     accumulator.Add(person); 

     foreach (var child in person.People) 
     { 
      FlattenPersonHelper(child, accumulator); 
     } 
     return; 
    } 

然後,您可以根據此列表運行斷言:

public IEnumerable<Person> Find (Func<Person, bool> predicate) { 
    var familyRoot = new Person() { People = family.Person }; 
    return FlattenTree(familyRoot).Where(predicate); 
} 
+2

+1我喜歡它 - 也歡迎來到stackoverflow! – 2012-01-12 16:41:32

+1

謝謝!謝謝! PS我經常使用這種模式,我已經寫了一個通用版本作爲擴展方法。超級有用。 – 2012-01-12 16:52:28

4

的SelectMany只有平展層次的一個級別:

public IEnumerable<Person> FindLevel2 (Func<Person, bool> predicate) 
{ 
    return family.Person.SelectMany(p => p.People).Where(predicate); 
} 

你可能需要的是一個任意深度的步行路程的層次結構。這最好通過遞歸完成(未測試)。

public IEnumerable<Person> Find(Func<Person, bool> predicate) 
{ 
    foreach(Person p in family.Person) 
    { 
    IEnumerable<Person> result = FindFromPerson(p); 
    foreach(Person x in result) 
    { 
     yield return x; 
    } 
    } 
} 

public IEnumerable<Person> FindFromPerson(Person p, Func<Person, bool> predicate) 
{ 
    if predicate(p) 
    { 
    yield return p; 
    } 
    foreach(Person child in p.People) 
    { 
    IEnumerable<Person> childResults = FindFromPerson(child); 
    foreach(Person x in childResults) 
    { 
     yield return x; 
    } 
    } 
}