179 votes

Java 8 : Limiter un flux infini par un prédicat

Existe-t-il une opération de flux de Java 8 qui limite un (potentiellement infini) Stream jusqu'au premier élément qui ne correspond pas à un prédicat ? Quelque chose qui ressemble à la (inexistante) takeWhile dans l'exemple ci-dessous et imprimerait tous les nombres inférieurs à 10 ?

IntStream
    .iterate(1, n -> n + 1)
    .takeWhile(n -> n < 10)
    .forEach(System.out::println);

Si une telle opération n'existe pas, quelle est la meilleure façon de l'implémenter de manière générale ?

80voto

Louis Wasserman Points 67557

Une telle opération devrait être possible avec un Java 8 Stream mais elle ne peut pas nécessairement être effectuée de manière efficace. Par exemple, on ne peut pas nécessairement paralléliser une telle opération, car il faut regarder les éléments dans l'ordre.

L'API ne fournit pas de moyen facile de le faire, mais le moyen le plus simple est probablement de prendre Stream.iterator() Enveloppez le Iterator d'avoir une implémentation "take-while", et ensuite de revenir à une Spliterator et ensuite un Stream . Ou -- peut-être -- envelopper le Spliterator bien qu'il ne puisse plus vraiment être divisé dans cette implémentation.

Voici une implémentation non testée de takeWhile sur un Spliterator :

static <T> Spliterator<T> takeWhile(
    Spliterator<T> splitr, Predicate<? super T> predicate) {
  return new Spliterators.AbstractSpliterator<T>(splitr.estimateSize(), 0) {
    boolean stillGoing = true;
    @Override public boolean tryAdvance(Consumer<? super T> consumer) {
      if (stillGoing) {
        boolean hadNext = splitr.tryAdvance(elem -> {
          if (predicate.test(elem)) {
            consumer.accept(elem);
          } else {
            stillGoing = false;
          }
        });
        return hadNext && stillGoing;
      }
      return false;
    }
  };
}

49voto

Michael Rowley Points 11

AllMatch() est une fonction de court-circuitage, vous pouvez donc l'utiliser pour arrêter le traitement. Le principal inconvénient est que vous devez effectuer votre test deux fois : une fois pour voir si vous devez le traiter, et une autre fois pour voir si vous devez continuer.

IntStream
    .iterate(1, n -> n + 1)
    .peek(n->{if (n<10) System.out.println(n);})
    .allMatch(n->n < 10);

13voto

Dominic Fox Points 123

TakeWhile est l'une des fonctions fournies par le module bibliothèque protonpack .

Stream<Integer> infiniteInts = Stream.iterate(0, i -> i + 1);
Stream<Integer> finiteInts = StreamUtils.takeWhile(infiniteInts, i -> i < 10);

assertThat(finiteInts.collect(Collectors.toList()),
           hasSize(10));

9voto

Saint Hill Points 2285

Je suis sûr que cela peut être grandement amélioré : (quelqu'un pourrait peut-être le rendre plus sûr).

Stream<Integer> stream = Stream.iterate(0, n -> n + 1);

TakeWhile.stream(stream, n -> n < 10000)
         .forEach(n -> System.out.print((n == 0 ? "" + n : "," + n)));

Un hack pour sûr... Pas élégant - mais ça marche ~:D

class TakeWhile<T> implements Iterator<T> {

    private final Iterator<T> iterator;
    private final Predicate<T> predicate;
    private volatile T next;
    private volatile boolean keepGoing = true;

    public TakeWhile(Stream<T> s, Predicate<T> p) {
        this.iterator = s.iterator();
        this.predicate = p;
    }

    @Override
    public boolean hasNext() {
        if (!keepGoing) {
            return false;
        }
        if (next != null) {
            return true;
        }
        if (iterator.hasNext()) {
            next = iterator.next();
            keepGoing = predicate.test(next);
            if (!keepGoing) {
                next = null;
            }
        }
        return next != null;
    }

    @Override
    public T next() {
        if (next == null) {
            if (!hasNext()) {
                throw new NoSuchElementException("Sorry. Nothing for you.");
            }
        }
        T temp = next;
        next = null;
        return temp;
    }

    public static <T> Stream<T> stream(Stream<T> s, Predicate<T> p) {
        TakeWhile tw = new TakeWhile(s, p);
        Spliterator split = Spliterators.spliterator(tw, Integer.MAX_VALUE, Spliterator.ORDERED);
        return StreamSupport.stream(split, false);
    }

}

4voto

Voici une version réalisée sur les ints - comme demandé dans la question.

Utilisation :

StreamUtil.takeWhile(IntStream.iterate(1, n -> n + 1), n -> n < 10);

Voici le code pour StreamUtil :

import java.util.PrimitiveIterator;
import java.util.Spliterators;
import java.util.function.IntConsumer;
import java.util.function.IntPredicate;
import java.util.stream.IntStream;
import java.util.stream.StreamSupport;

public class StreamUtil
{
    public static IntStream takeWhile(IntStream stream, IntPredicate predicate)
    {
        return StreamSupport.intStream(new PredicateIntSpliterator(stream, predicate), false);
    }

    private static class PredicateIntSpliterator extends Spliterators.AbstractIntSpliterator
    {
        private final PrimitiveIterator.OfInt iterator;
        private final IntPredicate predicate;

        public PredicateIntSpliterator(IntStream stream, IntPredicate predicate)
        {
            super(Long.MAX_VALUE, IMMUTABLE);
            this.iterator = stream.iterator();
            this.predicate = predicate;
        }

        @Override
        public boolean tryAdvance(IntConsumer action)
        {
            if (iterator.hasNext()) {
                int value = iterator.nextInt();
                if (predicate.test(value)) {
                    action.accept(value);
                    return true;
                }
            }

            return false;
        }
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X