2010-06-03 47 views
10

我有重複號碼的清單:LINQ:的GroupBy,每組最大計數

Enumerable.Range(1,3).Select(o => Enumerable.Repeat(o, 3)).SelectMany(o => o) 
// {1,1,1,2,2,2,3,3,3} 

I組他們,並得到一次出現的數量:

Enumerable.Range(1,3).Select(o => Enumerable.Repeat(o, 3)).SelectMany(o => o) 
    .GroupBy(o => o).Select(o => new { Qty = o.Count(), Num = o.Key }) 

Qty Num 
3  1 
3  2 
3  3 

我真正需要的是限制每個組的數量爲某個數字。如果限制是2以上分組的結果將是:

Qty Num 
2  1 
1  1 
2  2 
1  2 
2  3 
1  3 

所以,如果數量= 10和下限爲4,則結果爲3行(4,4,2)。每個數字的數量與例子中的數量不相等。指定的數量限制對於整個列表是相同的(不因數量而異)。

感謝

+0

我只是好奇。這個算法用於什麼? – Luke101 2010-06-04 03:33:19

+0

我需要以這種格式爲CNC機器吐出數據。 – JKJKJK 2010-06-30 19:58:02

回答

4

有一個similar question想出了最近詢問如何在SQL做到這一點 - 有沒有真正優雅的解決方案,除非這是LINQ到SQL或實體框架(即被翻譯成一個SQL查詢)我真的建議你不是嘗試用Linq解決這個問題,而是寫一個迭代解決方案;這將會更加高效和易於維護。

這就是說,如果你絕對必須使用基於集合的(「LINQ的」)方法,這是你能做到這一點的一種方法:

var grouped = 
    from n in nums 
    group n by n into g 
    select new { Num = g.Key, Qty = g.Count() }; 

int maxPerGroup = 2; 
var portioned = 
    from x in grouped 
    from i in Enumerable.Range(1, grouped.Max(g => g.Qty)) 
    where (x.Qty % maxPerGroup) == (i % maxPerGroup) 
    let tempQty = (x.Qty/maxPerGroup) == (i/maxPerGroup) ? 
     (x.Qty % maxPerGroup) : maxPerGroup 
    select new 
    { 
     Num = x.Num, 
     Qty = (tempQty > 0) ? tempQty : maxPerGroup 
    }; 

與簡單和快速迭代版本比較:

foreach (var g in grouped) 
{ 
    int remaining = g.Qty; 
    while (remaining > 0) 
    { 
     int allotted = Math.Min(remaining, maxPerGroup); 
     yield return new MyGroup(g.Num, allotted); 
     remaining -= allotted; 
    } 
} 
+0

你說的LINQ方法太複雜了。謝謝。 – JKJKJK 2010-06-03 17:30:25

0

Aaronaught的優秀答案不包括獲得兩全其美的可能性...使用擴展方法提供迭代解決方案。

未經測試:

public static IEnumerable<IEnumerable<U>> SplitByMax<T, U>(
    this IEnumerable<T> source, 
    int max, 
    Func<T, int> maxSelector, 
    Func<T, int, U> resultSelector 
) 
{ 
    foreach(T x in source) 
    { 
    int number = maxSelector(x); 
    List<U> result = new List<U>(); 
    do 
    { 
     int allotted = Math.Min(number, max); 
     result.Add(resultSelector(x, allotted)); 
     number -= allotted 
    } while (number > 0 && max > 0); 

    yield return result; 
    } 
} 

通過調用:

var query = grouped.SplitByMax(
    10, 
    o => o.Qty, 
    (o, i) => new {Num = o.Num, Qty = i} 
) 
.SelectMany(split => split); 
3

一些其他的答案都使得LINQ查詢遠遠大於它需要更復雜。使用foreach循環肯定更快,更高效,但LINQ替代方法仍然非常簡單。

var input = Enumerable.Range(1, 3).SelectMany(x => Enumerable.Repeat(x, 10)); 
int limit = 4; 

var query = 
    input.GroupBy(x => x) 
     .SelectMany(g => g.Select((x, i) => new { Val = x, Grp = i/limit })) 
     .GroupBy(x => x, x => x.Val) 
     .Select(g => new { Qty = g.Count(), Num = g.Key.Val });