如何计算Arraylist中的重复元素

2023/06/07

1. 概述

在这个简短的教程中,我们将介绍一些不同的方法来计算ArrayList中的重复元素。

2. 循环使用Map.put()

我们的预期结果是一个Map对象,它包含输入列表中的所有元素作为键,每个元素的计数作为值。

实现此目的最直接的解决方案是遍历输入列表并针对每个元素:

  • 如果resultMap包含该元素,我们将计数器加1
  • 否则,我们将一个新的Map条目(element, 1)放入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;
}

此实现具有最佳兼容性,因为它适用于所有现代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中,方便的compute()方法已被引入到Map接口中。我们也可以使用这个方法:

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>接口的重映射函数。对于给定的键,它要么返回其当前值加一(如果该键已经存在于Map中),要么返回默认值1。

为了使代码更具可读性,我们可以将重映射函数提取到它的变量中,甚至将其作为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,它会将键与提供的值相关联。否则,它会使用重映射函数计算一个新值并相应地更新Map。

请注意,这次我们使用Long::sum作为BiFunction<T, Long, Long>接口实现。

5. Stream API Collectors.toMap()

既然我们已经谈到了Java 8,那么就不能忘记强大的Stream API。感谢Stream API,我们可以用非常紧凑的方式解决问题。

toMap()收集器帮助我们将输入列表转换为Map:

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

toMap()是一个方便的收集器,它可以帮助我们将Stream转换为不同的Map实现。

6. Stream APICollectors.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 Collectors的正确使用使我们的代码紧凑且易于阅读。

7. 总结

在这篇简短的文章中,我们说明了计算列表中重复元素计数的各种方法。

如果你想复习ArrayList本身,可以查看参考文章

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章