543 votes

Est-il une manière concise pour effectuer une itération sur un ruisseau avec des indices en Java 8?

Est-il une manière concise pour effectuer une itération sur un flux de données tout en ayant accès à l'index dans le ruisseau?

Le meilleur que j'ai rencontré jusqu'à présent (défi 2) est

String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};

List<String> nameList;
Stream<Integer> indices = intRange(1, names.length).boxed();
nameList = zip(indices, stream(names), SimpleEntry::new)
        .filter(e -> e.getValue().length() <= e.getKey())
        .map(Entry::getValue)
        .collect(toList());

ce qui semble plutôt décevant par rapport à l'LINQ exemple donné, il

string[] names = { "Sam", "Pamela", "Dave", "Pascal", "Erik" };
var nameList = names.Where((c, index) => c.Length <= index + 1).ToList();

Est-il une manière plus concise?

De plus, il semble que le zip a été déplacée ou supprimée...

598voto

assylias Points 102015

La façon la plus propre est de commencer à partir d'un flux d'indices:

String[] names = {"Sam", "Pamela", "Dave", "Pascal", "Erik"};
IntStream.range(0, names.length)
         .filter(i -> names[i].length() <= i)
         .mapToObj(i -> names[i])
         .collect(Collectors.toList());

La liste contient "Erik".


Une solution qui semble plus familier lorsque vous êtes utilisé pour les boucles consisterait à maintenir ad hoc de compteur à l'aide d'une mutable objet, par exemple un AtomicInteger:

String[] names = {"Sam", "Pamela", "Dave", "Pascal", "Erik"};
AtomicInteger index = new AtomicInteger();
List<String> list = Arrays.stream(names)
                          .filter(n -> n.length() <= index.incrementAndGet())
                          .collect(Collectors.toList());

Notez que l'aide de la méthode de ce dernier sur un courant parallèle pourrait se briser que les éléments ne seraient pas forcément être traité "dans l'ordre".

103voto

Stuart Marks Points 8927

Java 8 API de flux manque les caractéristiques de l'obtention de l'indice d'un flux de l'élément ainsi que la capacité à zip ruisseaux ensemble. C'est malheureux, car il rend certaines applications (comme le LINQ défis) plus difficile que ce qu'ils seraient autrement.

Il y a souvent des solutions de contournement, cependant. Habituellement, cela peut être fait par la "conduite" le flux avec un intervalle entier, et en tirant parti du fait que les éléments d'origine sont souvent dans un tableau ou dans une collection accessible par l'index. Par exemple, le Défi 2 problème peut être résolu de cette façon:

String[] names = {"Sam", "Pamela", "Dave", "Pascal", "Erik"};

List<String> nameList =
    IntStream.range(0, names.length)
        .filter(i -> (names[i].length() <= i)
        .mapToObj(i -> names[i])
        .collect(toList());

Comme je l'ai mentionné ci-dessus, cela prend avantage du fait que la source de données (les noms array) est directement à plaquettes indexables. Si ce n'était pas le cas, cette technique ne fonctionne pas.

Je vais vous avouer que ce n'est pas satisfaire à l'intention de Challenge 2. Néanmoins, il n'résoudre le problème raisonnablement efficace.

MODIFIER

Mon exemple de code précédent utilisé flatMap à la fusion du filtre et de la carte des opérations, mais il était lourd et a fourni aucun avantage. J'ai mis à jour l'exemple par l'observation de Holger.

29voto

user1195526 Points 79

J'ai utilisé la solution suivante dans mon projet. Je pense que c'est mieux que d'utiliser des objets mutables ou entier plages.

import java.util.*;
import java.util.function.*;
import java.util.stream.Collector;
import java.util.stream.Collector.Characteristics;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static java.util.Objects.requireNonNull;


public class CollectionUtils {
    private CollectionUtils() { }

    /**
     * Converts an {@link java.util.Iterator} to {@link java.util.stream.Stream}.
     */
    public static <T> Stream<T> iterate(Iterator<? extends T> iterator) {
        int characteristics = Spliterator.ORDERED | Spliterator.IMMUTABLE;
        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, characteristics), false);
    }

    /**
     * Zips the specified stream with its indices.
     */
    public static <T> Stream<Map.Entry<Integer, T>> zipWithIndex(Stream<? extends T> stream) {
        return iterate(new Iterator<Map.Entry<Integer, T>>() {
            private final Iterator<? extends T> streamIterator = stream.iterator();
            private int index = 0;

            @Override
            public boolean hasNext() {
                return streamIterator.hasNext();
            }

            @Override
            public Map.Entry<Integer, T> next() {
                return new AbstractMap.SimpleImmutableEntry<>(index++, streamIterator.next());
            }
        });
    }

    /**
     * Returns a stream consisting of the results of applying the given two-arguments function to the elements of this stream.
     * The first argument of the function is the element index and the second one - the element value. 
     */
    public static <T, R> Stream<R> mapWithIndex(Stream<? extends T> stream, BiFunction<Integer, ? super T, ? extends R> mapper) {
        return zipWithIndex(stream).map(entry -> mapper.apply(entry.getKey(), entry.getValue()));
    }

    public static void main(String[] args) {
        String[] names = {"Sam", "Pamela", "Dave", "Pascal", "Erik"};

        System.out.println("Test zipWithIndex");
        zipWithIndex(Arrays.stream(names)).forEach(entry -> System.out.println(entry));

        System.out.println();
        System.out.println("Test mapWithIndex");
        mapWithIndex(Arrays.stream(names), (Integer index, String name) -> index+"="+name).forEach((String s) -> System.out.println(s));
    }
}

2voto

Josh M Points 4212

Il n'y a pas un moyen pour effectuer une itération sur un Stream tout en ayant accès à l'index parce qu'un Stream est contrairement à n'importe quel Collection. Un Stream est simplement un pipeline pour transporter des données d'un endroit à un autre, comme indiqué dans la documentation:

Pas de stockage. Un flux n'est pas une structure de données qui stocke les éléments; au lieu de cela, ils portent des valeurs à partir d'une source (qui peut être une structure de données, d'un générateur, d'un ar canal, etc) à travers un pipeline de calcul des opérations.

Bien sûr, comme vous semblez l'indiquer dans votre question, vous pouvez toujours convertir votre Stream<V> d'un Collection<V>, comme un List<V>, dans lequel vous aurez accès à l'index.

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