web-dev-qa-db-fra.com

java stream find match ou le dernier?

Comment trouver la première correspondance ou le dernier élément d'une liste en utilisant Java stream?

Ce qui signifie que si aucun élément ne correspond à la condition ,, renvoyez le dernier élément.

par exemple:

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

Que dois-je faire pour le faire revenir 5;

21
金潇泽

Compte tenu de la liste

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

Vous pourriez simplement faire:

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

Ici, si le filtre est évalué comme vrai, l'élément est récupéré, sinon le dernier élément du dernier est renvoyé.

Si la liste est vide vous pouvez retourner une valeur par défaut, par exemple -1.

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

Vous pouvez utiliser la fonction reduce() comme ça:

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

Fondamentalement, j'utiliserais l'une des deux méthodes ou écarts suivants:

Variante de flux:

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

variante sans flux:

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

Ou comme aussi Ilmari Karonen suggéré dans le commentaire avec Iterable<T> vous pouvez même appeler stream::iterator au cas où vous auriez vraiment affaire à un Stream au lieu d'un List. L'appel des méthodes affichées ressemblerait à ceci:

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

Je n'utiliserais pas reduce ici car cela me semble faux dans le sens où il passe également par toutes les entrées même si la première entrée aurait déjà pu correspondre, c'est-à-dire qu'elle ne court-circuite plus. De plus, pour moi, ce n'est pas aussi lisible que filter.findFirst.orElse... (mais c'est probablement juste mon avis)

Je finirais probablement même avec quelque chose comme suit:

<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...

pour que les appels ressemblent plutôt à:

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

si vous voulez faire cela dans un pipeline, vous pouvez faire:

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

mais vous feriez probablement mieux de collecter les nombres générés dans une liste, puis opérez-la comme suit:

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

Alternativement, si vous voulez que ce dernier retourne un optionnel vide dans le cas où la source est vide (si c'est un scénario possible) au lieu d'une exception, vous pouvez faire:

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

Je ne sais pas pourquoi vous voulez vraiment utiliser des streams pour cela, une simple boucle for- suffirait:

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

Qui peut alors s'appeler comme:

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