web-dev-qa-db-fra.com

Limiter un flux par un prédicat

Existe-t-il une opération de flux Java 8 qui limite un Stream (potentiellement infini) jusqu'à ce que le premier élément ne corresponde pas avec un prédicat?

Dans Java 9, nous pouvons utiliser takeWhile comme dans l'exemple ci-dessous pour imprimer tous les nombres inférieurs à 10.

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

Comme il n’existe pas cette opération dans Java 8, quelle est la meilleure façon de la mettre en œuvre de manière générale?

173
MForster

Une telle opération devrait être possible avec un Java 8 Stream, mais cela ne peut pas nécessairement être effectué efficacement - par exemple, vous ne pouvez pas nécessairement paralléliser une telle opération, car il faut regarder les éléments dans l’ordre.

L’API n’offre pas un moyen facile de le faire, mais le moyen le plus simple est probablement de prendre Stream.iterator(), d’envelopper la Iterator pour avoir une implémentation "take-while", puis de revenir à un Spliterator puis un Stream. Ou - peut-être - encapsulez la Spliterator, bien qu'elle ne puisse plus être scindée 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;
    }
  };
}

static <T> Stream<T> takeWhile(Stream<T> stream, Predicate<? super T> predicate) {
   return StreamSupport.stream(takeWhile(stream.spliterator(), predicate), false);
}
79
Louis Wasserman

Les opérations takeWhile et dropWhile ont été ajoutées à JDK 9. Votre exemple de code

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

se comportera exactement comme vous le souhaiteriez lorsqu'il sera compilé et exécuté sous JDK 9.

JDK 9 a été libéré. Il est disponible au téléchargement ici: http://jdk.Java.net/9/

142
Stuart Marks

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

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

En guise de suivi de @ StuartMarks answer . Ma bibliothèque StreamEx possède l'opération takeWhile compatible avec la mise en œuvre actuelle de JDK-9. Sous JDK-9, il ne fera que déléguer à l'implémentation JDK (via MethodHandle.invokeExact, ce qui est très rapide). Lors de l'exécution sous JDK-8, l'implémentation "polyfill" sera utilisée. Donc, en utilisant ma bibliothèque, le problème peut être résolu comme ceci:

IntStreamEx.iterate(1, n -> n + 1)
           .takeWhile(n -> n < 10)
           .forEach(System.out::println);
33
Tagir Valeev

takeWhile est l'une des fonctions fournies par bibliothèque de 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));
13
Dominic Fox

Vous pouvez utiliser Java8 + rxjava .

import Java.util.stream.IntStream;
import rx.Observable;


// Example 1)
IntStream intStream  = IntStream.iterate(1, n -> n + 1);
Observable.from(() -> intStream.iterator())
    .takeWhile(n ->
          {
                System.out.println(n);
                return n < 10;
          }
    ).subscribe() ;


// Example 2
IntStream intStream  = IntStream.iterate(1, n -> n + 1);
Observable.from(() -> intStream.iterator())
    .takeWhile(n -> n < 10)
    .forEach( n -> System.out.println(n));
8
frhack

Mise à jour: Java 9 Stream vient maintenant avec une méthode takeWhile .

Pas besoin de piratage ou d'autres solutions. Il suffit d'utiliser ça!


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

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 bidouillage à coup 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);
    }

}
8
The Coordinator

En fait, il y a 2 façons de le faire dans Java 8 sans aucune bibliothèque supplémentaire ou en utilisant Java 9.

Si vous souhaitez imprimer des nombres de 2 à 20 sur la console, vous pouvez procéder comme suit:

IntStream.iterate(2, (i) -> i + 2).peek(System.out::println).allMatch(i -> i < 20);

ou

IntStream.iterate(2, (i) -> i + 2).peek(System.out::println).anyMatch(i -> i >= 20);

La sortie est dans les deux cas:

2
4
6
8
10
12
14
16
18
20

Personne n'a encore mentionné anyMatch. C'est la raison de ce post.

5
gil.fernandes

Il s'agit de la source copiée à partir du JDK 9 Java.util.stream.Stream.takeWhile (Predicate). Une petite différence pour pouvoir travailler avec JDK 8.

static <T> Stream<T> takeWhile(Stream<T> stream, Predicate<? super T> p) {
    class Taking extends Spliterators.AbstractSpliterator<T> implements Consumer<T> {
        private static final int CANCEL_CHECK_COUNT = 63;
        private final Spliterator<T> s;
        private int count;
        private T t;
        private final AtomicBoolean cancel = new AtomicBoolean();
        private boolean takeOrDrop = true;

        Taking(Spliterator<T> s) {
            super(s.estimateSize(), s.characteristics() & ~(Spliterator.SIZED | Spliterator.SUBSIZED));
            this.s = s;
        }

        @Override
        public boolean tryAdvance(Consumer<? super T> action) {
            boolean test = true;
            if (takeOrDrop &&               // If can take
                    (count != 0 || !cancel.get()) && // and if not cancelled
                    s.tryAdvance(this) &&   // and if advanced one element
                    (test = p.test(t))) {   // and test on element passes
                action.accept(t);           // then accept element
                return true;
            } else {
                // Taking is finished
                takeOrDrop = false;
                // Cancel all further traversal and splitting operations
                // only if test of element failed (short-circuited)
                if (!test)
                    cancel.set(true);
                return false;
            }
        }

        @Override
        public Comparator<? super T> getComparator() {
            return s.getComparator();
        }

        @Override
        public void accept(T t) {
            count = (count + 1) & CANCEL_CHECK_COUNT;
            this.t = t;
        }

        @Override
        public Spliterator<T> trySplit() {
            return null;
        }
    }
    return StreamSupport.stream(new Taking(stream.spliterator()), stream.isParallel()).onClose(stream::close);
}
5
martian

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

Usage:

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;
        }
    }
}
4
Chris Greenaway

Allez chercher la bibliothèque AbacusUtil . Il fournit l’API exacte que vous voulez et plus encore:

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

Déclaration : Je suis le développeur de AbacusUtil.

1
user_3380739

Si vous connaissez le nombre exact de répétitions qui seront effectuées, vous pouvez faire

IntStream
          .iterate(1, n -> n + 1)
          .limit(10)
          .forEach(System.out::println);
1
Dilip Tharoor

Même si j'avais une exigence similaire - invoquer le service Web. S'il échoue, réessayez-le 3 fois. Si cela échoue même après ces nombreux essais, envoyez une notification par courrier électronique. Après avoir beaucoup cherché sur Google, anyMatch() est venu comme un sauveur. Mon exemple de code comme suit. Dans l'exemple suivant, si la méthode webServiceCall renvoie true à la première itération, stream ne se répète pas car nous avons appelé anyMatch(). Je crois que c'est ce que vous recherchez.

import Java.util.stream.IntStream;

import io.netty.util.internal.ThreadLocalRandom;

class TrialStreamMatch {

public static void main(String[] args) {        
    if(!IntStream.range(1,3).anyMatch(integ -> webServiceCall(integ))){
         //Code for sending email notifications
    }
}

public static boolean webServiceCall(int i){
    //For time being, I have written a code for generating boolean randomly
    //This whole piece needs to be replaced by actual web-service client code
    boolean bool = ThreadLocalRandom.current().nextBoolean();
    System.out.println("Iteration index :: "+i+" bool :: "+bool);

    //Return success status -- true or false
    return bool;
}
0
Chinmay Phadke
    IntStream.iterate(1, n -> n + 1)
    .peek(System.out::println) //it will be executed 9 times
    .filter(n->n>=9)
    .findAny();

au lieu de pic, vous pouvez utiliser mapToObj pour renvoyer l'objet ou le message final

    IntStream.iterate(1, n -> n + 1)
    .mapToObj(n->{   //it will be executed 9 times
            if(n<9)
                return "";
            return "Loop repeats " + n + " times";});
    .filter(message->!message.isEmpty())
    .findAny()
    .ifPresent(System.out::println);
0
Oleksandr Potomkin

Vous ne pouvez pas abandonner un flux sauf par un court-circuitage du terminal, ce qui laisserait certaines valeurs de flux non traitées, quelle que soit leur valeur. Mais si vous voulez simplement éviter les opérations sur un flux, vous pouvez ajouter une transformation et un filtre au flux:

import Java.util.Objects;

class ThingProcessor
{
    static Thing returnNullOnCondition(Thing thing)
    {    return( (*** is condition met ***)? null : thing);    }

    void processThings(Collection<Thing> thingsCollection)
    {
        thingsCollection.stream()
        *** regular stream processing ***
        .map(ThingProcessor::returnNullOnCondition)
        .filter(Objects::nonNull)
        *** continue stream processing ***
    }
} // class ThingProcessor

Cela transforme le flux d'éléments en éléments NULL lorsque les éléments remplissent certaines conditions, puis élimine les éléments NULL. Si vous êtes prêt à vous laisser aller à des effets secondaires, vous pouvez définir la valeur de la condition sur true dès que quelque chose est rencontré. Ainsi, tous les éléments suivants sont filtrés, quelle que soit leur valeur. Mais même si ce n'est pas le cas, vous pouvez économiser beaucoup (sinon la totalité) du traitement en filtrant les valeurs du flux que vous ne souhaitez pas traiter.

0
Matthew