web-dev-qa-db-fra.com

Comment créer un flux infini <E> à partir d'un itérateur <E>?

En regardant le cours suivant que j'ai fait:

public class FibonacciSupplier implements Iterator<Integer> {
    private final IntPredicate hasNextPredicate;

    private int beforePrevious = 0;
    private int previous = 1;

    private FibonacciSupplier(final IntPredicate hasNextPredicate) {
        this.hasNextPredicate = hasNextPredicate;
    }

    @Override
    public boolean hasNext() {
        return hasNextPredicate.test(previous);
    }

    @Override
    public Integer next() {
        int result = beforePrevious + previous;
        beforePrevious = previous;
        previous = result;
        return result;
    }

    public static FibonacciSupplier infinite() {
        return new FibonacciSupplier(i -> true);
    }

    public static FibonacciSupplier finite(final IntPredicate predicate) {
        return new FibonacciSupplier(predicate);
    }
} 

Et l'utilisation de celui-ci dans:

public class Problem2 extends Problem<Integer> {
    @Override
    public void run() {
        result = toList(FibonacciSupplier.finite(i -> (i <= 4_000_000)))
                .stream()
                .filter(i -> (i % 2 == 0))
                .mapToInt(i -> i)
                .sum();
    }

    @Override
    public String getName() {
        return "Problem 2";
    }

    private static <E> List<E> toList(final Iterator<E> iterator) {
        List<E> list = new ArrayList<>();
        while (iterator.hasNext()) {
            list.add(iterator.next());
        }
        return list;
    }
}

Comment pourrais-je créer un infini Stream<E>?

Si je devais utiliser Stream<Integer> infiniteStream = toList(FibonacciSupplier.infinite()).stream(), je n'aurais peut-être même pas un flux infini.
Au lieu de cela, le code serait bouclé à jamais pour la création de la variable list dans une méthode sous-jacente.

Jusqu'ici, c'est purement théorique, mais je peux certainement en comprendre le besoin si je veux d'abord sauter les x premiers nombres d'un flux infini, puis le limiter par les derniers y, quelque chose comme:

int x = MAGIC_NUMBER_X;
int y = MAGIC_NUMBER_y;
int sum = toList(FibonacciSupplier.infinite())
    .stream()
    .skip(x)
    .limit(y)
    .mapToInt(i -> i)
    .sum();

Le code ne renverrait jamais un résultat, comment devrait-il être fait?

21
skiwi

Votre erreur est de penser que vous avez besoin d'une Iterator ou d'une Collection pour créer une Stream. Pour créer un flux infini, une seule méthode fournissant une valeur après l'autre suffit. Donc pour votre classe FibonacciSupplier, l’utilisation la plus simple est:

IntStream s=IntStream.generate(FibonacciSupplier.infinite()::next);

ou, si vous préférez les valeurs encadrées:

Stream<Integer> s=Stream.generate(FibonacciSupplier.infinite()::next);

Notez que dans ce cas, la méthode n'a pas besoin d'être nommée next ni de remplir l'interface Iterator. Mais peu importe que cela soit comme avec votre classe. De plus, comme nous venons d'indiquer au flux d'utiliser la méthode next en tant que Supplier, la méthode hasNext ne sera jamais appelée. C’est juste infini.

Créer un flux fini en utilisant votre Iterator est un peu plus compliqué:

Stream<Integer> s=StreamSupport.stream(
  Spliterators.spliteratorUnknownSize(
    FibonacciSupplier.finite(intPredicate), Spliterator.ORDERED),
  false);

Dans ce cas, si vous voulez une IntStream finie avec des valeurs int sans boîte, votre FibonacciSupplier doit implémenter PrimitiveIterator.OfInt.

21
Holger

En Java 8, il n'y a pas de classes concrètes publiques et concrètes implémentant l'interface Stream . Cependant, il existe quelques méthodes d'usine statiques . L'un des plus importants est StreamSupport.stream . En particulier, il est utilisé dans la méthode default Collection.stream - héritée de la plupart des classes de collection:

default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

L'implémentation par défaut de cette méthode crée un Spliterator en appelant spliterator() et transmet l'objet créé à la méthode factory. Spliterator est une nouvelle interface introduite avec Java 8 pour prendre en charge les flux parallèles . Il est similaire à Iterator , mais contrairement à ce dernier, un Spliterator peut être divisé en plusieurs parties pouvant être traitées indépendamment. Voir Spliterator.trySplit pour plus de détails.

La méthode default Iterable.spliterator a également été ajoutée à Java 8, de sorte que chaque Iterable class prend automatiquement en charge Spliterators . L'implémentation se présente comme suit:

default Spliterator<T> spliterator() {
    return Spliterators.spliteratorUnknownSize(iterator(), 0);
}

La méthode crée le Spliterator à partir d'un arbitraire Iterator . Si vous combinez ces deux étapes, vous pouvez créer un Stream à partir d’un arbitraire Iterator :

<T> Stream<T> stream(Iterator<T> iterator) {
    Spliterator<T> spliterator
        = Spliterators.spliteratorUnknownSize(iterator, 0);
    return StreamSupport.stream(spliterator, false);
}

Pour avoir une impression de Spliterators , voici un exemple très simple sans utilisation de collections. La classe suivante implémente Spliterator pour itérer sur un intervalle d'entiers semi-ouvert:

public final class IntRange implements Spliterator.OfInt {
    private int first, last;
    public IntRange(int first, int last) {
        this.first = first;
        this.last = last;
    }
    public boolean tryAdvance(IntConsumer action) {
        if (first < last) {
            action.accept(first++);
            return true;
        } else {
            return false;
        }
    }
    public OfInt trySplit() {
        int size = last - first;
        if (size >= 10) {
            int temp = first;
            first += size / 2;
            return new IntRange(temp, first);
        } else {
            return null;
        }
    }
    public long estimateSize() {
        return Math.max(last - first, 0);
    }
    public int characteristics() {
        return ORDERED | DISTINCT | SIZED | NONNULL
            | IMMUTABLE | CONCURRENT | SUBSIZED;
    }
}
15
nosid

Vous pouvez utiliser les primitives de prise en charge de flux de bas niveau et la bibliothèque Spliterators pour créer un flux à partir de Iterator.

Le dernier paramètre de StreamSupport.stream() indique que le flux n'est pas parallèle. Assurez-vous de le laisser comme ça parce que votre itérateur de Fibonacci dépend des itérations précédentes.

return StreamSupport.stream( Spliterators.spliteratorUnknownSize( new Iterator<Node>()
{
    @Override
    public boolean hasNext()
    {
        // to implement
        return ...;
    }

    @Override
    public ContentVersion next()
    {
        // to implement
        return ...;
    }
}, Spliterator.ORDERED ), false );
0
Arnaud Tournier

Pour ajouter une autre réponse, AbstractSpliterator est peut-être un meilleur choix, surtout compte tenu de l'exemple de code. Generate est inflexible car il n'y a pas de [bonne] façon de donner une condition d'arrêt sauf en utilisant limit. Limit n'accepte qu'un nombre d'éléments plutôt qu'un prédicat. Nous devons donc savoir combien d'éléments nous voulons générer - ce qui pourrait ne pas être possible et que se passe-t-il si le générateur est une boîte noire?

AbstractSpliterator est une solution de transition entre avoir à écrire un spliterator complet et utiliser Iterator/Iterable. AbstractSpliterator manque juste de la méthode tryAdvance où nous vérifions d’abord notre prédicat pour être fait, et sinon passons la valeur générée à une action. Voici un exemple de séquence de Fibonacci utilisant AbstractIntSpliterator:

public class Fibonacci {
    private static class FibonacciGenerator extends Spliterators.AbstractIntSpliterator
    {
        private IntPredicate hasNextPredicate;
        private int beforePrevious = 0;
        private int previous = 0;

        protected FibonacciGenerator(IntPredicate hasNextPredicate)
        {
            super(Long.MAX_VALUE, 0);
            this.hasNextPredicate = hasNextPredicate;
        }

        @Override
        public boolean tryAdvance(IntConsumer action)
        {
            if (action == null)
            {
                throw new NullPointerException();
            }

            int next = Math.max(1, beforePrevious + previous);
            beforePrevious = previous;
            previous = next;

            if (!hasNextPredicate.test(next))
            {
                return false;
            }

            action.accept(next);

            return true;
        }

        @Override
        public boolean tryAdvance(Consumer<? super Integer> action)
        {
            if (action == null)
            {
                throw new NullPointerException();
            }

            int next = Math.max(1, beforePrevious + previous);
            beforePrevious = previous;
            previous = next;

            if (!hasNextPredicate.test(next))
            {
                return false;
            }

            action.accept(next);

            return true;
        }
    }

    public static void main(String args[])
    {
        Stream<Integer> infiniteStream = StreamSupport.stream(
                new FibonacciGenerator(i -> true), false);

        Stream<Integer> finiteStream = StreamSupport.stream(
                new FibonacciGenerator(i -> i < 100), false);

        // Print with a side-effect for the demo
        infiniteStream.limit(10).forEach(System.out::println);
        finiteStream.forEach(System.out::println);
    }
} 

Pour plus de détails, j'ai couvert les générateurs Java 8 dans mon blog http://thecannycoder.wordpress.com/

0
TheCannyCoder