使用 Streams 在两个列表 List<int[]> 中查找重复项

Find duplicates in two lists List<int[]> using Streams

使用流在两个列表 List 中查找重复项

我有一个 list 整数数组。我需要使用 2 int 数组列表找到这些数组的副本。

我确实尝试实现它,但我得到的是一个空数组。

keys = [[1,1,0], [0,0,1], [1,2,1], [1,3,1], [1,3,2]];
phaseKey = [[1,3,2], [1,2,1], [0,0,2], [1,2,3], [1,0,3]];
desired result: [[[1,3,2]], [1,2,1]];

我的代码:

Stream.concat(Stream.of(keys.stream().flatMapToInt(Arrays::stream)),                     
        Stream.of(phaseKey.stream().flatMapToInt(Arrays::stream)))
                            .collect(Collectors.groupingBy(
                                    Function.identity(),
                                    Collectors.counting()))
                            .entrySet()
                            .stream()
                            .filter(m -> m.getValue() > 1)
                            .map(Map.Entry::getKey)
                            .toArray();

鉴于您的两个列表:

List<int[]> keys = // ....
List<int[]> phaseKey = //...

您只需要过滤以在两个列表中找到共同的数组:

List<int[]> duplicates = keys.stream()
        .filter(k -> phaseKey.stream().anyMatch(p -> Arrays.equals(p,k)))
        .collect(Collectors.toList());

正如@Pshemo 已经在评论中指出的那样,数组没有正确实现 equals()hashCode() 方法。如果对于要用作地图中的键的每个对象,这都是至关重要的。

Java中的数组很特别。它们不是通过实例化 class 创建的,但它们是对象,并且它们从 java.lang.Object class 派生所有方法。因此,可通过数组访问的 equals()hashCode() 可用于建立唯一性,即这两个数组是否由内存中的同一对象表示。

为了确定两个数组的内容是否相同,您需要使用 Arrays 实用程序 class 提供的静态方法 equals()hashCode()。为了能够使用此功能,您可以创建一个 class 来包裹数组,根据数组内容实现 equals/hashCode 合约。

我选择将此包装器实现为 Java 16 record 以使其更精简,但您可以将其更改为常规 class.

public record ArrayWrapper(int[] arr) {
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ArrayWrapper other = (ArrayWrapper) o;
        return Arrays.equals(arr, other.arr);
    }
    
    @Override
    public int hashCode() {
        return Arrays.hashCode(arr);
    }
}

现在任何被该记录包裹的数组都可以用于 hash-based 数据结构。

因为你的例子有歧义,下面我列出了两种不同的实现方式。

选择两个列表中都存在的数组

为了检查 phaseKey 列表中包含的特定数组是否也存在于 keys 列表中,我们创建一个 HashSet 的 [=27] =] 对象,然后对该集合执行检查。这将允许在 线性时间 中解决此任务,通过每个列表仅执行 单次传递

public static void main(String[] args) {
    List<int[]> keys = List.of(new int[]{1, 1, 0}, new int[]{0, 0, 1}, new int[]{1, 2, 1},
                               new int[]{1, 3, 1}, new int[]{1, 3, 2});
    List<int[]> phaseKey = List.of(new int[]{1, 3, 2}, new int[]{1, 2, 1}, new int[]{0, 0, 2},
                               new int[]{1, 2, 3}, new int[]{1, 0, 3});

    Set<ArrayWrapper> wrappedKeys = keys.stream().map(ArrayWrapper::new).collect(Collectors.toSet());
    
    List<int[]> result = phaseKey.stream()
        .map(ArrayWrapper::new)
        .filter(wrappedKeys::contains)
        .distinct()                    // to ensure that each array will be present in the array only once
        .map(ArrayWrapper::arr)
        .collect(Collectors.toList()); // toList() with Java 16+
    
    result.forEach(arr -> System.out.println(Arrays.toString(arr)));
}

正在获取出现次数超过一次的数组

要查明特定数组是否在给定列表之一(或两个列表)中出现多次,我们可以通过将任何键的初始值分配为 [=29] 来创建中间映射 Map<ArrayWrapper,Boolean> =](不是重复项),并从旨在解决重复项的 mergeFunction 返回 true

public static void main(String[] args) {
    List<int[]> keys = List.of(new int[]{1, 1, 0}, new int[]{0, 0, 1}, new int[]{1, 2, 1},
        new int[]{1, 3, 1}, new int[]{1, 3, 2});
    List<int[]> phaseKey = List.of(new int[]{1, 3, 2}, new int[]{1, 2, 1}, new int[]{0, 0, 2},
        new int[]{1, 2, 3}, new int[]{1, 0, 3});
    
    List<int[]> result = Stream.of(keys, phaseKey)
        .flatMap(List::stream)
        .map(ArrayWrapper::new)
        .collect(Collectors.toMap(    // creates an intermediate map Map<ArrayWrapper, Boolean>
            Function.identity(),
            next -> false,            // first occurrence
            (left, right) -> true))   // all subsequent occurrences
        .entrySet().stream()
        .filter(Map.Entry::getValue)
        .map(Map.Entry::getKey)
        .map(ArrayWrapper::arr)
        .collect(Collectors.toList()); // toList() with Java 16+
    
    result.forEach(arr -> System.out.println(Arrays.toString(arr)));
}

输出

[1, 3, 2]
[1, 2, 1]