Java 检测流中的重复组

Java 检测流中的重复组,java,java-8,java-stream,Java,Java 8,Java Stream,我想确保列表中的所有数字都分组在一起。让我举例说明: {1, 1, 1, 2, 2} // OK, two distinct groups {1, 1, 2, 2, 1, 1} // Bad, two groups with "1" {1, 2, 3, 4} // OK, 4 distinct groups of size 1 {1, 1, 1, 1} // OK, 1 group {3, 4, 3} // Bad, two groups with

我想确保列表中的所有数字都分组在一起。让我举例说明:

{1, 1, 1, 2, 2}    // OK, two distinct groups
{1, 1, 2, 2, 1, 1} // Bad, two groups with "1"
{1, 2, 3, 4}       // OK, 4 distinct groups of size 1
{1, 1, 1, 1}       // OK, 1 group
{3, 4, 3}          // Bad, two groups with "3"
{99, -99, 99}      // Bad, two groups with "99"
{}                 // OK, no groups
以下是我获取流的方式:

IntStream.of(numbers)
    ...
现在我需要为“OK”示例传递或返回true,并为“Bad”示例抛出
AssertionError
或返回false。如何使用流API实现这一点

以下是我当前创建的附加
的解决方案:

Set<Integer> previousNumbers = new HashSet<>();
IntStream.of(numbers)
        .reduce(null, (previousNumber, currentNumber) -> {
                    if (currentNumber == previousNumber) {
                        assertThat(previousNumbers).doesNotContain(currentNumber);
                        previousNumbers.add(currentNumber);
                    }
                    return currentNumber;
                }
        );
Set previousNumbers=new HashSet();
IntStream.of(个数)
.reduce(null,(以前的数字,当前数字)->{
if(currentNumber==previousNumber){
资产(以前的编号)。不包含(当前编号);
以前的编号。添加(当前编号);
}
返回当前编号;
}
);
使用我的免费库:

如果存在重复组,此代码将抛出
IllegalStateException

这里使用的方法是。它折叠相等的相邻元素,用
Map.Entry
替换它们,其中key是输入元素,value是重复次数。最后使用了
toMap()
,这是
.collect(Collectors.toMap(Entry::getKey,Entry::getValue))
的快捷方式。我们使用的是这样一个事实:
.toMap()
在重复键时抛出
非法状态异常
(除非提供了自定义合并函数)


作为成功执行的免费奖励,您将拥有一个映射,其中键是输入元素,值是序列长度。

在我看来,这个问题根本不适合
流API
,但我很好奇如何实现这一点(不过是以性能方式实现的)

问题是您必须跟踪所看到的元素,并且整个测试应该具有短路行为。因此我提出了这个解决方案(没有
Streams
):


注意:为了并行化这个
,您应该使用线程安全的

我们可以尝试使用collect方法来回答这个问题。这种方法的问题(正如其他人所指出的)是,缩减操作不会很快终止

一般来说,要使长还原操作短路,我们可以使还原功能短路。这样,尽管我们仍然遍历流中的所有项,但所需的工作量是最小的

public static boolean hasUniqueGroups(int... arr) {
    return !IntStream
        .of(arr) 
        .collect(
                Container::new, // 1
                (container, current) -> {
                    if (container.skip) return; // 2
                    if (current != container.previous) {
                        container.previous = current;
                        if (!container.integers.add(current))
                            container.skip = true; // 3
                    }
                },
                (c1, c2) -> {
                    if (c1.skip != c2.skip) {
                        c1.skip = true;
                        c1.integers.addAll(c2.integers);
                    }
                }
        )
        .skip;
}

private static class Container {
    private int previous = MAX_VALUE; // 4
    private boolean skip = false;
    private Set<Integer> integers = new HashSet<>();
}


返回
false
。这当然不适用于无限流,但检查无限流中的唯一组实际上没有意义。

您的解决方案不正确。考虑到当前的实现(并且显然假定顺序执行),它可能会工作,但该函数显然违反了关联性要求。不幸的是,没有第三方的帮助就没有简单的解决方案…@Holger你能解释什么是“关联性要求”@MichalKordas,请参阅:累加器必须根据规范关联。很好,+1。这里的关键洞察是谓词
arr[i]!=arr[i-1]
。对于更一般的问题,我会使用收集器生成结果,但是对于这个特定的情况,使用
allMatch(seen::add)
是非常聪明的。另一方面,名称
hasMultipleGroups
有错误的含义;也许
hasUniqueGroups
会更好?@StuartMarks使用
收集器
是我第一次尝试,但它没有短路行为。因此,它不适用于这个问题。
public static boolean hasUniqueGroups(int[] arr) {
    Objects.requireNonNull(arr);
    Set<Integer> seen = new HashSet<>();
    for (int i = 0; i < arr.length; i++) {
        if (i == 0 || arr[i] != arr[i - 1]) {
            if (!seen.add(arr[i])) {
                return false;
            }
        }
    }
    return true;
}
public static boolean hasUniqueGroups(int[] arr) {
    Objects.requireNonNull(arr);
    Set<Integer> seen = new HashSet<>();
    return IntStream.range(0, arr.length)
            .filter(i -> i == 0 || arr[i] != arr[i - 1])
            .mapToObj(i -> arr[i])
            .allMatch(seen::add);
}
public static boolean hasUniqueGroups(int... arr) {
    return !IntStream
        .of(arr) 
        .collect(
                Container::new, // 1
                (container, current) -> {
                    if (container.skip) return; // 2
                    if (current != container.previous) {
                        container.previous = current;
                        if (!container.integers.add(current))
                            container.skip = true; // 3
                    }
                },
                (c1, c2) -> {
                    if (c1.skip != c2.skip) {
                        c1.skip = true;
                        c1.integers.addAll(c2.integers);
                    }
                }
        )
        .skip;
}

private static class Container {
    private int previous = MAX_VALUE; // 4
    private boolean skip = false;
    private Set<Integer> integers = new HashSet<>();
}
IntStream.of(arr)
IntStream.concat(IntStream.of(1, 2), IntStream.range(1, Integer.MAX_VALUE))