Y a-t-il une façon concise d'itérer sur un flux avec des indices en Java 8?

y a-t-il une façon concise d'itérer sur un flux tout en ayant accès à l'index du 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 de LINQ donné ici

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

y a-t-il un moyen plus concis?

de plus, il semble que la fermeture éclair ait été déplacée ou enlevée...

289
demandé sur Jordi Castilla 2013-08-31 23:31:09

17 réponses

la manière la plus propre est de 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 contient" Erik " seulement.


une alternative qui semble plus familière lorsque vous êtes habitué à des boucles 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());

noter que utilisant cette dernière méthode sur un flux parallèle .

330
répondu assylias 2014-05-20 09:39:49

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

Il ya souvent des solutions de contournement, cependant. Généralement, cela peut être fait en "pilotant" le flux avec une portée entière, et en profitant du fait que les éléments originaux sont souvent dans un tableau ou dans un collection accessible par index. Par exemple, le problème du 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 tire avantage du fait que la source de données (le tableau des noms) est directement indexable. Si ce n'était pas le cas, cette technique ne fonctionne pas.

j'admets que cela ne répond pas à l'intention du défi 2. Néanmoins, elle résout le problème de manière raisonnablement efficace.

EDIT

mon exemple de code précédent utilisait flatMap pour fusionner les opérations de filtrage et de cartographie, mais cela était lourd et n'apportait aucun avantage. J'ai mis à jour l'exemple dans le commentaire de Holger.

59
répondu Stuart Marks 2016-08-17 21:37:23

depuis goyave 21, vous pouvez utiliser

Streams.mapWithIndex()

exemple (de doc officiel ):

Streams.mapWithIndex(
    Stream.of("a", "b", "c"),
    (str, index) -> str + ":" + index)
) // will return Stream.of("a:0", "b:1", "c:2")
24
répondu 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
répondu user1195526 2014-04-14 02:58:00

en plus de protonpack, Jooλ's Seq fournit cette fonctionnalité (et par extension les bibliothèques qui construisent sur elle 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 supporte aussi Seq.of (names) et construira un flux JDK sous les couvertures.

l'équivalent à réaction simple ressemblerait également à

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

Le simple fait de réagir la version est plus adaptée au traitement asynchrone / simultané.

13
répondu John McClean 2017-06-08 23:30:47

juste pour l'exhaustivité voici la solution impliquant mon StreamEx bibliothèque:

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

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

12
répondu Tagir Valeev 2015-09-10 02:07:51

il n'y a pas moyen d'itérer sur un Stream tout en ayant accès à l'index parce qu'un Stream est différent de tout 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 des éléments; au lieu de cela, ils portent des valeurs d'une source (qui pourrait être une structure de données, un générateur, un IO 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> à un Collection<V> , comme un List<V> , dans lequel vous aurez accès à l'index.

4
répondu Josh M 2013-08-31 19:37:26

avec https://github.com/poetix/protonpack u peut le faire que 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
répondu 42n4 2015-01-16 17:33:36

si cela ne vous dérange pas d'utiliser 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 de Collections JDK et Eclipse en 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 en 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 le lambdas aux classes internes anonymes ci - dessus, alors tous ces exemples de code fonctionneront aussi en Java 5-7.

Note: je suis un committer pour les Collections Eclipse

3
répondu 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
répondu V0idst4r 2017-06-20 19:33:31

j'ai trouvé les solutions ici quand le flux est créé de liste ou de tableau (et vous savez la taille). Mais que faire si Stream est de 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
répondu alex.b 2017-11-08 00:00:00

voici le code de 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
répondu 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
répondu 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
répondu alexpfx 2016-02-14 02:14:03

à Cette question ( cours d'eau de Façon à obtenir l'indice du premier élément correspondant boolean ) a marqué la question actuelle comme un doublon, donc je ne peux pas 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 <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 ça comme ça:

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
répondu 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());

ajouter .orElse(-1); dans le cas où vous voulez retourner une valeur si elle ne le trouve pas.

0
répondu 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));

utiliser une classe anonyme le long d'un ruisseau n'est pas bien utilisé tout en étant très utile.

-1
répondu Jean-Baptiste Yunès 2017-08-31 21:22:03