LINQ com groupby e count

221

Isso é bastante simples, mas estou perplexo: dado esse tipo de conjunto de dados:

UserInfo(name, metric, day, other_metric)

e este conjunto de dados de amostra:

joe  1 01/01/2011 5
jane 0 01/02/2011 9
john 2 01/03/2011 0
jim  3 01/04/2011 1
jean 1 01/05/2011 3
jill 2 01/06/2011 5
jeb  0 01/07/2011 3
jenn 0 01/08/2011 7

Gostaria de recuperar uma tabela que lista as métricas em ordem (0,1,2,3 ..) com o número total de vezes que a contagem ocorre. Portanto, desse conjunto, você terminaria com:

0 3    
1 2    
2 2    
3 1

Estou lutando com a sintaxe LINQ, mas estou preso em onde colocar um groupby e contar .... qualquer ajuda ??

POST Edit: Eu nunca consegui que as respostas postadas funcionassem, pois elas sempre retornavam um registro com o número de contagens diferentes. No entanto, consegui montar um exemplo de LINQ to SQL que funcionou:

        var pl = from r in info
                 orderby r.metric    
                 group r by r.metric into grp
                 select new { key = grp.Key, cnt = grp.Count()};

Esse resultado me deu um conjunto ordenado de registros com 'métricas' e o número de usuários associados a cada um. Eu sou claramente novo no LINQ em geral e, para meus olhos destreinados, essa abordagem parece muito semelhante à abordagem pura do LINQ, mas me deu uma resposta diferente.

Gio
fonte
Sim, sim, mas a explicação de jimmy me ajudou mais. No entanto, eu nunca fui capaz de fazer o exemplo dele funcionar, mas isso me levou a uma nova direção.
Gio
@Jimmy usou a sintaxe funcional para expressões LINQ em vez da sintaxe de consulta LINQ padrão, além disso, ele decidiu mostrar a execução imediata dessas funções em vez do formato de execução atrasada. Para uma nova pessoa que seria confusa. Não sei por que ele fez isso.
Richard Robertson

Respostas:

394

Após a chamada GroupBy, você obtém uma série de grupos IEnumerable<Grouping>, nos quais cada Agrupamento expõe o Keyusado para criar o grupo e também é um IEnumerable<T>dos itens que estão no seu conjunto de dados original. Você apenas precisa chamar Count()esse agrupamento para obter o subtotal.

foreach(var line in data.GroupBy(info => info.metric)
                        .Select(group => new { 
                             Metric = group.Key, 
                             Count = group.Count() 
                        })
                        .OrderBy(x => x.Metric)
{
     Console.WriteLine("{0} {1}", line.Metric, line.Count);
}


Essa foi uma resposta rápida e brilhante, mas estou tendo um problema com a primeira linha, especificamente "data.groupby (info => info.metric)"

Suponho que você já tenha uma lista / matriz de alguns classque se parece com

class UserInfo {
    string name;
    int metric;
    ..etc..
} 
...
List<UserInfo> data = ..... ;

Quando você faz data.GroupBy(x => x.metric)isso, significa "para cada elemento xno IEnumerable definido por data, calcule-o e .metric, em seguida, agrupe todos os elementos com a mesma métrica em ae Groupingretorne um IEnumerablede todos os grupos resultantes. Dado seu conjunto de dados de exemplo de

    <DATA>           | Grouping Key (x=>x.metric) |
joe  1 01/01/2011 5  | 1
jane 0 01/02/2011 9  | 0
john 2 01/03/2011 0  | 2
jim  3 01/04/2011 1  | 3
jean 1 01/05/2011 3  | 1
jill 2 01/06/2011 5  | 2
jeb  0 01/07/2011 3  | 0
jenn 0 01/08/2011 7  | 0

resultaria no seguinte resultado após o groupby:

(Group 1): [joe  1 01/01/2011 5, jean 1 01/05/2011 3]
(Group 0): [jane 0 01/02/2011 9, jeb  0 01/07/2011 3, jenn 0 01/08/2011 7]
(Group 2): [john 2 01/03/2011 0, jill 2 01/06/2011 5]
(Group 3): [jim  3 01/04/2011 1]
Jimmy
fonte
Essa foi uma resposta rápida e brilhante, mas estou tendo um problema com a primeira linha, especificamente "data.groupby (info => info.metric)". Claramente 'data' é o conjunto de dados atualmente, mas o que 'info.metric' representa? a definição de classe?
Gio
"info.metric" seria a propriedade / campo métrico na classe UserInfo que você mencionou na sua pergunta.
Lee-m
1
Obrigado, entendemos isso, mas, na verdade, isso parece me fornecer um valor único - ou seja, o número total de contagens de métricas diferentes. Neste exemplo, recebo "métricas 4", que indica quantas contagens diferentes tenho.
Gio
1
Uau. Você explicou totalmente o agrupamento !! Só isso valeu a pena o post .... ainda recebo o resultado "métricas 4", mas obrigado!
Gio
4
O início desta resposta ", depois de chamar GroupBy, você obtém uma série de grupos IEnumerable <Grouping>, onde cada Agrupamento expõe a Chave usada para criar o grupo e também é um IEnumerable <T> dos itens que estão em seus dados originais set ", é a explicação mais clara do LINQ GroupBy que eu já li, obrigado.
dumbledad
48

Assumindo que userInfoListé um List<UserInfo>:

        var groups = userInfoList
            .GroupBy(n => n.metric)
            .Select(n => new
            {
                MetricName = n.Key,
                MetricCount = n.Count()
            }
            )
            .OrderBy(n => n.MetricName);

A função lambda para GroupBy(), n => n.metricsignifica que ele obterá campo metricde todos os UserInfoobjetos encontrados. O tipo de ndepende do contexto, na primeira ocorrência é do tipo UserInfo, porque a lista contém UserInfoobjetos. Na segunda ocorrência, né do tipo Grouping, porque agora é uma lista de Groupingobjetos.

Groupings têm métodos de extensão como .Count(), .Key()e praticamente qualquer outra coisa que você esperaria. Assim como você verificaria .Lenghtum string, você pode verificar .Count()um grupo.

Vladislav Zorov
fonte
23
userInfos.GroupBy(userInfo => userInfo.metric)
        .OrderBy(group => group.Key)
        .Select(group => Tuple.Create(group.Key, group.Count()));
DanielOfTaebl
fonte
1
pequeno erro de digitação -> group.keyna Select (...) deve sergroup.Key
Jan