Группировать путем подсчета в Java 8 stream API

Я пытаюсь найти простой способ в API-интерфейсе Java 8 для выполнения группировки, я получаю этот сложный способ!

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

Я ценю ваш вклад.

Ответ 1

Я думаю, вы просто ищете перегрузку , которая принимает еще один Collector, чтобы указать, что делать с каждой группой... и затем Collectors.counting() выполнить подсчет:

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

Результат:

{Hello=2, World=1}

(Также существует возможность использования groupingByConcurrent для большей эффективности. Что-то нужно помнить для вашего реального кода, если бы это было безопасно в вашем контексте.)

Ответ 2

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

Ответ 3

Вот несколько разных вариантов выполнения поставленной задачи.

используя toMap:

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

используя Map::merge:

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

Ответ 4

Вот пример для списка объектов

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

Ответ 5

Вот простое решение StreamEx

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

Уменьшите код шаблона: collect(Collectors.

Ответ 6

Если вы открыты для использования сторонней библиотеки, вы можете использовать класс Collectors2 в Eclipse Collections для преобразования List в Bag с помощью Stream. Bag - это структура данных, созданная для подсчета.

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

Выход:

{World=1, Hello=2}

В этом конкретном случае вы можете просто collect List прямо в Bag.

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

Вы также можете создать Bag без использования Stream, адаптировав List с протоколами Eclipse Collections.

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

или в данном конкретном случае:

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

Вы также можете просто создать сумку напрямую.

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

Bag<String> похож на Map<String, Integer> в том, что он внутренне отслеживает ключи и их количество. Но, если вы спросите Map о ключе, который в ней не содержится, он вернет null. Если вы попросите Bag для ключа, который он не содержит с помощью occurrencesOf, он вернет 0.

Примечание: я являюсь коммиттером для Eclipse Collections.