web-dev-qa-db-ja.com

javaストリーム検索一致または最後の一致?

Java stream?を使用してリストの最初の一致または最後の要素を見つける方法

つまり、条件に一致する要素がない場合、最後の要素を返します。

例えば:

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

5を返すにはどうすればよいですか。

21
金潇泽

リストを考える

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));

ここで、フィルターがtrueと評価された場合、要素が取得されます。そうでない場合、最後の要素が返されます。

リストがemptyの場合、-1などのデフォルト値を返すことができます。

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

次のようなreduce()関数を使用できます。

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

基本的には、次の2つの方法のいずれか、またはその変形を使用します。

ストリームバリアント:

<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<T>そうすれば、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(...)
5
Roland

1つのパイプラインでこれを実行する場合は、次のようにします。

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();
3
Aomine

なぜあなたが本当にそのためにストリームを使用したいのか分かりません、単純な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);
2
Lino