Saltar al contenido

Recuento de frecuencia de palabras Java 8

Viviana, parte de este gran equipo, nos ha hecho el favor de redactar esta reseña porque domina a la perfección el tema.

Quiero compartir la solución que encontré porque al principio esperaba usar métodos de mapa y reducción, pero fue un poco diferente.

Map collect = 
        wordsList.stream().collect(groupingBy(Function.identity(), counting()));

O para valores enteros:

Map collect = 
        wordsList.stream().collect(groupingBy(Function.identity(), summingInt(e -> 1)));

EDITAR

Añado cómo ordenar el mapa por valor:

LinkedHashMap countByWordSorted = collect.entrySet()
            .stream()
            .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
            .collect(Collectors.toMap(
                    Map.Entry::getKey,
                    Map.Entry::getValue,
                    (v1, v2) -> 
                        throw new IllegalStateException();
                    ,
                    LinkedHashMap::new
            ));

(NOTA: Vea las ediciones a continuación.)

Como alternativa a la respuesta de Mounas, aquí hay un enfoque que cuenta las palabras en paralelo:

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class ParallelWordCount

    public static void main(String[] args)
    
        List list = Arrays.asList(
            "hello", "bye", "ciao", "bye", "ciao");
        Map counts = list.parallelStream().
            collect(Collectors.toConcurrentMap(
                w -> w, w -> 1, Integer::sum));
        System.out.println(counts);
    

EDITAR En respuesta al comentario, realicé una pequeña prueba con JMH, comparando el toConcurrentMap y el groupingByConcurrent enfoque, con diferentes tamaños de lista de entrada y palabras aleatorias de diferentes longitudes. Esta prueba sugirió que el toConcurrentMap El acercamiento fue más rápido. Al considerar cuán diferentes son estos enfoques “bajo el capó”, es difícil predecir algo como esto.

Como una extensión adicional, basada en comentarios adicionales, extendí la prueba para cubrir las cuatro combinaciones de toMap, groupingBy, serial y paralelo.

Los resultados siguen siendo que toMap El enfoque es más rápido, pero inesperadamente (al menos para mí) las versiones “concurrentes” en ambos casos son más lentas que las versiones en serie …:

             (method)  (count) (wordLength)  Mode  Cnt     Score    Error  Units
      toConcurrentMap     1000            2  avgt   50   146,636 ±  0,880  us/op
      toConcurrentMap     1000            5  avgt   50   272,762 ±  1,232  us/op
      toConcurrentMap     1000           10  avgt   50   271,121 ±  1,125  us/op
                toMap     1000            2  avgt   50    44,396 ±  0,541  us/op
                toMap     1000            5  avgt   50    46,938 ±  0,872  us/op
                toMap     1000           10  avgt   50    46,180 ±  0,557  us/op
           groupingBy     1000            2  avgt   50    46,797 ±  1,181  us/op
           groupingBy     1000            5  avgt   50    68,992 ±  1,537  us/op
           groupingBy     1000           10  avgt   50    68,636 ±  1,349  us/op
 groupingByConcurrent     1000            2  avgt   50   231,458 ±  0,658  us/op
 groupingByConcurrent     1000            5  avgt   50   438,975 ±  1,591  us/op
 groupingByConcurrent     1000           10  avgt   50   437,765 ±  1,139  us/op
      toConcurrentMap    10000            2  avgt   50   712,113 ±  6,340  us/op
      toConcurrentMap    10000            5  avgt   50  1809,356 ±  9,344  us/op
      toConcurrentMap    10000           10  avgt   50  1813,814 ± 16,190  us/op
                toMap    10000            2  avgt   50   341,004 ± 16,074  us/op
                toMap    10000            5  avgt   50   535,122 ± 24,674  us/op
                toMap    10000           10  avgt   50   511,186 ±  3,444  us/op
           groupingBy    10000            2  avgt   50   340,984 ±  6,235  us/op
           groupingBy    10000            5  avgt   50   708,553 ±  6,369  us/op
           groupingBy    10000           10  avgt   50   712,858 ± 10,248  us/op
 groupingByConcurrent    10000            2  avgt   50   901,842 ±  8,685  us/op
 groupingByConcurrent    10000            5  avgt   50  3762,478 ± 21,408  us/op
 groupingByConcurrent    10000           10  avgt   50  3795,530 ± 32,096  us/op

No tengo tanta experiencia con JMH, tal vez hice algo mal aquí; las sugerencias y correcciones son bienvenidas:

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.infra.Blackhole;

@State(Scope.Thread)
public class ParallelWordCount


    @Param("toConcurrentMap", "toMap", "groupingBy", "groupingByConcurrent")
    public String method;

    @Param("2", "5", "10")
    public int wordLength;

    @Param("1000", "10000" )
    public int count;

    private List list;

    @Setup
    public void initList()
    
         list = createRandomStrings(count, wordLength, new Random(0));
    

    @Benchmark
    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    public void testMethod(Blackhole bh)
    

        if (method.equals("toMap"))
        
            Map counts =
                list.stream().collect(
                    Collectors.toMap(
                        w -> w, w -> 1, Integer::sum));
            bh.consume(counts);
        
        else if (method.equals("toConcurrentMap"))
        
            Map counts =
                list.parallelStream().collect(
                    Collectors.toConcurrentMap(
                        w -> w, w -> 1, Integer::sum));
            bh.consume(counts);
        
        else if (method.equals("groupingBy"))
        
            Map counts =
                list.stream().collect(
                    Collectors.groupingBy(
                        Function.identity(), Collectors.counting()));
            bh.consume(counts);
        
        else if (method.equals("groupingByConcurrent"))
        
            Map counts =
                list.parallelStream().collect(
                    Collectors.groupingByConcurrent(
                        Function.identity(), Collectors. counting()));
            bh.consume(counts);
        
    

    private static String createRandomString(int length, Random random)
    
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++)
        
            int c = random.nextInt(26);
            sb.append((char) (c + 'a'));
        
        return sb.toString();
    

    private static List createRandomStrings(
        int count, int length, Random random)
    
        List list = new ArrayList(count);
        for (int i = 0; i < count; i++)
        
            list.add(createRandomString(length, random));
        
        return list;
    

Los tiempos son sólo similares para el caso de serie de una lista con 10000 elementos y palabras de 2 letras.

Podría valer la pena comprobar si para tamaños de lista aún más grandes, las versiones simultáneas eventualmente superan a las seriales, pero actualmente no tienen tiempo para otra ejecución de referencia detallada con todas estas configuraciones.

Encuentre el artículo más frecuente en la colección, con genéricos:

private  V findMostFrequentItem(final Collection items)

  return items.stream()
      .filter(Objects::nonNull)
      .collect(Collectors.groupingBy(Functions.identity(), Collectors.counting()))
      .entrySet()
      .stream()
      .max(Comparator.comparing(Entry::getValue))
      .map(Entry::getKey)
      .orElse(null);

Calcule las frecuencias de los artículos:

private  Map findFrequencies(final Collection items)

  return items.stream()
      .filter(Objects::nonNull)
      .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)


Tags :

Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *