web-dev-qa-db-fra.com

Existe-t-il une méthode concise pour parcourir un flux avec des index dans Java 8?

Existe-t-il un moyen concis 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 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 le Zip ait été déplacé ou supprimé.

309
Graeme Moss

Le moyen le 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" uniquement.


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

358
assylias

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

Cependant, il existe souvent des solutions de contournement. Pour ce faire, vous pouvez généralement "piloter" le flux avec une plage entière et tirer parti du fait que les éléments d'origine se trouvent souvent dans un tableau ou dans une collection accessible par index. Par exemple, le problème du défi 2 peut être résolu de la manière suivante:

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é plus haut, 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'objectif 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 mappage, mais cette opération était lourde et ne présentait aucun avantage. J'ai mis à jour l'exemple en fonction du commentaire de Holger.

61
Stuart Marks

Depuis la 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")
27
numéro6

J'ai utilisé la solution suivante dans mon projet. Je pense que c'est mieux que d'utiliser des objets 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 <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));
    }
}
23
user1195526

En plus de protonpack, jOOλ's Seq fournit cette fonctionnalité (et par extension, les bibliothèques qui le construisent 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 prend également en charge que Seq.of (noms) et créera un flux JDK sous les couvertures.

L'équivalent simple-réagir ressemblerait de la même façon 

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

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

13
John McClean

Juste pour compléter, voici la solution impliquant ma StreamEx library:

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

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

12
Tagir Valeev

J'ai trouvé les solutions ici lorsque le flux est créé de liste ou tableau (et vous connaissez la taille) Mais que se passe-t-il 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);
            }
        };
    }
}

Usage:

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());
    });
}
4
alex.b

Il n’existe pas de moyen de parcourir une Stream tout en ayant accès à l’index car une Stream ne ressemble à aucune Collection. Un Stream est simplement un pipeline permettant de 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 provenant d'une source (une structure de données, un générateur, un canal IO, etc.) via un pipeline d'opérations de calcul.

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

4
Josh M

Avec https://github.com/poetix/protonpack U peut faire cela 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
42n4

Si vous n’êtes pas dérangé par l’utilisation d’une bibliothèque tierce, Collections Eclipse a zipWithIndex et forEachWithIndex disponible pour différents types. Voici un ensemble de solutions à ce défi pour les types JDK et les collections Eclipse à l'aide de 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 modifiez les lambdas en classes internes anonymes ci-dessus, tous ces exemples de code fonctionneront également en Java 5 - 7.

Note: Je suis un partisan des collections Eclipse

3
Donald Raab

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
V0idst4r

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), ?)

En effet, Streams sont paresseux et nous n'avons aucune idée des éléments suivants dans le flux.

2
Grzegorz Piwowarek

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
user_3380739

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());

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

0
live-love

Cette question ( Stream Way pour obtenir l'index du premier élément correspondant à boolean ) 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 <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
Steven Spungin
String[] namesArray = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
String completeString
         =  IntStream.range(0,namesArray.length)
           .mapToObj(i -> namesArray[i]) // Converting each array element into Object
           .map(String::valueOf) // Converting object to String again
           .collect(Collectors.joining(",")); // getting a Concat String of all values
        System.out.println(completeString);

SORTIE: Sam, Pamela, Dave, Pascal, Erik

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

IntStream.range(0,namesArray.length)
               .mapToObj(i -> namesArray[i]) // Converting each array element into Object
               .map(String::valueOf) // Converting object to String again
               .forEach(s -> {
                //You can do various operation on each element here
                System.out.println(s);
               }); // getting a Concat String of all 
0
Arpan Saini

Vous pouvez utiliser IntStream.iterate() pour obtenir l'index:

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

Ceci ne fonctionne que pour Java 9 à partir de Java 8, vous pouvez utiliser ceci:

String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
List<String> nameList = IntStream.iterate(0, i -> i + 1)
        .limit(names.length)
        .filter(i -> names[i].length() <= i)
        .mapToObj(i -> names[i])
        .collect(Collectors.toList());
0
Samuel Philipp

Vous pouvez créer une classe interne statique pour encapsuler l'indexeur comme je le devais 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
alexpfx