选取列表元素直到条件满足 Java 8 Lambdas
Picking elements of a list until condition is met with Java 8 Lambdas
我正在尝试转换思路,以功能方式思考,最近遇到一种情况,我需要从列表中选取元素直到满足条件,但我找不到一种简单自然的方法来实现这一点.显然我还在学习。
假设我有这个列表:
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;
}
}
提前感谢您的投入
注意:
在发布这篇文章之前,我阅读了 Limit a stream by a predicate,但我看不出如何使该答案适应我的问题。如有任何帮助,我们将不胜感激。
一个选项使用一个需要两个函数的收集器,一个函数将字符串添加到列表中,另一个函数组合以前可能并行创建的列表。仅当先前的部分输出不以以 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);
});
如果您真的必须使用流 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);
如果您想要一个不受原始列表支持的独立副本,则可以从子列表中创建一个新的 List
。
在 JDK9 中将有一个新的 Stream
操作称为 takeWhile
which does the thing similar to what you need. I backported this operation to my StreamEx 库,因此您甚至可以在 Java-8:
中使用它
List<String> list = StreamEx.of(tokens)
.takeWhile(t -> !t.toUpperCase().endsWith("STOP"))
.toList();
不幸的是它不带"STOP"
元素本身,所以第二遍需要手动添加:
list.add(StreamEx.of(tokens).findFirst(t -> t.toUpperCase().endsWith("STOP")).get());
请注意,takeWhile
和 findFirst
都是短路操作(如果不需要,它们不会处理整个输入流),因此您可以将它们用于非常长甚至无限的流。
然而,使用 StreamEx,您可以使用 groupRuns
的技巧在一次通过中解决它。 groupRuns
方法根据提供的谓词将相邻的 Stream 元素分组到 List
,该谓词告诉两个给定的相邻元素是否应该分组。我们可以认为该组以包含 "STOP"
的元素结束。那么我们只需要拿第一组:
List<String> list = StreamEx.of(tokens)
.groupRuns((a, b) -> !a.toUpperCase().endsWith("STOP"))
.findFirst().get();
当第一组完成时,此解决方案也不会做额外的工作。
尽管上述答案完全有效,但它们需要在处理元素之前 收集 and/or 预取 元素(如果流很长,两者都可能成为问题)。
对于我的 needs, I therefore adapted Louis's answer Julian 指出的问题,并对其进行了调整以保留 stop/break 项。请参阅 keepBreak
参数 ::
public static <T> Spliterator<T> takeWhile(final Spliterator<T> splitr, final Predicate<? super T> predicate, final boolean keepBreak) {
return new Spliterators.AbstractSpliterator<T>(splitr.estimateSize(), 0) {
boolean stillGoing = true;
@Override
public boolean tryAdvance(final Consumer<? super T> consumer) {
if (stillGoing) {
final boolean hadNext = splitr.tryAdvance(elem -> {
if (predicate.test(elem)) {
consumer.accept(elem);
} else {
if (keepBreak) {
consumer.accept(elem);
}
stillGoing = false;
}
});
return hadNext && (stillGoing || keepBreak);
}
return false;
}
};
}
public static <T> Stream<T> takeWhile(final Stream<T> stream, final Predicate<? super T> predicate, final boolean keepBreak) {
return StreamSupport.stream(takeWhile(stream.spliterator(), predicate, keepBreak), false);
}
用法:
public List<String> values = Arrays.asList("some", "words", "before", "BREAK", "AFTER");
@Test
public void testStopAfter() {
Stream<String> stream = values.stream();
//how to filter stream to stop at the first BREAK
stream = stream.filter(makeUntil(s -> "BREAK".equals(s)));
final List<String> actual = stream.collect(Collectors.toList());
final List<String> expected = Arrays.asList("some", "words", "before", "BREAK");
assertEquals(expected, actual);
}
免责声明:我不是 100% 确定这会在并行(新流肯定不是并行)或非顺序流上工作。如果您对此有一些提示,请comment/edit。
严格使用 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);
}
然后您可以通过以下方式对其进行专门化:
对于流
public static <R> Stream<? extends R> takeUntil(Stream<R> stream, Predicate<? super R> stopFilter) {
return takeUntil(stream.iterator(), stopFilter);
}
对于collections
public static <R> Stream<? extends R> takeUntil(Collection<R> col, Predicate<? super R> stopFilter) {
return takeUntil(col.iterator(), stopFilter);
}
我正在尝试转换思路,以功能方式思考,最近遇到一种情况,我需要从列表中选取元素直到满足条件,但我找不到一种简单自然的方法来实现这一点.显然我还在学习。
假设我有这个列表:
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;
}
}
提前感谢您的投入
注意: 在发布这篇文章之前,我阅读了 Limit a stream by a predicate,但我看不出如何使该答案适应我的问题。如有任何帮助,我们将不胜感激。
一个选项使用一个需要两个函数的收集器,一个函数将字符串添加到列表中,另一个函数组合以前可能并行创建的列表。仅当先前的部分输出不以以 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);
});
如果您真的必须使用流 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);
如果您想要一个不受原始列表支持的独立副本,则可以从子列表中创建一个新的 List
。
在 JDK9 中将有一个新的 Stream
操作称为 takeWhile
which does the thing similar to what you need. I backported this operation to my StreamEx 库,因此您甚至可以在 Java-8:
List<String> list = StreamEx.of(tokens)
.takeWhile(t -> !t.toUpperCase().endsWith("STOP"))
.toList();
不幸的是它不带"STOP"
元素本身,所以第二遍需要手动添加:
list.add(StreamEx.of(tokens).findFirst(t -> t.toUpperCase().endsWith("STOP")).get());
请注意,takeWhile
和 findFirst
都是短路操作(如果不需要,它们不会处理整个输入流),因此您可以将它们用于非常长甚至无限的流。
然而,使用 StreamEx,您可以使用 groupRuns
的技巧在一次通过中解决它。 groupRuns
方法根据提供的谓词将相邻的 Stream 元素分组到 List
,该谓词告诉两个给定的相邻元素是否应该分组。我们可以认为该组以包含 "STOP"
的元素结束。那么我们只需要拿第一组:
List<String> list = StreamEx.of(tokens)
.groupRuns((a, b) -> !a.toUpperCase().endsWith("STOP"))
.findFirst().get();
当第一组完成时,此解决方案也不会做额外的工作。
尽管上述答案完全有效,但它们需要在处理元素之前 收集 and/or 预取 元素(如果流很长,两者都可能成为问题)。
对于我的 needs, I therefore adapted Louis's answer Julian 指出的问题,并对其进行了调整以保留 stop/break 项。请参阅 keepBreak
参数 ::
public static <T> Spliterator<T> takeWhile(final Spliterator<T> splitr, final Predicate<? super T> predicate, final boolean keepBreak) {
return new Spliterators.AbstractSpliterator<T>(splitr.estimateSize(), 0) {
boolean stillGoing = true;
@Override
public boolean tryAdvance(final Consumer<? super T> consumer) {
if (stillGoing) {
final boolean hadNext = splitr.tryAdvance(elem -> {
if (predicate.test(elem)) {
consumer.accept(elem);
} else {
if (keepBreak) {
consumer.accept(elem);
}
stillGoing = false;
}
});
return hadNext && (stillGoing || keepBreak);
}
return false;
}
};
}
public static <T> Stream<T> takeWhile(final Stream<T> stream, final Predicate<? super T> predicate, final boolean keepBreak) {
return StreamSupport.stream(takeWhile(stream.spliterator(), predicate, keepBreak), false);
}
用法:
public List<String> values = Arrays.asList("some", "words", "before", "BREAK", "AFTER");
@Test
public void testStopAfter() {
Stream<String> stream = values.stream();
//how to filter stream to stop at the first BREAK
stream = stream.filter(makeUntil(s -> "BREAK".equals(s)));
final List<String> actual = stream.collect(Collectors.toList());
final List<String> expected = Arrays.asList("some", "words", "before", "BREAK");
assertEquals(expected, actual);
}
免责声明:我不是 100% 确定这会在并行(新流肯定不是并行)或非顺序流上工作。如果您对此有一些提示,请comment/edit。
严格使用 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);
}
然后您可以通过以下方式对其进行专门化:
对于流
public static <R> Stream<? extends R> takeUntil(Stream<R> stream, Predicate<? super R> stopFilter) {
return takeUntil(stream.iterator(), stopFilter);
}
对于collections
public static <R> Stream<? extends R> takeUntil(Collection<R> col, Predicate<? super R> stopFilter) {
return takeUntil(col.iterator(), stopFilter);
}