2011-09-09 62 views
4

我試圖根據以下(簡化)規則進行排序項的列表:複雜的LINQ與團體分類

我會每個項目具有以下屬性:

Id (int), 
ParentId (int?), 
Name (string) 

PARENTID是將ForeignKey自連接到Id。如果某個項目具有ParentId,則該父項也將存在於該列表中。

我需要對列表進行排序,以便所有具有父項的項目都立即出現在其父項之後。然後所有項目將按名稱排序。

所以,如果我有以下幾點:

Id: 1, ParentId: null, Name: Pi 
Id: 2, ParentId: null, Name: Gamma 
Id: 11, ParentId: 1, Name: Charlie 
Id: 12, ParentId: 1, Name: Beta 
Id: 21, ParentId: 2, Name: Alpha 
Id: 22, ParentId: 2, Name: Omega 

然後,我希望他們整理如下:

IDS:2,21,22,1,12,11

目前最好的我可以想出的是按姓名排序,然後按ParentId分組如下:

var sortedItems = itemsToSort.OrderBy(x=> x.Name).GroupBy(x=> x.ParentId); 

我的出發計劃是計算方式如下:(非功能代碼)

var finalCollection = new List<Item> 

var parentGroup = sortedItems.Where(si => si.Key == null); 

foreach(parent in parentGroup) 
{ 
    finalCollection.Add(parent); 
    foreach(child in sortedItems.Where(si => si.Key == parent.Id) 
    { 
     finalCollection.Add(child); 
    } 
} 

然而,parentGroup不

IEnumerable<Item> 

所以這是行不通的。

我覺得有一個更簡單,更簡潔的方式來實現這一目標,但目前它正在逃避我 - 任何人都可以幫忙嗎?

+0

parentGroup肯定會是'IEnumerable <>' - 給你的印象是什麼? – Jamiec

+0

是否只有頂級項目和一級子項,或者還有更深層次的項目? – dtb

+0

@Jamiec:收到它 - 我的斜角括號已被隱藏 - 讓我改正 – BonyT

回答

2

如果你只有兩個級別,你可以做這樣的:

var lookup = itemsToSort.OrderBy(x => x.Name).ToLookup(x => x.ParentId, x => x); 
var parents = lookup[null]; 
var sortedItems = parents.SelectMany(x => new[] { x }.Concat(lookup[x.Id])); 

內部項目按名稱排序,以確保當它們稍後分成組時,它們將保持排序。

然後創建一個查找表,允許通過ParentId進行查找。然後使用SelectMany將通過擁有null ParentId而識別的父母與他們的孩子聯繫起來,並使用查找表找到孩子。父母在孩子之前插入以獲得期望的序列。

如果你想解決超過兩個層次的一般情況,你需要使用遞歸。這裏有一個方法來遞歸得到一個節點substree:

IEnumerable<Item> GetSubtreeForParent(Item parent, ILookup<Int32?, Item> lookup) { 
    yield return parent; 
    foreach (var child in lookup[parent.Id]) 
    foreach (var descendant in GetSubtreeForParent(child, lookup)) 
     yield return descendant; 
} 

的代碼幾乎相同,上述簡單的情況:

var lookup = itemsToSort.OrderBy(x => x.Name).ToLookup(x => x.ParentId, x => x); 
var parents = lookup[null]; 
var sortedItems = parents.SelectMany(x => GetSubtreeForParent(x, lookup)); 

通過使用遞歸拉姆達你甚至可以做到這一切「內聯」:

var lookup = itemsToSort.OrderBy(x => x.Name).ToLookup(x => x.ParentId, x => x); 
// Declare Func to allow recursion. 
Func<Int32?, IEnumerable<Item>> getSubTreeForParent = null; 
getSubTreeForParent = 
    id => lookup[id].SelectMany(x => new[] { x }.Concat(getSubTreeForParent(x.Id))); 
var sortedItems = getSubTreeForParent(null); 
+0

我喜歡這種方式的外觀 - 謝謝 - 我會試試看。 – BonyT

+0

有時間玩這個 - 點擊很好地感謝。 – BonyT

0

var parentGroup = sortedItems.Where(si => si.Key == null).ToList() 

會讓parentGroupIEnumerable<Item>

你會鬆懈頂級的,但我認爲這很好,由於上下文。

+0

謝謝,但這不起作用。 – BonyT

1

這可以通過以下方式實現:

list.Select(i => 
     new {Parent=list.Where(x => x.Id == i.ParentId).FirstOrDefault(), Item = i}) 
    .OrderBy(i => i.Parent == null ? i.Item.Name : i.Parent.Name + i.Item.Name) 
    .Select(i => i.Item) 

活生生的例子:http://rextester.com/rundotnet?code=WMEZ40628

輸出是:

2 
21 
22 
1 
12 
11 
+0

所有項目,包括父母都按名稱排序 - 因此,由於姓名排序,父母2位於父母1之前。 – BonyT

+0

但無後顧之憂 - 我可以處理這種併發症 - 您的答案提供了我無法看到的邏輯 - 天才 - 謝謝。 – BonyT

+0

啊錯過了「按名稱排序」的要求。我會立即檢查出 – Jamiec

3

當我明白你的問題,你要訂購母公司名稱的結果(如果它的父),然後通過孩子的名字(如果它是一個孩子),但你希望所有的孩子出現在lis中t在他們各自的父母之後。

這應該做的伎倆:

更新,以解決@Martin Liversage提到的問題。

var query = from item in itemsToSort 
      let parent = itemsToSort.Where(i => i.Id == item.ParentId).FirstOrDefault() 
      //get the name of the item's parent, or the item itself if it is a parent 
      let parentName = (parent != null) ? parent.Name : item.Name 
      //get the name of the child (use null if the item isn't a child) 
      let childName = (parent != null) ? item.Name : null 
      orderby parentName, childName 
      select item; 

var finalCollection = query.ToList(); 

下面是輸出:

enter image description here

+0

好點,我現在更新了我的答案來解決這個問題。 –

1

我與DoctaJonez的answer 2級去。

它可以擴展到N個級別,像這樣:

Func<int?,Item> lookup = id => list.Where(i => i.Id == id).FirstOrDefault(); 

Func<Item,string> makeSortString = null; 
makeSortString = i => i.ParentId == null ? i.Name : makeSortString(lookup(i.ParentId)) + i.Name; 

list.OrderBy(makeSortString).ToList(); 
+0

如果任何父母的名字是另一父母及其子女的一個串聯名稱,這將會中斷。嘗試將'Pi'重命名爲'GammaAlpha'並查看輸出。 –

0

這個怎麼樣?

var lookup = items.ToLookup(x => x.ParentId); 

Func<int?, IEnumerable<Item>> f = null; 
f = ni => 
    from a in lookup[ni].OrderBy(x => x.Name) 
    from b in (new [] { a }).Concat(f(a.Id)) 
    select b; 

然後拿到排序列表做到這一點:

var sorted = f(null); 

簡單。 :-)