Java 8 Stream混合了两个元素

Man*_*pal 7 java java-8 java-stream

我在数组列表中有许多Slot类型的对象.

槽类如下图所示 -

Slot{
   int start;
   int end;
}
Run Code Online (Sandbox Code Playgroud)

让我们List<Slot>调用类型列表slots.插槽根据开始时间排序.一个时隙的结束时间可以等于下一个时隙的开始时间,但它们永远不会重叠.

有没有什么方法可以使用Java 8流迭代这个列表,如果一个的结束时间匹配下一个的开始时间并将它们输出到ArrayList?

Mis*_*sha 5

由于这些类型的问题出现了很多,我认为编写一个可以通过谓词对相邻元素进行分组的收集器可能是一个有趣的练习.

假设我们可以在Slot类中添加组合逻辑

boolean canCombine(Slot other) {
    return this.end == other.start;
}

Slot combine(Slot other) {
    if (!canCombine(other)) {
        throw new IllegalArgumentException();
    }
    return new Slot(this.start, other.end);
}
Run Code Online (Sandbox Code Playgroud)

所述groupingAdjacent然后收集器可以被使用如下:

List<Slot> combined = slots.stream()
    .collect(groupingAdjacent(
        Slot::canCombine,         // test to determine if two adjacent elements go together
        reducing(Slot::combine),  // collector to use for combining the adjacent elements
        mapping(Optional::get, toList())  // collector to group up combined elements
    ));
Run Code Online (Sandbox Code Playgroud)

或者,第二个参数可以是collectingAndThen(reducing(Slot::combine), Optional::get),第三个参数是toList()

这是源的groupingAdjacent.它可以处理null元素并且是并行友好的.有点麻烦,类似的事情可以做一个Spliterator.

public static <T, AI, I, AO, R> Collector<T, ?, R> groupingAdjacent(
        BiPredicate<? super T, ? super T> keepTogether,
        Collector<? super T, AI, ? extends I> inner,
        Collector<I, AO, R> outer
) {
    AI EMPTY = (AI) new Object();

    // Container to accumulate adjacent possibly null elements.  Adj can be in one of 3 states:
    // - Before first element: curGrp == EMPTY
    // - After first element but before first group boundary: firstGrp == EMPTY, curGrp != EMPTY
    // - After at least one group boundary: firstGrp != EMPTY, curGrp != EMPTY
    class Adj {

        T first, last;     // first and last elements added to this container
        AI firstGrp = EMPTY, curGrp = EMPTY;
        AO acc = outer.supplier().get();  // accumlator for completed groups

        void add(T t) {
            if (curGrp == EMPTY) /* first element */ {
                first = t;
                curGrp = inner.supplier().get();
            } else if (!keepTogether.test(last, t)) /* group boundary */ {
                addGroup(curGrp);
                curGrp = inner.supplier().get();
            }
            inner.accumulator().accept(curGrp, last = t);
        }

        void addGroup(AI group) /* group can be EMPTY, in which case this should do nothing */ {
            if (firstGrp == EMPTY) {
                firstGrp = group;
            } else if (group != EMPTY) {
                outer.accumulator().accept(acc, inner.finisher().apply(group));
            }
        }

        Adj merge(Adj other) {
            if (other.curGrp == EMPTY) /* other is empty */ {
                return this;
            } else if (this.curGrp == EMPTY) /* this is empty */ {
                return other;
            } else if (!keepTogether.test(last, other.first)) /* boundary between this and other*/ {
                addGroup(this.curGrp);
                addGroup(other.firstGrp);
            } else if (other.firstGrp == EMPTY) /* other container is single-group. prepend this.curGrp to other.curGrp*/ {
                other.curGrp = inner.combiner().apply(this.curGrp, other.curGrp);
            } else /* other Adj contains a boundary.  this.curGrp+other.firstGrp form a complete group. */ {
                addGroup(inner.combiner().apply(this.curGrp, other.firstGrp));
            }
            this.acc = outer.combiner().apply(this.acc, other.acc);
            this.curGrp = other.curGrp;
            this.last = other.last;
            return this;
        }

        R finish() {
            AO combined = outer.supplier().get();
            if (curGrp != EMPTY) {
                addGroup(curGrp);
                assert firstGrp != EMPTY;
                outer.accumulator().accept(combined, inner.finisher().apply(firstGrp));
            }
            return outer.finisher().apply(outer.combiner().apply(combined, acc));
        }
    }
    return Collector.of(Adj::new, Adj::add, Adj::merge, Adj::finish);
}
Run Code Online (Sandbox Code Playgroud)


Tag*_*eev 5

我的免费StreamEx库完美支持这种情况,它增强了标准的Stream API.有一个intervalMap中间操作,它能够将几个相邻的流元素折叠到单个元素.这是完整的例子:

// Slot class and sample data are taken from @Andreas answer
List<Slot> slots = Arrays.asList(new Slot(3, 5), new Slot(5, 7), 
                new Slot(8, 10), new Slot(10, 11), new Slot(11, 13));

List<Slot> result = StreamEx.of(slots)
        .intervalMap((s1, s2) -> s1.end == s2.start,
                     (s1, s2) -> new Slot(s1.start, s2.end))
        .toList();
System.out.println(result);
// Output: [3-7, 8-13]
Run Code Online (Sandbox Code Playgroud)

该intervalMap方法有两个参数.第一个是BiPredicate接受来自输入流的两个相邻元素,如果必须合并它们,则返回true(这里的条件是s1.end == s2.start).第二个参数是a BiFunction,它从合并的系列中获取第一个和最后一个元素,并生成结果元素.

请注意,如果您有例如100个相邻的插槽应该组合成一个,这个解决方案不会创建100个中间对象(比如在@ Misha的答案中,这仍然非常有趣),它会立即跟踪系列中的第一个和最后一个插槽忘记中间人.当然这个解决方案是并行友好的.如果您有数千个输入插槽,使用.parallel()可能会提高性能.

请注意,Slot即使它未与任何内容合并,当前实现也会重新创建.在这种情况下,两次BinaryOperator接收相同的Slot参数.如果您想优化此案例,可以进行额外检查,例如s1 == s2 ? s1 : ...:

List<Slot> result = StreamEx.of(slots)
        .intervalMap((s1, s2) -> s1.end == s2.start,
                     (s1, s2) -> s1 == s2 ? s1 : new Slot(s1.start, s2.end))
        .toList();
Run Code Online (Sandbox Code Playgroud)