Java流:查找相邻值匹配的最简单方法

3

如何将这个迭代的代码块转换为流呢?

Integer prev = -1;
boolean adjacentPair = false;

for(Integer num : numberList) {
    if (num.equals(prev)) {
        adjacentNumberSame = true;
        break;
    }
    prev = num;
}
return adjacentNumberSame 

3
为什么使用流?这很简单快速。虽然流可能较慢,但它们仍然是有用的。 - Rick Suggs
1
你知道编程中的Advent of Code吗?所以我正在将所有迭代解决方案转换为Java流,以加强对流的理解和掌握。我知道流可能不会更快,但是在流中解决简单问题将有助于之后在更高层次上解决问题。 - tjago
为了加强我的stream理解 - 是的,一开始多练习很好。但要记住,stream并不总是比for好。例如,在这里,for易于理解和直观,而stream笨重且难以阅读。 - Arnaud Claudel
4个回答

4

这是一种(不太好的)实现方式:

IntStream.range(1, numberList.size())
         .anyMatch(i -> numberList.get(i).equals(numberList.get(i-1)));

如果您的numberListArrayList或类似的数据结构,那么它的时间复杂度是可以接受的。如果不是,最好使用iterator。用法如下:
var Iter = list.stream().iterator();
var res = list.stream()
              .skip(1)
              .map(v -> v.equals(Iter.next()) )
              .anyMatch(v -> v)
              ;

更优雅的方式需要使用一些带有zip功能的第三方库。使用zip,代码如下:

var res = zip(
              list.stream().skip(1),
              list.stream(),
              (a, b) -> a.equals(b)
          ).anyMatch(x->x);

您可以自己编写zip,例如:
public static <A,B,C> Stream<C> zip(Stream<A> listA, Stream<B> listB, BiFunction<A,B,C> zipper){
    var iB = listB.iterator();
    return listA.filter( a -> iB.hasNext())
                .map( a -> zipper.apply(a, iB.next()) );
}

提示:我不建议像@vbezhenar的回答中使用reduce,因为它不能短路,如您可以在这里看到 https://repl.it/repls/DisastrousWideRevisioncontrol(查找异常)


我觉得使用 anyMatch 解决方案的代码非常直观,至少对于整数列表是这样。感谢您额外付出努力提供拉链代码片段。它将成为寻找相同解决方案的人们的良好参考。谢谢 x00! - tjago

4
您可以使用reduce操作。示例代码:
public static void main(String[] args) {
    List<Integer> list = Arrays.asList(1, 2, 2, 3);
    class Result {
        boolean adjacentPair = false;
        Integer prevItem = null;
    }
    boolean adjacentPair = list.stream().reduce(new Result(),
            (result, item) -> {
                result.adjacentPair = result.adjacentPair || item.equals(result.prevItem);
                result.prevItem = item;
                return result;
            },
            (result1, result2) -> {
                throw new UnsupportedOperationException();
            }
    ).adjacentPair;
    System.out.println(adjacentPair);
}

Result 类保存中间结果。 adjacentPair 字段指示是否遇到了相邻的一对元素。 prevItem 字段保存上一次迭代时用于比较的前一个元素值。此函数的第三个参数是 combiner,它用于并行流,但此示例适用于串行流。


非常原创的方法,我曾经认为缩减可以解决问题,但不知道具体如何操作。这是一个很好的解决方案示例,特别是当问题扩大到更大的范围时。 - tjago

1
你不需要做那么多。在你的方法主体中实现这个:
return numberList.contains(prev);

例如,您可以有以下内容:
public boolean adjacentNumberIsSame(int currentValue, ArrayList<Integer> numberList){
    return numberList.contains(currentValue);
}

嗨,谢谢你的建议。如果我想在列表中查找一个出现,这很好。但是在这里,我们只想检查相邻的值是否匹配。因此,在您的示例中,如果第一个匹配最后一个值,则返回True。这与伪代码尝试检查的相反。 - tjago
@tjago,你需要传入一个包含相邻值的列表。你应该将创建相邻值列表和检查值的过程拆分成两个不同的方法。 - C. Williams

1
流并不是最好的选择。这是一个相当普遍的说法。
一个特殊的问题在于,你无法折叠和短路。
我想出来的“流式解决方案”类似于@vbezhenar,但是它表现良好(具有组合器)并且不引入累加器类。
    // Accumulator is: { leftElement, match, rightElement }
    //    (Could introduce a record.)
    // States of the accumulator are:
    //    { null null null } - initial
    //    { x null x } - first element
    //    { x m null } - found match m
    //    { x null y } - no matches found
    Integer[] result = numberList.stream().<Integer[]>collect(
        // supplier
        () -> new Integer[3],
        // accumulator
        (acc,t) -> {
            if (acc[0] == null) {
                // First into acc.
                acc[0] = acc[2] = t;
            } else if (acc[1] != null) {
                // Already found pair to the left.
            } else if (t.equals(acc[2])) {
                // Found first pair.
                acc[1] = t;
                acc[2] = null;
            } else {
                // Otherwise, t replaces rightmost.
                acc[2] = t;
            }
        },
        // combiner
        (acc, other) -> {
            if (acc[1] != null) {
                // Alread found pair to the left.
            } else if (acc[2] == null) {
                // Necessary anyone? Empty acc is combined.
                acc[0] = other[0];
                acc[1] = other[1];
                acc[2] = other[2];
            } else if (acc[2].equals(other[0])) {
                // Found match.
                acc[1] = acc[2];
                acc[2] = null;
            } else {
                // Otherwise, leftmost element with rest as other acc.
                acc[1] = other[1];
                acc[2] = other[2];
            }
        }
    );
    return result[1] != null;

我实际上没有测试过这个合并器。我怀疑大多数合并器从未被测试过

如果我们对流的行为做出类似于@vbezhenar的假设,那么dropWhile就是我们的Johnny 5朋友。

    return numberList.stream().dropWhile(new Predicate<>() {
        Integer last;
        public boolean test(Integer t) {
            Integer lastLast = last;
            last = t;
            return !t.equals(lastLast);
        }
    }).findFirst().isPresent();

匿名内部类赢了!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接