Поток Java найти совпадение или последний?

Как найти первое совпадение или последний элемент в списке с помощью потока Java?

Это означает, что если ни один элемент не соответствует условию ,, тогда возвращается последний элемент.

например:

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

Что делать, чтобы вернуть 5;

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
23
0
6 822
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

если вы хотите сделать это в одном конвейере, вы можете:

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();
Ответ принят как подходящий

Учитывая список

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

Вы можете использовать функцию reduce() так:

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

Это блестяще. Однако вы можете захотеть использовать сборщик reducing () , который позволяет вам указывать элемент идентификации, чтобы вы могли обрабатывать пустые потоки и потоки с одним элементом.

daniu 06.12.2018 10:15

@daniu единственная проблема заключается в том, что это не короткое замыкание, поэтому, если ваше первое совпадение окажется на первом элементе, вам все равно придется пройти весь источник потока, даже если вы уже знаете результат.

Eugene 06.12.2018 10:39

@ Евгений полностью согласен. В основном я бы использовал LinkedHashSet для проверки существования элемента и в противном случае возвращал бы последний элемент.

statut 06.12.2018 10:44

@Eugene хороший замечание, но короткое замыкание ли reduce ()? Кроме того, на самом деле существует reduce () с элементом идентификации, но к тому времени, когда я его нашел, я уже не редактировал.

daniu 06.12.2018 10:50

@daniu не требуется специальной обработки одиночных потоков элементов. Кроме того, для пустого потока это просто возвращает пустой Optional, который позволяет справляться с ситуацией любым удобным для вас способом, без необходимости изобретать элемент идентификации для операции.

Holger 06.12.2018 10:53

@daniu нет такого reduce, который был бы короткозамкнутым, в нашей базе кода на работе мы генерируем исключение без трассировки стека для выхода из сокращения, но обход источника в нашем случае немного дороже, то есть причина, по которой мы это делаем

Eugene 06.12.2018 10:59
reduce предназначен не для поиска, а для ассоциативных операций, то есть для суммирования элементов. Для этого в Stream есть методы, называемые findAny и findFirst. Кроме того, если бы поток был параллельным, это не сработало бы, в то время как findFirst выполнял бы точную работу, несмотря на характеристики потока. Как заявляли другие, это тоже не вызывает короткого замыкания ...
fps 06.12.2018 13:36

@FedericoPeraltaSchaffner: эта функция ассоциативна, поэтому у нее не будет проблем с параллельным выполнением. Так что проблема только в том, что это неэффективно.

Holger 06.12.2018 14:43

@Holger Я поправляюсь. Функция на самом деле ассоциативна, поэтому математически верна.

fps 06.12.2018 15:57

Я не уверен, почему вы действительно хотите использовать для этого потоки, простого цикла for будет достаточно:

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

В основном я бы использовал один из следующих двух методов или их отклонения:

Вариант потока:

<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 ... (но это, наверное, только мое мнение)

Я, вероятно, тогда даже получу что-то вроде этого:

<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<T>, а не только List<T>. Таким образом, если вы действительно выполняете необходимость для обработки потока, вы можете просто передать stream::iterator этому методу.

Ilmari Karonen 06.12.2018 20:03

Другие вопросы по теме