Existe-t-il un moyen concis d’itérer sur un stream avec des index dans Java 8?

Existe-t-il un moyen concis d’itérer sur un stream tout en ayant access à l’index du stream?

Ssortingng[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"}; List nameList; Stream 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 donné là

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

Y a-t-il une manière plus concise?

En outre, il semble que le zip soit déplacé ou soit retiré …

Le moyen le plus propre est de partir d’un stream d’indices:

 Ssortingng[] 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 contient “Erik” uniquement.


Une alternative qui semble plus familière lorsque vous êtes habitué aux boucles serait de gérer un compteur ad hoc à l’aide d’un object mutable, par exemple un AtomicInteger :

 Ssortingng[] names = {"Sam", "Pamela", "Dave", "Pascal", "Erik"}; AtomicInteger index = new AtomicInteger(); List 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 stream parallèle pourrait être interrompue car les éléments ne seraient pas nécessairement traités "dans l'ordre" .

L’API de stream Java 8 ne dispose pas des fonctionnalités permettant d’obtenir l’index d’un élément de stream ainsi que la possibilité de compresser les stream entre eux. C’est regrettable, car cela rend certaines applications (comme les défis LINQ) plus difficiles qu’elles ne le seraient autrement.

Il existe souvent des solutions de contournement. Généralement, cela peut être fait en “pilotant” le stream avec une plage entière, et en tirant parti du fait que les éléments d’origine sont souvent dans un tableau ou dans une collection accessible par index. Par exemple, le problème du Challenge 2 peut être résolu de cette manière:

 Ssortingng[] names = {"Sam", "Pamela", "Dave", "Pascal", "Erik"}; List 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 tire parti 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 répond pas à l’intention du défi 2. Néanmoins, cela résout le problème de manière raisonnablement efficace.

MODIFIER

Mon exemple de code précédent utilisait flatMap pour fusionner les opérations de filtrage et de cartographie, mais cela était lourd et ne présentait aucun avantage. J’ai mis à jour l’exemple selon le commentaire de Holger.

Depuis la goyave 21, vous pouvez utiliser

 Streams.mapWithIndex() 

Exemple (extrait du document officiel ):

 Streams.mapWithIndex( Stream.of("a", "b", "c"), (str, index) -> str + ":" + index) ) // will return Stream.of("a:0", "b:1", "c:2") 

J’ai utilisé la solution suivante dans mon projet. Je pense que c’est mieux que d’utiliser des objects mutables ou des plages entières.

 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  Stream iterate(Iterator iterator) { int characteristics = Spliterator.ORDERED | Spliterator.IMMUTABLE; return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, characteristics), false); } /** * Zips the specified stream with its indices. */ public static  Stream> zipWithIndex(Stream stream) { return iterate(new Iterator>() { private final Iterator streamIterator = stream.iterator(); private int index = 0; @Override public boolean hasNext() { return streamIterator.hasNext(); } @Override public Map.Entry 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  Stream mapWithIndex(Stream stream, BiFunction mapper) { return zipWithIndex(stream).map(entry -> mapper.apply(entry.getKey(), entry.getValue())); } public static void main(Ssortingng[] args) { Ssortingng[] 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, Ssortingng name) -> index+"="+name).forEach((Ssortingng s) -> System.out.println(s)); } } 

En plus de protonpack, la Seq de jOOλ fournit cette fonctionnalité (et par les bibliothèques d’extension qui l’ utilisent 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 ne supporte que Seq.of (noms) et va construire un Stream JDK sous les couvertures.

L'équivalent à réaction simple ressemblerait de la même manière

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

La version à réaction simple est plus adaptée au traitement asynchrone / simultané.

Pour être plus complet, voici la solution impliquant ma bibliothèque StreamEx :

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

Nous créons ici un object EntryStream qui étend Stream> et ajoute des opérations spécifiques comme filterKeyValue ou des values . Le raccourci toList() est également utilisé.

Il n’y a pas moyen d’itérer sur un Stream tout en ayant access à l’index, car un Stream ne ressemble à aucune autre 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 stream n’est pas une structure de données qui stocke des éléments. au lieu de cela, ils transportent des valeurs provenant d’une source (qui pourrait être une structure de données, un générateur, un canal IO, etc.) via un pipeline d’opérations de calcul.

Bien entendu, comme vous semblez l’indiquer dans votre question, vous pouvez toujours convertir votre Stream en Collection , telle que List , dans laquelle vous aurez access aux index.

Avec https://github.com/poetix/protonpack, vous pouvez faire ce zip:

 Ssortingng[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"}; List nameList; Stream 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); 

Si cela ne vous dérange pas d’utiliser une bibliothèque tierce, Eclipse Collections a zipWithIndex et forEachWithIndex disponibles pour plusieurs types. Voici un ensemble de solutions à ce problème pour les types JDK et les types de collections Eclipse utilisant zipWithIndex .

 Ssortingng[] names = { "Sam", "Pamela", "Dave", "Pascal", "Erik" }; ImmutableList expected = Lists.immutable.with("Erik"); Predicate> predicate = pair -> pair.getOne().length() <= pair.getTwo() + 1; // JDK Types List ssortingngs1 = ArrayIterate.zipWithIndex(names) .collectIf(predicate, Pair::getOne); Assert.assertEquals(expected, ssortingngs1); List list = Arrays.asList(names); List ssortingngs2 = ListAdapter.adapt(list) .zipWithIndex() .collectIf(predicate, Pair::getOne); Assert.assertEquals(expected, ssortingngs2); // Eclipse Collections types MutableList mutableNames = Lists.mutable.with(names); MutableList ssortingngs3 = mutableNames.zipWithIndex() .collectIf(predicate, Pair::getOne); Assert.assertEquals(expected, ssortingngs3); ImmutableList immutableNames = Lists.immutable.with(names); ImmutableList ssortingngs4 = immutableNames.zipWithIndex() .collectIf(predicate, Pair::getOne); Assert.assertEquals(expected, ssortingngs4); MutableList ssortingngs5 = mutableNames.asLazy() .zipWithIndex() .collectIf(predicate, Pair::getOne, Lists.mutable.empty()); Assert.assertEquals(expected, ssortingngs5); 

Voici une solution utilisant forEachWithIndex place.

 MutableList mutableNames = Lists.mutable.with("Sam", "Pamela", "Dave", "Pascal", "Erik"); ImmutableList expected = Lists.immutable.with("Erik"); List 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 également dans Java 5 - 7.

Note: je suis committer pour les collections Eclipse

Avec une liste, vous pouvez essayer

 List ssortingngs = 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(Ssortingng.format("%d => %s", i, o)); }); 

Sortie:

 0 => First 1 => Second 2 => Third 3 => Fourth 4 => Fifth 

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

 public class WithIndex { 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 Ssortingng toSsortingng() { return value + "(" + index + ")"; } public static  Function> indexed() { return new Function>() { int index = 0; @Override public WithIndex apply(T t) { return new WithIndex<>(index++, t); } }; } } 

Usage:

 public static void main(Ssortingng[] args) { Stream stream = Stream.of("a", "b", "c", "d", "e"); stream.map(WithIndex.indexed()).forEachOrdered(e -> { System.out.println(e.index() + " -> " + e.value()); }); } 

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 d'AbacusUtil.

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 les Streams sont paresseux et que nous n’avons aucune idée des éléments suivants dans le stream.

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 Ssortingng getRegex() { EnumSet range = EnumSet.allOf(MeasureUnit.class); SsortingngBuilder sb = new SsortingngBuilder(); 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(); } 

Cette question ( Stream Way pour obtenir l’index du premier élément correspondant au booléen ) a marqué la question actuelle comme un doublon, je ne peux donc pas y répondre; 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  int indexOf(List items, Predicate matches) { return IntStream.range(0, items.size()) .filter(index -> matches.test(items.get(index))) .findFirst().orElse(-1); } 

Et appelez comme ceci:

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

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

  public static  int indexOf(Collection items, Predicate matches) { int index = -1; Iterator it = items.iterator(); while (it.hasNext()) { index++; if (matches.test(it.next())) { return index; } } return -1; } 

Une manière possible est d’indexer chaque élément sur le stream:

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

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

Si vous essayez d’obtenir un index 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()); 

Ajouter .orElse(-1); Si vous souhaitez retourner une valeur si elle ne le trouve pas.