2010-06-03 70 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 });