Сгруппировать, считая в Java 8 stream API


170

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

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
Что вы пытаетесь достичь здесь?
Keppil

2
Это очень распространенный случай, например, у меня ошибка произошла за определенный период времени, и я хочу видеть некоторые статистические данные о количестве событий за каждый день в этот период времени.
Мухаммед Хьюди

Ответы:


341

Я думаю, что вы просто ищете перегрузку, которая требует другого, 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для большей эффективности. Что-то, что нужно иметь в виду для вашего реального кода, если это будет безопасно в вашем контексте.)


1
Отлично! ... от javadocand then performing a reduction operation on the values associated with a given key using the specified downstream Collector
Мухаммед Хьюди

6
Использование Function.identity () (со статическим импортом) вместо e -> e делает чтение более приятным: Map <String, Long> countted = list.stream (). Collect (groupingBy (identity (), counting ()) ));
Кучи

Привет, мне было интересно, может ли кто-нибудь объяснить аспект кода в Map Map<String, Long> counted = list.stream() .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));, что именно происходит в этот момент, и любые ссылки с дополнительным объяснением, связанные с темой, которую можно отправить более
Бланк

@Blank: Это чувствует , как это было бы лучше всего, как новый вопрос, с вами объясняя , какие его части вы действительно понимаете первый. Прохождение каждого аспекта этого (не зная, какой бит вы не понимаете), заняло бы очень много времени - больше времени, чем я готов вложить в ответ, который на данный момент старше 5 лет, когда большинство из вас вы может уже понять.
Джон Скит

@JonSkeet Круто, я поставлю это на новый вопрос, хотя я выделил аспект, который я не понял в своем вопросе. Таким образом, весь фрагмент кода, который я добавил вместе с ним.
Пусто

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

8

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

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

8

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

используя 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

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

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

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


1
Какая причина использовать его в потоках Java8?
Торстен

1

Если вы открыты для использования библиотеки сторонних производителей , вы можете использовать Collectors2класс в Eclipse , Collections преобразовать Listк Bagиспользуя Stream. A 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}

В данном конкретном случае, вы можете просто непосредственно в .collectListBag

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

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.