如何计算数组列表中的重复元素

评论 0 浏览 0 2019-10-26

1.概述

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

2.用Map.put()进行循环。

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

实现这一目标的最直接的解决方案是循环浏览输入列表,并对每个元素进行处理。

  • 如果resultMap包含该元素,我们就将一个计数器增加1。
  • 否则,我们输入一个新的条目(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中,Map接口中引入了方便的compute()方法。我们也可以利用这个方法。

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

为了使代码更具可读性,我们可以将重映射函数提取到其变量中,或者甚至将其作为countByForEachLoopWithMapCompute.的输入参数。

4.用Map.merge()进行循环

当使用Map.compute()时,我们必须明确地处理null值 – 例如,如果一个给定键的映射不存在。这就是为什么我们在重映射函数中实现了null检查。然而,这看起来并不漂亮。

让我们在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. 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() 是一个方便的收集器,它可以帮助我们将数据流转化为不同的Map实现。

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 Collectors,可以使我们的代码变得紧凑,易于阅读。

7.结语

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

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

一如既往,完整的源代码可在GitHub上获得

最后更新2023-03-11
0 个评论
标签