2010-06-03 3 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). Количество каждого числа не равно, как в примере. Указанный лимит Qty одинаковый для всего списка (не зависит от числа).

Благодаря

+0

Мне просто интересно. Для чего используется этот алгоритм? – Luke101

+0

Мне нужно выплевывать данные в этом формате для машины с ЧПУ. – JKJKJK

ответ

4

Был similar question, который придумал недавно спрашивали, как сделать это в SQL - нет действительно элегантное решение, и если это не Linq для SQL или Entity Framework (т.е. переводится в запрос 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

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 }); 
Смежные вопросы