Cómo contar la frecuencia de palabras de la Lista en Java 8?

List <String> wordsList = Lists.newArrayList("hello", "bye", "ciao", "bye", "ciao");

El resultado debe ser:

{ciao=2, hello=1, bye=2}
InformationsquelleAutor Mouna | 2015-03-18

8 Comentarios

  1. 83

    Quiero compartir la solución que he encontrado, porque al principio yo esperaba mapa de uso-y-reducir métodos, pero era un poco diferente.

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

    O para valores Enteros:

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

    EDITAR

    Puedo añadir cómo ordenar el mapa de valor:

    LinkedHashMap<String, Long> 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
                ));
  2. 27

    (NOTA: Vea las ediciones de abajo)

    Como una alternativa a la Mounas respuesta, aquí es un enfoque que hace el recuento de 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<String> list = Arrays.asList(
                "hello", "bye", "ciao", "bye", "ciao");
            Map<String, Integer> counts = list.parallelStream().
                collect(Collectors.toConcurrentMap(
                    w -> w, w -> 1, Integer::sum));
            System.out.println(counts);
        }
    }

    EDITAR En respuesta a los comentarios, me encontré con una pequeña prueba con JMH, comparando la toConcurrentMap y la groupingByConcurrent enfoque, con diferentes entradas de la lista de tamaños y palabras al azar, de diferentes longitudes. Esta prueba sugiere que la toConcurrentMap enfoque fue más rápido. Al considerar cómo estos diferentes enfoques están «bajo el capó», es difícil predecir algo como esto.

    Como una mayor extensión, en función de los comentarios, me extendió la prueba para cubrir todas las cuatro combinaciones de toMap, groupingBy, en serie y en paralelo.

    Los resultados son todavía que el toMap enfoque es más rápido, pero de forma inesperada (al menos, para mí), el «concurrente» de las versiones en ambos casos son más lentos que los de serie de las versiones…:

                 (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 soy tan experimentado con JMH, tal vez hice algo mal aquí – 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<String> 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<String, Integer> counts =
                    list.stream().collect(
                        Collectors.toMap(
                            w -> w, w -> 1, Integer::sum));
                bh.consume(counts);
            }
            else if (method.equals("toConcurrentMap"))
            {
                Map<String, Integer> counts =
                    list.parallelStream().collect(
                        Collectors.toConcurrentMap(
                            w -> w, w -> 1, Integer::sum));
                bh.consume(counts);
            }
            else if (method.equals("groupingBy"))
            {
                Map<String, Long> counts =
                    list.stream().collect(
                        Collectors.groupingBy(
                            Function.identity(), Collectors.<String>counting()));
                bh.consume(counts);
            }
            else if (method.equals("groupingByConcurrent"))
            {
                Map<String, Long> counts =
                    list.parallelStream().collect(
                        Collectors.groupingByConcurrent(
                            Function.identity(), Collectors.<String> 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<String> createRandomStrings(
            int count, int length, Random random)
        {
            List<String> list = new ArrayList<String>(count);
            for (int i = 0; i < count; i++)
            {
                list.add(createRandomString(length, random));
            }
            return list;
        }
    }

    Las veces solo son similares para la serie de casos de una lista con 10000 elementos, y 2 de la carta de las palabras.

    Podría ser útil para comprobar si incluso para los más grandes de la lista de tamaños, el de versiones concurrentes eventualmente superar a la serie, pero en la actualidad no tienen el tiempo para otro detallada referencia correr con todas estas configuraciones.

    • Supongo que, Collectors.groupingByConcurrent(w->w, Collectors.counting()) será más eficiente.
    • He añadido una EDICIÓN sobre esto.
    • Debe también la atención sobre el número de igualdad palabras. La opinión de una sola entrada de mapa podría tener un impacto significativo. Contando mil palabras distintas pueden trabajar sin ningún tipo de contención en Java 8 ConcurrentMap aunque yo no llamaría a almacenar 1s «contar». Así que, contando mil repeticiones de la misma palabra puede dar una imagen diferente…
    • Seguro, eso es lo que traté de cubrir (medida de lo razonablemente posible) con un tamaño de la lista de 10000 contiene palabras al azar, de longitud 2: Hay entre 6 y 32 (promedio: ~15) occurrances de las mismas palabras. Un rápido (!) prueba con 100000 veces «aa» parece que los tiempos son más similares, pero esta prueba no hablan mucho sobre el rendimiento en una aplicación realista caso.
    • Sería grande si usted podría publicar su código de referencia.
    • Yo no quiero engordar la respuesta con algo que realmente no responder a la pregunta, pero voy a limpiar y a publicar el código el día de hoy.
    • Comprensible, pero cuando se habla de alternativas, teniendo una comparación va a ser útil. Después de todo, incluso el OP de la propia groupingBy enfoque puede funcionar en paralelo y sería genial para ampliar el punto de referencia para ver las diferencias entre todas las variantes (groupingBy vs toMap)×(ordinario vs Concurrent)…

  3. 6

    Encontrar más frecuentes elemento en la colección, con los genéricos:

    private <V> V findMostFrequentItem(final Collection<V> 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);
    }

    Calcular elemento de frecuencias:

    private <V> Map<V, Long> findFrequencies(final Collection<V> items)
    {
      return items.stream()
          .filter(Objects::nonNull)
          .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
    }
  4. 3

    Aquí una manera de crear una frecuencia mapa mediante la asignación de funciones.

    List<String> words = Stream.of("hello", "bye", "ciao", "bye", "ciao").collect(toList());
    Map<String, Integer> frequencyMap = new HashMap<>();
    
    words.forEach(word ->
            frequencyMap.merge(word, 1, (v, newV) -> v + newV)
    );
    
    System.out.println(frequencyMap); //{ciao=2, hello=1, bye=2}

    O

    words.forEach(word ->
           frequencyMap.compute(word, (k, v) -> v != null ? v + 1 : 1)
    );
    • Me gusta mapa.de combinación, sin embargo no sé por qué usted hizo todo lo que el trabajo para crear la Lista de<Cadena> palabras ¿por qué no hacer : Lista de<Cadena> lista = Arrays.asList(«hola», «adiós», «ciao», «adiós», «ciao»)
    • Gracias! Acerca de la lista de código de creación, me mantengo alejado de las Matrices.asList porque devuelve un java.util.Matrices de$ArrayList en lugar de la normal de ArrayList. Yo prefiero no usarlo porque, finalmente, si los datos se van a serializarse en su aplicación, se crea no deseados serializado en la estructura de las Matrices.asList.
  5. 3

    Si utiliza Eclipse Colecciones, sólo se puede convertir el List a un Bolsa.

    Bag<String> words = 
        Lists.mutable.with("hello", "bye", "ciao", "bye", "ciao").toBag();
    
    Assert.assertEquals(2, words.occurrencesOf("ciao"));
    Assert.assertEquals(1, words.occurrencesOf("hello"));
    Assert.assertEquals(2, words.occurrencesOf("bye"));

    Este código funciona con Java 5 – 8.

    Nota: soy un confirmador de Eclipse Colecciones

  6. 2

    Voy a presentar aquí la solución que he hecho (el de la agrupación es mucho mejor 🙂 ).

    static private void test0(List<String> input) {
        Set<String> set = input.stream()
                .collect(Collectors.toSet());
        set.stream()
                .collect(Collectors.toMap(Function.identity(),
                        str -> Collections.frequency(input, str)));
    }

    Sólo mi 0.02$

  7. 0

    Otro 2% de la mina, dado un array:

    import static java.util.stream.Collectors.*;
    
    String[] str = {"hello", "bye", "ciao", "bye", "ciao"};    
    Map<String, Integer> collected 
    = Arrays.stream(str)
            .collect(groupingBy(Function.identity(), 
                        collectingAndThen(counting(), Long::intValue)));
  8. 0
    public class Main {
    
        public static void main(String[] args) {
    
    
            String testString ="qqwweerrttyyaaaaaasdfasafsdfadsfadsewfywqtedywqtdfewyfdweytfdywfdyrewfdyewrefdyewdyfwhxvsahxvfwytfx"; 
            long java8Case2 = testString.codePoints().filter(ch -> ch =='a').count();
            System.out.println(java8Case2);
    
            ArrayList<Character> list = new ArrayList<Character>();
            for (char c : testString.toCharArray()) {
              list.add(c);
            }
            Map<Object, Integer> counts = list.parallelStream().
                collect(Collectors.toConcurrentMap(
                    w -> w, w -> 1, Integer::sum));
            System.out.println(counts);
        }
    
    }

Dejar respuesta

Please enter your comment!
Please enter your name here