Перейти к основному содержимому

Как подсчитать повторяющиеся элементы в Arraylist

· 4 мин. чтения

1. Обзор

В этом коротком руководстве мы рассмотрим несколько различных способов подсчета повторяющихся элементов в ArrayList .

2. Цикл с Map.put()

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

Самым простым решением для достижения этого было бы перебрать список ввода и для каждого элемента:

  • если resultMap содержит элемент, мы увеличиваем счетчик на 1
  • в противном случае мы помещаем новую запись карты (элемент, 1) на карту
public <T> Map<T, Long> countByClassicalLoop(List<T> inputList) {
Map<T, Long> resultMap = new HashMap<>();
for (T element : inputList) {
if (resultMap.containsKey(element)) {
resultMap.put(element, resultMap.get(element) + 1L);
} else {
resultMap.put(element, 1L);
}
}
return resultMap;
}

Эта реализация имеет наилучшую совместимость, так как работает со всеми современными версиями Java.

Если нам не нужна совместимость до Java 8, мы можем еще больше упростить наш метод:

public <T> Map<T, Long> countByForEachLoopWithGetOrDefault(List<T> inputList) {
Map<T, Long> resultMap = new HashMap<>();
inputList.forEach(e -> resultMap.put(e, resultMap.getOrDefault(e, 0L) + 1L));
return resultMap;
}

Далее создадим входной список для тестирования метода:

private List<String> INPUT_LIST = Lists.list(
"expect1",
"expect2", "expect2",
"expect3", "expect3", "expect3",
"expect4", "expect4", "expect4", "expect4");

А теперь давайте проверим это:

private void verifyResult(Map<String, Long> resultMap) {
assertThat(resultMap)
.isNotEmpty().hasSize(4)
.containsExactly(
entry("expect1", 1L),
entry("expect2", 2L),
entry("expect3", 3L),
entry("expect4", 4L));
}

Мы будем повторно использовать этот тестовый набор для остальных наших подходов.

3. Цикл с Map.compute()

В Java 8 в интерфейс Map был добавлен удобный метод calculate() . Мы также можем использовать этот метод: ``

public <T> Map<T, Long> countByForEachLoopWithMapCompute(List<T> inputList) {
Map<T, Long> resultMap = new HashMap<>();
inputList.forEach(e -> resultMap.compute(e, (k, v) -> v == null ? 1L : v + 1L));
return resultMap;
}

Обратите внимание (k, v) -> v == null ? 1L : v + 1L — это функция переназначения, реализующая интерфейс BiFunction<T, Long, Long> . Для данного ключа он либо возвращает его текущее значение, увеличенное на единицу (если ключ уже присутствует в карте), либо возвращает значение по умолчанию, равное единице.

Чтобы сделать код более читабельным, мы могли бы извлечь функцию переназначения в ее переменную или даже взять ее в качестве входного параметра для countByForEachLoopWithMapCompute.

4. Цикл с Map.merge()

При использовании Map.compute() мы должны явно обрабатывать нулевые значения — например, если сопоставление для данного ключа не существует. Вот почему мы реализовали нулевую проверку в нашей функции переназначения. Однако это не выглядит красиво.

Давайте еще больше очистим наш код с помощью метода Map.merge() :

public <T> Map<T, Long> countByForEachLoopWithMapMerge(List<T> inputList) {
Map<T, Long> resultMap = new HashMap<>();
inputList.forEach(e -> resultMap.merge(e, 1L, Long::sum));
return resultMap;
}

Теперь код выглядит чистым и лаконичным.

Давайте объясним, как работает merge() . Если сопоставление для данного ключа не существует или его значение равно null , он связывает ключ с предоставленным значением. В противном случае он вычисляет новое значение с помощью функции переназначения и соответствующим образом обновляет сопоставление.

Обратите внимание, что на этот раз мы использовали Long::sum в качестве реализации интерфейса BiFunction<T, Long, Long> .

5. Сборщики потокового API.toMap()

Поскольку мы уже говорили о Java 8, нельзя забывать о мощном Stream API. Благодаря Stream API мы можем решить задачу очень компактно.

Сборщик toMap() помогает нам преобразовать входной список в карту :

public <T> Map<T, Long> countByStreamToMap(List<T> inputList) {
return inputList.stream().collect(Collectors.toMap(Function.identity(), v -> 1L, Long::sum));
}

toMap () — это удобный сборщик , который может помочь нам преобразовать поток в различные реализации карты .

6. Stream API Collectors.groupingBy() и Collectors.counting()

За исключением toMap() , нашу проблему можно решить двумя другими сборщиками, groupingBy() и counting() :

public <T> Map<T, Long> countByStreamGroupBy(List<T> inputList) {
return inputList.stream().collect(Collectors.groupingBy(k -> k, Collectors.counting()));
}

Правильное использование коллекторов Java 8 делает наш код компактным и легко читаемым.

7. Заключение

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

Если вы хотите освежить в памяти сам ArrayList, вы можете ознакомиться со справочной статьей .

Как всегда, полный исходный код доступен на GitHub .