选择列表的元素,直到Java 8 Lambdas满足条件

Jul*_*ian 15 java lambda java-8 java-stream

我试图改变主意思考功能方式,并且最近面临一种情况,我需要从列表中获取元素,直到满足条件,我找不到一种简单的自然方式来实现这一点.显然我还在学习.

说我有这个清单:

List<String> tokens = Arrays.asList("pick me", "Pick me", "pick Me",
    "PICK ME", "pick me and STOP", "pick me", "pick me and Stop", "pick me");

// In a non lambdas was you would do it like below
List<String> myTokens = new ArrayList<>();
for (String token : tokens) {
    myTokens.add(token);
    if (token.toUpperCase().endsWith("STOP")) {
        break;
    }
}
Run Code Online (Sandbox Code Playgroud)

提前感谢您的意见

注意:在发布之前,我读了一个谓词限制流,但我看不出如何能够根据我的问题调整答案.任何帮助将不胜感激.

Tag*_*eev 13

在JDK9中,将会有一个新的Stream操作takeWhile,它会使您的操作类似于您需要的操作.我将此操作向后移植到我的StreamEx库,因此即使在Java-8中也可以使用它:

List<String> list = StreamEx.of(tokens)
                            .takeWhile(t -> !t.toUpperCase().endsWith("STOP"))
                            .toList();
Run Code Online (Sandbox Code Playgroud)

不幸的是它不需要"STOP"元素本身,所以第二遍是必要的手动添加它:

list.add(StreamEx.of(tokens).findFirst(t -> t.toUpperCase().endsWith("STOP")).get());
Run Code Online (Sandbox Code Playgroud)

请注意这两个takeWhilefindFirst具有短路操作(如果必要,他们将不会处理整个输入流),这样你就可以很长,甚至无限流使用它们.

但是使用StreamEx,你可以使用诀窍一次性解决它groupRuns.该groupRuns方法List基于所提供的谓词将相邻的流元素分组,该谓词告知是否应该对两个给定的相邻元素进行分组.我们可以认为该组以包含的元素结束"STOP".然后我们只需要采取第一组:

List<String> list = StreamEx.of(tokens)
                            .groupRuns((a, b) -> !a.toUpperCase().endsWith("STOP"))
                            .findFirst().get();
Run Code Online (Sandbox Code Playgroud)

第一组完成后,此解决方案也不会执行额外的工作.

  • @TagirValeev无序`takeWhile`对于我们看到的传统的基于集合的问题确实无用.如果谓词基于外部状态,例如在固定时间内处理事件流,则证明是有用的. (2认同)

Mis*_*sha 10

如果您真的必须使用Streams API,请保持简单并使用索引流:

int lastIdx = IntStream.range(0, tokens.size())
        .filter(i -> tokens.get(i).toUpperCase().endsWith("STOP"))
        .findFirst()
        .orElse(-1);

List<String> myTokens = tokens.subList(0, lastIdx + 1);
Run Code Online (Sandbox Code Playgroud)

List如果您想要一个没有原始列表支持的独立副本,或者从子列表中创建一个新的.


sma*_*c89 6

严格使用 Java 8 API:

public static <R> Stream<? extends R> takeUntil(Iterator<R> iterator, Predicate<? super R> stopFilter) {
    final boolean isParallelStream = false;
    
    return StreamSupport.stream(Spliterators.spliteratorUnknownSize(new Iterator<R>() {
        private R next = null;
        private boolean conditionSatisfied = false;
        private boolean hasTaken = true;
        
        @Override
        public boolean hasNext() {
            if (conditionSatisfied || !iterator.hasNext()) {
                return false;
            }

            if (hasTaken) {
                next = iterator.next();
                conditionSatisfied = stopFilter.test(next);
                hasTaken = false;
            }
            return !conditionSatisfied;
        }

        @Override
        public R next() {
            if (!hasNext()) {
                throw new NoSuchElementException("There are no more items to consume");
            }
            hasTaken = true;
            return next;
        }
    }, 0), isParallelStream);
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以通过以下方式对其进行专业化:

对于流

public static <R> Stream<? extends R> takeUntil(Stream<R> stream, Predicate<? super R> stopFilter) {
    return takeUntil(stream.iterator(), stopFilter);
}
Run Code Online (Sandbox Code Playgroud)

对于收藏

public static <R> Stream<? extends R> takeUntil(Collection<R> col, Predicate<? super R> stopFilter) {
    return takeUntil(col.iterator(), stopFilter);
}
Run Code Online (Sandbox Code Playgroud)


Wil*_*ord 2

一种选项使用需要两个函数的收集器,一个函数将字符串添加到列表中,另一个函数组合先前可能并行创建的列表。对于每个输出,仅当先前的部分输出不以以 STOP 结尾的元素结尾时,它才会添加字符串或整个列表:

tokens.stream().collect(() -> new ArrayList<String>(), (l, e) -> {
    if(l.isEmpty() || !l.get(l.size()-1).toUpperCase().endsWith("STOP"))
        l.add(e);
}, (l1, l2) -> {
    if(l1.isEmpty() || !l1.get(l1.size()-1).toUpperCase().endsWith("STOP"))
        l1.addAll(l2);
});
Run Code Online (Sandbox Code Playgroud)

  • 请注意,此解决方案不是短路:您仍然需要迭代整个输入流,忽略“STOP”之后的所有内容。如果您有很长(或无限)的流,这可能会出现问题。 (5认同)
  • 我绝对不会在这里选择 O(n2) 的解决方案。 (2认同)