Existe-t-il un moyen concis d'itérer sur un flux avec des index en Java 8?


Existe-t-il un moyen concis d'itérer sur un flux tout en ayant accès à l'index dans le flux?

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'exemple LINQ qui y est donné

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

Existe - t-il un moyen plus concis?

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

Author: Jordi Castilla, 2013-08-31

17 answers

Le moyen le plus propre consiste à 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 résultante ne contient que "Erik".


Une alternative qui semble plus familière lorsque vous êtes habitué aux boucles for serait de maintenir un compteur ad hoc en utilisant un objet mutable, 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'utilisation de cette dernière méthode sur un flux parallèle pourrait casser car les éléments ne seraient pas nécessairement traités "dans l'ordre".

 330
Author: assylias, 2014-05-20 09:39:49

L'API Java 8 streams n'a pas les fonctionnalités permettant d'obtenir l'index d'un élément stream, ni la possibilité de compresser des flux ensemble. C'est regrettable, car cela rend certaines applications (comme les défis LINQ) plus difficiles qu'elles ne le seraient autrement.

Il y a souvent des solutions de contournement, cependant. Habituellement, cela peut être fait en "conduisant" le flux avec une plage entière, et en profitant du fait que les éléments d'origine sont souvent dans un tableau ou dans une collection accessible par l'index. Par exemple, le problème Challenge 2 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 profite du fait que la source de données (le tableau de noms) est directement indexable. Si ce n'était pas le cas, cette technique ne fonctionnerait pas.

J'admets que cela ne satisfait pas l'intention du défi 2. Néanmoins, il résout le problème raisonnablement efficacement.

MODIFIER

Mon exemple de code précédent a utilisé flatMap pour fusionner le filtre et la carte opérations, mais cela était lourd et ne fournissait aucun avantage. J'ai mis à jour l'exemple par le commentaire de Holger.

 59
Author: Stuart Marks, 2016-08-17 21:37:23

Depuis la goyave 21, vous pouvez utiliser

Streams.mapWithIndex()

Exemple (à partir de document officiel):

Streams.mapWithIndex(
    Stream.of("a", "b", "c"),
    (str, index) -> str + ":" + index)
) // will return Stream.of("a:0", "b:1", "c:2")
 24
Author: numéro6, 2017-03-08 14:22:54

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));
    }
}
 22
Author: user1195526, 2014-04-14 02:58:00

En plus de protonpack, jOO's Seqfournit cette fonctionnalité (et par extension les bibliothèques qui s'appuient dessus comme cyclops-react, je suis l'auteur de cette bibliothèque).

Seq.seq(Stream.of(names)).zipWithIndex()
                         .filter( namesWithIndex -> namesWithIndex.v1.length() <= namesWithIndex.v2 + 1)
                         .toList();

Seq prend également en charge uniquement Seq.de (noms) et va construire un flux JDK sous les couvertures.

L'équivalent simple-react ressemblerait de la même manière à

 LazyFutureStream.of(names)
                 .zipWithIndex()
                 .filter( namesWithIndex -> namesWithIndex.v1.length() <= namesWithIndex.v2 + 1)
                 .toList();

La version simple-react est plus adaptée pour le traitement asynchrone / simultané.

 13
Author: John McClean, 2017-06-08 23:30:47

Juste pour être complet, voici la solution impliquant ma bibliothèque StreamEx :

String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
EntryStream.of(names)
    .filterKeyValue((idx, str) -> str.length() <= idx+1)
    .values().toList();

Ici, nous créons une EntryStream<Integer, String> qui s'étend Stream<Entry<Integer, String>> et ajoute certaines opérations spécifiques comme filterKeyValue ou values. Aussi toList() raccourci est utilisé.

 12
Author: Tagir Valeev, 2015-09-10 02:07:51

Il n'y a pas de moyen d'itérer sur un Stream tout en ayant accès à l'index car un Stream ne ressemble à aucun Collection. Un Stream est simplement un pipeline pour transporter des données d'un endroit à un autre, comme indiqué dans le documentation:

Pas de stockage. Un flux n'est pas une structure de données qui stocke des éléments; au lieu de cela, ils transportent des valeurs d'une source (qui pourrait être une structure de données, un générateur, un canal d'E / S, etc.) via un pipeline d'opérations de calcul.

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

 4
Author: Josh M, 2013-08-31 19:37:26

Avec https://github.com/poetix/protonpack u peut faire ce zip:

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

List<String> nameList;
Stream<Integer> indices = IntStream.range(0, names.length).boxed(); 

nameList = StreamUtils.zip(indices, stream(names),SimpleEntry::new)
        .filter(e -> e.getValue().length() <= e.getKey()).map(Entry::getValue).collect(toList());                   

System.out.println(nameList);
 3
Author: 42n4, 2015-01-16 17:33:36

Si vous n'avez pas l'esprit à l'aide d'une bibliothèque tierce, Eclipse Collections a zipWithIndex et forEachWithIndex disponible pour une utilisation dans de nombreux types. Voici un ensemble de solutions à ce défi pour les types JDK et les types de collections Eclipse utilisant zipWithIndex.

String[] names = { "Sam", "Pamela", "Dave", "Pascal", "Erik" };
ImmutableList<String> expected = Lists.immutable.with("Erik");
Predicate<Pair<String, Integer>> predicate =
    pair -> pair.getOne().length() <= pair.getTwo() + 1;

// JDK Types
List<String> strings1 = ArrayIterate.zipWithIndex(names)
    .collectIf(predicate, Pair::getOne);
Assert.assertEquals(expected, strings1);

List<String> list = Arrays.asList(names);
List<String> strings2 = ListAdapter.adapt(list)
    .zipWithIndex()
    .collectIf(predicate, Pair::getOne);
Assert.assertEquals(expected, strings2);

// Eclipse Collections types
MutableList<String> mutableNames = Lists.mutable.with(names);
MutableList<String> strings3 = mutableNames.zipWithIndex()
    .collectIf(predicate, Pair::getOne);
Assert.assertEquals(expected, strings3);

ImmutableList<String> immutableNames = Lists.immutable.with(names);
ImmutableList<String> strings4 = immutableNames.zipWithIndex()
    .collectIf(predicate, Pair::getOne);
Assert.assertEquals(expected, strings4);

MutableList<String> strings5 = mutableNames.asLazy()
    .zipWithIndex()
    .collectIf(predicate, Pair::getOne, Lists.mutable.empty());
Assert.assertEquals(expected, strings5);

Voici une solution utilisant forEachWithIndex à la place.

MutableList<String> mutableNames =
    Lists.mutable.with("Sam", "Pamela", "Dave", "Pascal", "Erik");
ImmutableList<String> expected = Lists.immutable.with("Erik");

List<String> actual = Lists.mutable.empty();
mutableNames.forEachWithIndex((name, index) -> {
        if (name.length() <= index + 1)
            actual.add(name);
    });
Assert.assertEquals(expected, actual);

Si vous changez les lambdas en classes internes anonymes ci-dessus, tous ces exemples de code fonctionneront dans Java 5 - 7 comme Bien.

Remarque: Je suis un committer pour les collections Eclipse

 3
Author: Donald Raab, 2016-02-28 06:53:12

Avec une liste, vous pouvez essayer

List<String> strings = new ArrayList<>(Arrays.asList("First", "Second", "Third", "Fourth", "Fifth")); // An example list of Strings
strings.stream() // Turn the list into a Stream
    .collect(HashMap::new, (h, o) -> h.put(h.size(), o), (h, o) -> {}) // Create a map of the index to the object
        .forEach((i, o) -> { // Now we can use a BiConsumer forEach!
            System.out.println(String.format("%d => %s", i, o));
        });

Sortie:

0 => First
1 => Second
2 => Third
3 => Fourth
4 => Fifth
 3
Author: V0idst4r, 2017-06-20 19:33:31

J'ai trouvé les solutions ici, lorsque le Flux est créé de liste ou de tableau (et vous connaissez la taille). Mais si ce Flux est avec taille inconnue? Dans ce cas, essayez cette variante:

public class WithIndex<T> {
    private int index;
    private T value;

    WithIndex(int index, T value) {
        this.index = index;
        this.value = value;
    }

    public int index() {
        return index;
    }

    public T value() {
        return value;
    }

    @Override
    public String toString() {
        return value + "(" + index + ")";
    }

    public static <T> Function<T, WithIndex<T>> indexed() {
        return new Function<T, WithIndex<T>>() {
            int index = 0;
            @Override
            public WithIndex<T> apply(T t) {
                return new WithIndex<>(index++, t);
            }
        };
    }
}

Utilisation:

public static void main(String[] args) {
    Stream<String> stream = Stream.of("a", "b", "c", "d", "e");
    stream.map(WithIndex.indexed()).forEachOrdered(e -> {
        System.out.println(e.index() + " -> " + e.value());
    });
}
 3
Author: alex.b, 2017-11-08 00:00:00

Voici le code par AbacusUtil

Stream.of(names).indexed()
      .filter(e -> e.value().length() <= e.index())
      .map(Indexed::value).toList();

Divulgation: Je suis le développeur de AbacusUtil.

 1
Author: user_3380739, 2017-06-07 00:05:14

Si vous utilisez Vavr (anciennement Javaslang), vous pouvez utiliser la méthode dédiée:

Stream.of("A", "B", "C")
  .zipWithIndex();

Si nous imprimons le contenu, nous verrons quelque chose d'intéressant:

Stream((A, 0), ?)

C'est parce que Streams sont paresseux et nous n'avons aucune idée des éléments suivants dans le flux.

 1
Author: Grzegorz Piwowarek, 2017-07-25 17:23:56

Vous pouvez créer une classe interne statique pour encapsuler l'indexeur comme je devais le faire dans l'exemple ci-dessous:

static class Indexer {
    int i = 0;
}

public static String getRegex() {
    EnumSet<MeasureUnit> range = EnumSet.allOf(MeasureUnit.class);
    StringBuilder sb = new StringBuilder();
    Indexer indexer = new Indexer();
    range.stream().forEach(
            measureUnit -> {
                sb.append(measureUnit.acronym);
                if (indexer.i < range.size() - 1)
                    sb.append("|");

                indexer.i++;
            }
    );
    return sb.toString();
}
 0
Author: alexpfx, 2016-02-14 02:14:03

Cette question ( Stream Way pour obtenir l'index du premier élément correspondant booléen) a marqué la question actuelle comme un doublon, donc je ne peux pas y répondre là; je réponds ici.

Voici une solution générique pour obtenir l'index correspondant qui ne nécessite pas de bibliothèque externe.

Si vous avez une liste.

public static <T> int indexOf(List<T> items, Predicate<T> matches) {
        return IntStream.range(0, items.size())
                .filter(index -> matches.test(items.get(index)))
                .findFirst().orElse(-1);
}

Et appelez-le comme ceci:

int index = indexOf(myList, item->item.getId()==100);

Et si vous utilisez une collection, essayez celle-ci.

   public static <T> int indexOf(Collection<T> items, Predicate<T> matches) {
        int index = -1;
        Iterator<T> it = items.iterator();
        while (it.hasNext()) {
            index++;
            if (matches.test(it.next())) {
                return index;
            }
        }
        return -1;
    }
 0
Author: Steven Spungin, 2017-05-23 11:54:59

Si vous essayez d'obtenir un indice basé sur un prédicat, essayez ceci:

Si vous ne vous souciez que du premier index:

OptionalInt index = IntStream.range(0, list.size())
    .filter(i -> list.get(i) == 3)
    .findFirst();

Ou si vous voulez trouver plusieurs index:

IntStream.range(0, list.size())
   .filter(i -> list.get(i) == 3)
   .collect(Collectors.toList());

Ajoutez .orElse(-1); si vous souhaitez renvoyer une valeur si elle ne la trouve pas.

 0
Author: live-love, 2018-04-05 17:10:09

Une façon possible est d'indexer chaque élément sur le flux:

AtomicInteger index = new AtomicInteger();
Stream.of(names)
  .map(e->new Object() { String n=e; public i=index.getAndIncrement(); })
  .filter(o->o.n.length()<=o.i) // or do whatever you want with pairs...
  .forEach(o->System.out.println("idx:"+o.i+" nam:"+o.n));

L'utilisation d'une classe anonyme le long d'un flux n'est pas bien utilisée tout en étant très utile.

 -1
Author: Jean-Baptiste Yunès, 2017-08-31 21:22:03