java流找到匹配还是最后一个?

Posted

技术标签:

【中文标题】java流找到匹配还是最后一个?【英文标题】:java stream find match or the last one? 【发布时间】:2019-05-07 21:36:35 【问题描述】:

如何使用 java 流找到列表中的第一个匹配项或最后一个元素?

表示如果没有元素符合条件,则返回最后一个元素。

例如:

OptionalInt i = IntStream.rangeClosed(1,5)
                         .filter(x-> x == 7)
                         .findFirst();
System.out.print(i.getAsInt());

我应该怎么做才能让它返回 5;

【问题讨论】:

【参考方案1】:

给定列表

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

你可以这样做:

int value = list.stream().filter(x -> x == 2)
                         .findFirst()
                         .orElse(list.get(list.size() - 1));

这里如果过滤器的计算结果为真,则检索元素,否则返回最后一个元素。

如果列表是,你可以返回一个默认值,例如-1。

int value = list.stream().filter(x -> x == 2)
                         .findFirst()
                         .orElse(list.isEmpty() ? -1 : list.get(list.size() - 1));

【讨论】:

【参考方案2】:

你可以像这样使用reduce()函数:

OptionalInt i = IntStream.rangeClosed(1, 5)
        .reduce((first, second) -> first == 7 ? first : second);
System.out.print(i.getAsInt());

【讨论】:

太棒了。不过,您可能想使用 reducing () 收集器,它允许您提供一个标识元素,因此您可以处理空元素流和一个元素流。 @daniu 唯一的问题是这不是短路的,所以如果你的第一个匹配恰好在第一个元素上,你仍然需要遍历整个流的源,即使你知道结果已经出来了。 @Eugene 完全同意。基本上我会使用 LinkedHashSet 来检查元素是否存在,否则返回最后一个元素。 reduce 并不意味着执行搜索,而是执行关联操作,即元素的总和。 Stream 中有一些方法称为 findAnyfindFirst 来执行此操作。此外,如果流是并行的,这将不起作用,而 findFirst 将完成确切的工作,尽管流的特征。正如其他人所说,这也不会短路...... @FedericoPeraltaSchaffner 这个函数是关联的,所以并行执行不会有任何问题。所以唯一的问题是效率低下。【参考方案3】:

基本上我会使用以下两种方法之一或其偏差:

流变体:

<T> T getFirstMatchOrLast(List<T> list, Predicate<T> filter, T defaultValue) 
    return list.stream()
            .filter(filter)
            .findFirst()
            .orElse(list.isEmpty() ? defaultValue : list.get(list.size() - 1));

非流变体:

<T> T getFirstMatchOrLast(Iterable<T> iterable, Predicate<T> filter, T defaultValue) 
    T relevant = defaultValue;
    for (T entry : iterable) 
        relevant = entry;
        if (filter.test(entry))
            break;
    
    return relevant;

或者正如Ilmari Karonen 在Iterable&lt;T&gt; 的评论中建议的那样,您甚至可以致电stream::iterator,以防您真的处理Stream 而不是List。调用显示的方法如下所示:

getFirstMatchOrLast(Arrays.asList(1, 20, 3), i -> i == 20, 1); // returns 20
getFirstMatchOrLast(Collections.emptyList(), i -> i == 3, 20); // returns 20
getFirstMatchOrLast(Arrays.asList(1, 2, 20), i -> i == 7, 30); // returns 20
// only non-stream variant: having a Stream<Integer> stream = Stream.of(1, 2, 20)
getFirstMatchOrLast(stream::iterator, i -> i == 7, 30); // returns 20

我不会在这里使用reduce,因为这听起来对我来说是错误的,即使第一个条目可能已经匹配,它也会遍历整个条目,即它不再短路。此外,对我来说,它不像filter.findFirst.orElse 那样可读...(但这可能只是我的看法)

我什至可能会得到如下结果:

<T> Optional<T> getFirstMatchOrLast(Iterable<T> iterable, Predicate<T> filter) 
    T relevant = null;
    for (T entry : iterable) 
        relevant = entry;
        if (filter.test(entry))
            break;
    
    return Optional.ofNullable(relevant);

// or transform the stream variant to somethinng like that... however I think that isn't as readable anymore...

所以调用看起来像:

getFirstMatchOrLast(Arrays.asList(1, 2, 3, 5), i -> i == 7).orElseThrow(...)
getFirstMatchOrLast(Arrays.asList(1, 2, 3, 5), i -> i == 7).orElse(0);
getFirstMatchOrLast(Arrays.asList(1, 2, 3, 5), i -> i == 7).orElseGet(() -> /* complex formula */);
getFirstMatchOrLast(stream::iterator, i -> i == 5).ifPresent(...)

【讨论】:

我喜欢你的“非流变体”(有或没有可选),但我建议将它概括为接受任何Iterable&lt;T&gt; 而不仅仅是List&lt;T&gt;。这样,如果您确实 需要 处理流,you can just pass stream::iterator to this method.【参考方案4】:

如果您想在一个管道中执行此操作,那么您可以这样做:

int startInc = 1;
int endEx = 5;
OptionalInt first = 
       IntStream.concat(IntStream.range(startInc, endEx)
                .filter(x -> x == 7), endEx > 1 ? IntStream.of(endEx) : IntStream.empty())
                .findFirst();

但您最好将生成的数字收集到一个列表中,然后按如下方式对其进行操作:

// first collect the numbers into a list
List<Integer> result = IntStream.rangeClosed(startInc,endEx)
                                   .boxed()
                                   .collect(toList());
    // then operate on it 
int value = result.stream()
                  .filter(x -> x == 7)
                  .findFirst()
                  .orElse(result.get(result.size() - 1)); 

或者,如果您想让后者在源为空的情况下返回一个空 Optional(如果这是可能的情况)而不是异常,那么您可以这样做:

List<Integer> result = IntStream.rangeClosed(startInc,endEx)
                                .boxed()
                                .collect(toList());

Optional<Integer> first = 
         Stream.concat(result.stream().filter(x -> x == 7), result.isEmpty() ? 
                Stream.empty() : Stream.of(result.get(result.size() - 1)))
                .findFirst();

【讨论】:

【参考方案5】:

我不确定您为什么真的要为此使用流,一个简单的for-loop 就足够了:

public static <T> T getFirstMatchingOrLast(List<? extends T> source, Predicate<? super T> predicate)
    // handle empty case
    if(source.isEmpty())
        return null;
    
    for(T t : source)
        if(predicate.test(t))
            return t;
        
    
    return source.get(source.size() -1);
 

然后可以这样调用:

Integer match = getFirstMatchingOrLast(ints, i -> i == 7);

【讨论】:

以上是关于java流找到匹配还是最后一个?的主要内容,如果未能解决你的问题,请参考以下文章

nodejs 匹配字符串问题

Java 流 - 同时具有 anyMatch 和 noneMatch 操作的目的?

java io流对文件的增删改查

Java IO流中先关闭输出流还是先关闭输入流?为啥?

java读取文件问题该选字节流还是字符流?

如何找到张量流模型(.pb 格式的冻结图)、onnx 模型(.onnx 格式)的浮点精度,比如它是 FP32 还是 FP16?