2010-06-03 6 views
10

J'ai une liste de numéros en double:LINQ: GroupBy avec un nombre maximum dans chaque groupe

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

Je groupe les et obtenir quantité de occurance:

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 

Ce que je vraiment besoin est de limiter la quantité par groupe à un certain nombre. Si la limite est de 2 le résultat du regroupement ci-dessus serait:

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

Donc, si Quantité = 10 et la limite est de 4, le résultat est de 3 lignes (4, 4, 2). La quantité de chaque nombre n'est pas égale comme dans l'exemple. La limite de quantité spécifiée est la même pour toute la liste (ne diffère pas en fonction du nombre).

Merci

+0

Je suis simplement curieux. À quoi sert cet algorithme? – Luke101

+0

J'ai besoin de cracher des données dans ce format pour une machine CNC. – JKJKJK

Répondre

4

Il y avait un similar question qui est venu demander récemment comment faire cela dans SQL - il n'y a pas de solution vraiment élégante et à moins que cela est LINQ to Framework SQL ou entité (c.-à-être traduit dans une requête SQL), Je suggère vraiment que vous pas essayer de résoudre ce problème avec Linq et à la place écrire une solution itérative; ça va être beaucoup plus efficace et facile à entretenir.

Cela dit, si vous devez absolument utiliser une méthode (« Linq ») basé sur un ensemble, c'est une façon, vous pouvez le faire:

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

Comparer avec la version plus simple et plus rapide itérative:

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

Vous avez raison de dire que la méthode LINQ est trop complexe. Merci. – JKJKJK

0

L'excellente réponse d'Aaronaught ne couvre pas la possibilité d'obtenir le meilleur des deux mondes ... en utilisant une méthode d'extension pour fournir une solution itérative.

Untested:

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

Appelé par:

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

Certains des autres réponses sont faites beaucoup plus complexe la requête LINQ qu'il doit être. L'utilisation d'une boucle foreach est certainement plus rapide et plus efficace, mais l'alternative LINQ est encore assez simple.

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