2010-06-03 9 views
10

Mam listę duplikatów numerów:LINQ: GroupBy z maksymalnej liczby w każdej grupie

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

I Grupa nich i dostać ilość wystąpieniu:

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 

Co naprawdę potrzebne jest ograniczenie ilość na grupę do pewnej liczby. Jeśli granica wynosi 2 wynika z powyższej grupy to:

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

Tak więc, jeśli Ilość = 10, a granica wynosi 4, wynik 3 rzędy (4, 4, 2). Ilość każdej liczby nie jest równa, jak w przykładzie. Określony limit ilości jest taki sam dla całej listy (nie różni się w zależności od liczby).

Dzięki

+0

Jestem po prostu ciekawy. Do czego służy ten algorytm? – Luke101

+0

Muszę wypluć dane w tym formacie dla maszyny CNC. – JKJKJK

Odpowiedz

4

Było similar question że wymyślił niedawno pytanie, jak to zrobić w SQL - nie ma naprawdę eleganckie rozwiązanie i chyba to jest LINQ do SQL lub Entity Framework (tj tłumaczony na zapytania SQL) Naprawdę zasugerowałbym, że nie próbujesz rozwiązać tego problemu z Linq i zamiast tego napisałeś rozwiązanie iteracyjne; będzie to znacznie wydajniejsze i łatwiejsze w utrzymaniu.

Powiedział, że jeśli absolutnie muszą stosować metodę nastawioną na bazie („Linq”), to jest jeden sposób można to zrobić:

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 
    }; 

porównania prostsze i szybsze iteracyjny wersji:

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

Masz rację, że metoda LINQ jest zbyt skomplikowana. Dzięki. – JKJKJK

0

Doskonała odpowiedź Aaronaught nie obejmuje możliwości uzyskania najlepszego z obu światów ... za pomocą metody rozszerzającej w celu zapewnienia iteracyjnego rozwiązania.

Nietestowane:

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; 
    } 
} 

Wywoływana przez:

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

Niektóre inne odpowiedzi są jak kwerendy LINQ znacznie bardziej skomplikowane niż to musi być. Korzystanie z pętli foreach jest z pewnością szybsze i bardziej wydajne, ale alternatywa LINQ jest wciąż dość prosta.

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 }); 
Powiązane problemy