选择列表的元素直到条件满足 Java 8 Lambda
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/32290278/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Picking elements of a list until condition is met with Java 8 Lambdas
提问by Julian
I am trying to switch my mind to think the functional way and recently faced a situation in which I needed to pick up elements from a list until a condition is met and I could not find an easy natural way of achieving this. Obviously I am still learning.
我试图将我的思想转变为功能方式,最近遇到了一种情况,我需要从列表中提取元素直到满足条件,但我找不到一种简单的自然方法来实现这一点。显然我还在学习。
Say I have this list:
说我有这个清单:
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;
}
}
Thank you in advance for your inputs
预先感谢您的意见
NOTE: Before publishing this I read Limit a stream by a predicatebut I could not see how I can adapt that answer to my problem. Any help would be appreciated thanks.
注意:在发布之前,我阅读了通过谓词限制流,但我不知道如何根据我的问题调整该答案。任何帮助将不胜感激谢谢。
采纳答案by WillShackleford
One option uses a collector requiring two functions one that adds strings to lists and another which combines lists previously potentially created in parallel. For each it adds the string or the whole list only if the previous partial output doesn't end with an element that that ends with STOP:
一种选择使用需要两个函数的收集器,一个是将字符串添加到列表中,另一个是组合以前可能并行创建的列表。对于每个它,仅当前一个部分输出不以以 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);
});
回答by Tagir Valeev
In JDK9 there will be a new Stream
operation called takeWhile
which does the thing similar to what you need. I backported this operation to my StreamExlibrary, so you can use it even in Java-8:
在 JDK9 中将有一个Stream
名为的新操作takeWhile
,它执行类似于您需要的操作。我将此操作反向移植到我的StreamEx库中,因此您甚至可以在 Java-8 中使用它:
List<String> list = StreamEx.of(tokens)
.takeWhile(t -> !t.toUpperCase().endsWith("STOP"))
.toList();
Unfortunately it does not take the "STOP"
element itself, so the second pass is necessary to add it manually:
不幸的是,它不采用"STOP"
元素本身,因此需要手动添加第二遍:
list.add(StreamEx.of(tokens).findFirst(t -> t.toUpperCase().endsWith("STOP")).get());
Note that both takeWhile
and findFirst
are short-circuit operations (they will not process the whole input stream if unnecessary), so you can use them with very long or even infinite streams.
请注意,takeWhile
和findFirst
都是短路操作(如果不需要,它们不会处理整个输入流),因此您可以将它们用于很长甚至无限的流。
However using StreamEx you can solve it in single pass using the trick with groupRuns
. The groupRuns
method groups adjacent Stream elements to the List
based on the supplied predicate which tells whether two given adjacent elements should be grouped or not. We may consider that the group ends with the element containing "STOP"
. Then we just need to take the first group:
但是,使用 StreamEx,您可以使用groupRuns
. 该groupRuns
方法List
根据提供的谓词将相邻的 Stream 元素分组到,该谓词指示是否应将两个给定的相邻元素分组。我们可以认为该组以包含 的元素结束"STOP"
。那么我们只需要取第一组:
List<String> list = StreamEx.of(tokens)
.groupRuns((a, b) -> !a.toUpperCase().endsWith("STOP"))
.findFirst().get();
This solution also will not do extra work when the first group is finished.
当第一组完成时,此解决方案也不会做额外的工作。
回答by Misha
If you really must use Streams API, keep it simple and use a stream of indexes:
如果您确实必须使用 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);
Or make a new List
out of the sublist if you want an independent copy that's not backed by the original list.
或者List
,如果您想要一个不受原始列表支持的独立副本,则从子列表中创建一个新副本。
回答by smac89
Using strictly just Java 8 API:
仅使用 Java 8 API:
public static <R> Stream<? extends R> takeUntil(Iterator<R> iterator, Predicate<? super R> stopFilter) {
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(new Iterator<R>() {
private R next = null;
private boolean hasTaken = true;
private boolean stopIteration = !iterator.hasNext();
@Override
public boolean hasNext() {
if (stopIteration) {
return false;
}
if (!hasTaken) {
return true;
}
if (!iterator.hasNext()) {
stopIteration = true;
return false;
}
next = iterator.next();
stopIteration = stopFilter.test(next);
hasTaken = stopIteration;
return !stopIteration;
}
@Override
public R next() {
if (!hasNext()) {
throw new NoSuchElementException("There are no more items to consume");
}
hasTaken = true;
return next;
}
}, 0), false);
}
You can then specialize it in the following ways:
然后,您可以通过以下方式对其进行专门化:
For streams
对于流
public static <R> Stream<? extends R> takeUntil(Stream<R> stream, Predicate<? super R> stopFilter) {
return takeUntil(stream.iterator(), stopFilter);
}
For collections
对于收藏
public static <R> Stream<? extends R> takeUntil(Collection<R> col, Predicate<? super R> stopFilter) {
return takeUntil(col.iterator(), stopFilter);
}
回答by Beno?t
Althought the above answers are perfectly valid, they require to collectand/or pre-fetchthe elements before processing them (both can be an issue if the Stream is very long).
尽管上述答案是完全有效的,但它们需要在处理元素之前收集和/或预取元素(如果 Stream 很长,两者都可能是一个问题)。
For my needs, I therefore adapted Louis's answerto the question pointed out by Julian and adapted it to keep the stop/break item. See the keepBreak
parameter ::
根据我的需要,我因此改编了路易斯对朱利安指出的问题的回答,并将其改编为保留停止/中断项目。查看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);
}
Usage:
用法:
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);
}
Disclaimer: I am not 100% sure this will work on parallel (the new Stream is certainly not parallel) or non-sequential streams. Please comment/edit if you have some hints on this.
免责声明:我不是 100% 确定这将适用于并行(新的 Stream 肯定不是并行的)或非顺序流。如果您对此有一些提示,请发表评论/编辑。