java stream найти совпадение или последний?

Как найти первое совпадение или последний элемент в списке с помощью 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));

Здесь, если фильтр получает значение true, элемент возвращается, иначе возвращается последний элемент в последнем.

Если список пуст, вы можете вернуть значение по умолчанию, например -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());

Ответ 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;
}

Или, как сказал Илмари Каронен в комментарии к 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 как 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(...)

Ответ 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)); 

Кроме того, если вы хотите, чтобы последний возвращал пустой Необязательный в случае, если источник был пустым (если это возможный сценарий) вместо исключения, вы могли бы сделать:

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

Ответ 6

Вы можете сделать это так,

List<Integer> ints = Arrays.asList(1, 2, 3, 4, 5);
int value = ints.stream().filter(x -> x == 7)
    .findFirst().orElse(ints.get(ints.size() - 1));

Вместо этого или возврата Optional вы можете передать значение по умолчанию, которое вы хотите использовать в методе orElse. Поэтому, если ни один элемент не соответствует заданным критериям, возвращается последний элемент в списке.

Ответ 7

Используя peek вы можете сохранить характеристики потока и не полагаться на коллекцию исходных элементов со случайным доступом. Поэтому это эффективное решение для памяти даже для больших потоков с миллионами элементов.

int latest = 0;
int result = IntStream.rangeClosed(1, 5).peek(x -> latest = x)
                      .filter(x -> x == 7).findAny().orElse(latest);

Ответ 8

Хорошо, если ваше описание соответствует фактическому вопросу, и у вас действительно есть List, это просто:

 yourList.stream()
         .filter(x -> x == 7)
         .findFirst()
         .orElse(yourList.get(yourList.size() - 1));