Agrupe contando en Java 8 stream API

170

Intento encontrar una manera simple en la API de transmisión Java 8 para hacer la agrupación, ¡salí con esta forma compleja!

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

Agradezco tu aporte.

Muhammad Hewedy
fuente
1
¿Qué estás tratando de lograr aquí?
Keppil
2
Es un caso muy común, por ejemplo, tengo un error en un período de tiempo y quiero ver algunas estadísticas sobre el número de incidencias por día en este período de tiempo.
Muhammad Hewedy

Respuestas:

341

Creo que solo está buscando la sobrecarga que requiere otra Collectorpara especificar qué hacer con cada grupo ... y luego Collectors.counting()hacer el recuento:

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}

(También existe la posibilidad de usar groupingByConcurrentpara una mayor eficiencia. Algo a tener en cuenta para su código real, si fuera seguro en su contexto).

Jon Skeet
fuente
1
¡Perfecto! ... de javadocand then performing a reduction operation on the values associated with a given key using the specified downstream Collector
Muhammad Hewedy
66
El uso de Function.identity () (con importación estática) en lugar de e -> e hace que sea un poco más agradable de leer: Map <String, Long> counted = list.stream (). Collect (groupingBy (identity (), countting () ));
Kuchi
Hola, me preguntaba si alguien podría explicar el aspecto del mapa del código Map<String, Long> counted = list.stream() .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));, qué está sucediendo exactamente en este momento y cualquier enlace con una explicación adicional asociada con el tema que podría enviarse
blanco
@Blank: que se siente como que sería mejor como una nueva pregunta, la que explica qué partes de la misma que no entienden en primer lugar. Pasar por todos los aspectos (sin saber qué parte no comprende) llevaría mucho tiempo, más tiempo del que estoy dispuesto a dar en una respuesta que tiene más de 5 años en este momento, cuando la mayoría Puede que ya lo entienda.
Jon Skeet
@ JonSkeet Cool, lo pondré en una nueva pregunta, aunque destaqué el aspecto que no entendí en mi pregunta. Es decir, todo el fragmento de código que agregué junto con él.
blanco
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
fuente
8

Aquí hay un ejemplo para la lista de objetos

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

Aquí hay opciones ligeramente diferentes para realizar la tarea en cuestión.

utilizando toMap:

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

utilizando Map::merge:

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

Aquí está la solución simple de StreamEx

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

Reduzca el código repetitivo: collect(Collectors.

user_3380739
fuente
1
¿Cuál es la razón para usarlo en secuencias Java8?
Torsten Ojaperv
1

Si está abierto a usar una biblioteca de terceros, puede usar la Collectors2clase en Eclipse Collections para convertir Lista a Bagusando a Stream. A Bages una estructura de datos creada para contar .

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

Salida:

{World=1, Hello=2}

En este caso particular, puede simplemente collectla Listdirectamente en una Bag.

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

También puede crear el archivo Bagsin usar un Streammediante la adaptación del Listprotocolo Eclipse Collections.

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

o en este caso particular:

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

También puede crear la bolsa directamente.

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

A Bag<String>es como un Map<String, Integer>en que internamente realiza un seguimiento de las teclas y sus cuentas. Pero, si solicita una Mapclave que no contiene, volverá null. Si solicita una Bagclave que no contiene occurrencesOf, devolverá 0.

Nota: Soy un committer para Eclipse Collections.

Donald Raab
fuente