Java Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Overview

In this short tutorial, we'll look at some different ways to count the duplicated elements in an ArrayList.

2. Loop with Map.put()

Our expected result would be a Map object, which contains all elements from the input list as keys and the count of each element as value.

The most straightforward solution to achieve this would be to loop through the input list and for each element:

  • if the resultMap contains the element, we increment a counter by 1
  • otherwise, we put a new map entry (element, 1) to the map
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;
}

This implementation has the best compatibility, as it works for all modern Java versions.

If we don't need the pre-Java 8 compatibility, we can simplify our method further:

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

Next, let's create an input list to test the method:

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

And now let's verify it:

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

We'll reuse this test harness for the rest of our approaches.

3. Loop with Map.compute()

In Java 8, the handy compute() method has been introduced to the Map interface. We can make use of this method as well:

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

Notice (k, v) -> v == null ? 1L : v + 1L is the remapping function that implements the BiFunction<T, Long, Long> interface. For a given key, it either returns its current value incremented by one (if the key is already present in the map) or returns the default value of one.

To make the code more readable, we could extract the remapping function to its variable or even take it as the input parameter for the countByForEachLoopWithMapCompute.

4. Loop with Map.merge()

When using Map.compute(), we must handle the null values explicitly – for instance, if a mapping for a given key doesn't exist. This is why we've implemented a null check in our remapping function. This, however, doesn't look pretty.

Let's clean up our code further with the help of Map.merge() method:

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

Now the code looks clean and concise.

Let's explain how merge() works. If the mapping for a given key doesn't exist, or its value is null, it associates the key with the provided value. Otherwise, it calculates a new value using the remapping function and updates the mapping accordingly.

Notice that this time we used Long::sum as the BiFunction<T, Long, Long> interface implementation.

5. Stream API Collectors.toMap()

Since we've already talked about Java 8, we can't forget the powerful Stream API. Thanks to the Stream API, we can solve the problem in a very compact way.

The toMap() collector helps us to convert the input list into a Map:

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

The toMap() is a convenient collector, which can help us to transform the stream into different Map implementations.

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

Except for the toMap(), our problem can be solved by two other collectors, groupingBy() and counting():

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

The proper usage of Java 8 Collectors makes our code compact and easy to read.

7. Conclusion

In this quick article, we illustrated various ways to calculate the count of duplicate elements in a list.

If you'd like to brush up on the ArrayList itself, you can check out the reference article.

As always, the complete source code is available over on GitHub.

Java bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
2 Comments
Oldest
Newest
Inline Feedbacks
View all comments
Moe
Moe
1 year ago

In the first method you could get rid of containsKey and use getOrDefault for counting:
Much more concise, yet readable, code:

HashMap resultMap = new HashMap(inputList.size());
for (String e: inputList) {
resultMap.put(e, resultMap.getOrDefault(e, 0l) + 1l);
}

Eric Martin
Eric Martin
11 months ago
Reply to  Moe

Hey Moe,
Thanks for your observation, we’ll look into it.
Cheers,
Team

Comments are closed on this article!