Agrupe contando na API de fluxo Java 8

170

Eu tento encontrar uma maneira simples na API de fluxo Java 8 para fazer o agrupamento, e saio dessa maneira complexa!

List<String> list = new ArrayList<>();

list.add("Hello");
list.add("Hello");
list.add("World");

Map<String, List<String>> collect = list.stream().collect(
        Collectors.groupingBy(o -> o));
System.out.println(collect);

List<String[]> collect2 = collect
        .entrySet()
        .stream()
        .map(e -> new String[] { e.getKey(),
                String.valueOf(e.getValue().size()) })
        .collect(Collectors.toList());

collect2.forEach(o -> System.out.println(o[0] + " >> " + o[1]));

Agradeço sua opinião.

Muhammad Hewedy
fonte
1
O que você está tentando realizar aqui?
Keppil
2
É um caso muito comum, por exemplo, um erro ocorre em um período de tempo e quero ver algumas estatísticas sobre o número de ocorrências a cada dia nesse período.
Muhammad Hewedy

Respostas:

340

Eu acho que você está apenas procurando a sobrecarga que leva outro Collectorpara especificar o que fazer com cada grupo ... e depois Collectors.counting()fazer a contagem:

import java.util.*;
import java.util.stream.*;

class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        list.add("Hello");
        list.add("Hello");
        list.add("World");

        Map<String, Long> counted = list.stream()
            .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

        System.out.println(counted);
    }
}

Resultado:

{Hello=2, World=1}

(Também há a possibilidade de usar groupingByConcurrentpara obter mais eficiência. Algo a ter em mente para o seu código real, se for seguro no seu contexto.)

Jon Skeet
fonte
1
Perfeito! ... de javadocand then performing a reduction operation on the values associated with a given key using the specified downstream Collector
Muhammad Hewedy
6
Usar Function.identity () (com importação estática) em vez de e -> e torna um pouco melhor ler: Map <String, Long> count = list.stream (). Collect (groupingBy (identity (), counting ()) ));
Kuchi
Oi, Fiquei me perguntando se alguém poderia explicar o aspecto do mapa do código Map<String, Long> counted = list.stream() .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));, o que exatamente está acontecendo neste momento e quaisquer links com mais explicações associadas ao tópico que poderia ser enviado
Blank
@Blank: que parece que seria melhor como uma nova questão, com você explicando que partes dele que você não entender primeiro. Passar por todos os aspectos (sem saber qual parte você não entende) levaria muito tempo - mais tempo do que estou disposto a colocar em uma resposta com mais de 5 anos neste momento, quando a maioria delas já pode entender.
Jon Skeet
@ JonSkeet Cool, vou colocar em uma nova pergunta, embora tenha destacado o aspecto que não entendi na minha pergunta. Sendo assim, todo o trecho de código que eu adicionei.
branco
9
List<String> list = new ArrayList<>();

list.add("Hello");
list.add("Hello");
list.add("World");

Map<String, List<String>> collect = list.stream()
                                        .collect(Collectors.groupingBy(o -> o));
collect.entrySet()
       .forEach(e -> System.out.println(e.getKey() + " - " + e.getValue().size()));
Sivakumar
fonte
8

Aqui está um exemplo para a lista de objetos

Map<String, Long> requirementCountMap = requirements.stream().collect(Collectors.groupingBy(Requirement::getRequirementType, Collectors.counting()));
fjkjava
fonte
8

Aqui estão opções ligeiramente diferentes para realizar a tarefa em questão.

usando toMap:

list.stream()
    .collect(Collectors.toMap(Function.identity(), e -> 1, Math::addExact));

usando Map::merge:

Map<String, Integer> accumulator = new HashMap<>();
list.forEach(s -> accumulator.merge(s, 1, Math::addExact));
Ousmane D.
fonte
4

Aqui está a solução simples da StreamEx

StreamEx.of(list).groupingBy(Function.identity(), Collectors.countingInt());

Reduza o código padrão: collect(Collectors.

user_3380739
fonte
1
Qual é o motivo para usá-lo em fluxos Java8?
Torsten Ojaperv 16/07/19
1

Se você estiver aberto para usar uma biblioteca de terceiros, poderá usar a Collectors2classe em Coleções do Eclipse para convertê-la Listem uma Bagusando a Stream. A Bagé uma estrutura de dados criada para a contagem .

Bag<String> counted =
        list.stream().collect(Collectors2.countBy(each -> each));

Assert.assertEquals(1, counted.occurrencesOf("World"));
Assert.assertEquals(2, counted.occurrencesOf("Hello"));

System.out.println(counted.toStringOfItemToCount());

Resultado:

{World=1, Hello=2}

Nesse caso em particular, você pode simplesmente collecto Listdiretamente para a Bag.

Bag<String> counted = 
        list.stream().collect(Collectors2.toBag());

Você também pode criar o Bagsem usar a Stream, adaptando o Listcom os protocolos Eclipse Collections.

Bag<String> counted = Lists.adapt(list).countBy(each -> each);

ou neste caso particular:

Bag<String> counted = Lists.adapt(list).toBag();

Você também pode simplesmente criar a bolsa diretamente.

Bag<String> counted = Bags.mutable.with("Hello", "Hello", "World");

A Bag<String>é como um Map<String, Integer>, pois ele internamente controla as chaves e suas contagens. Mas, se você solicitar uma Mapchave que não contenha, ela retornará null. Se você solicitar uma Bagchave, ela não conterá o uso deoccurrencesOf , ela retornará 0.

Nota: Sou um colaborador das Coleções Eclipse.

Donald Raab
fonte