Как я могу подсчитать вхождения с помощью groupBy?

#java #loops #arraylist #count #compare

#java #функциональное программирование #java-8

Вопрос:

Я хочу собрать элементы в потоке в карту, которая группирует равные объекты вместе и сопоставляет количество вхождений.

 List<String> list = Arrays.asList("Hello", "Hello", "World");
Map<String, Long> wordToFrequency = // what goes here?
 

Итак, в этом случае я бы хотел, чтобы карта состояла из этих записей:

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

Комментарии:

1. Отлично! … из javadoc and then performing a reduction operation on the values associated with a given key using the specified downstream Collector

2. Использование Function.identity() (со статическим импортом) вместо e -> e делает чтение немного приятнее: Map<String, Long> counted = list.stream().collect(groupingBy(identity(), counting()));

3. Здравствуйте, у меня есть еще один вопрос, что, если я хочу показать его в порядке убывания? Если у меня есть 4 мира и 2 привета и что им показать {World = 4, Hello = 2}

4. @MichaelKors: Если у вас есть другой вопрос, вы должны задать его в виде отдельного сообщения после проведения соответствующих исследований.

Ответ №2:

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

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

Ответ №3:

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

Ответ №4:

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

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

Ответ №5:

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

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

Преимущество этого заключается в сокращении шаблонного кода Java stream: collect(Collectors.

Комментарии:

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

2. @TorstenOjaperv Единственная реальная причина в том, что он более лаконичен (это уменьшает шаблонность).

Ответ №6:

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

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

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

Вы также можете создать Bag без использования a 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> похож на a Map<String, Integer> в том, что он внутренне отслеживает ключи и их количество. Но, если вы Map попросите a указать ключ, которого он не содержит, он вернется null . Если вы запросите Bag у a ключ, который он не содержит using occurrencesOf , он вернет 0 .

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