C'è un modo conciso per iterare su un flusso con indici in Java 8?


Esiste un modo conciso per iterare su un flusso pur avendo accesso all'indice nel flusso?

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

Che sembra piuttosto deludente rispetto all'esempio LINQ dato lì

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

Esiste un modo più conciso?

Inoltre sembra che lo zip sia stato spostato o rimosso...

Author: Jordi Castilla, 2013-08-31

17 answers

Il modo più pulito è iniziare da un flusso di indici:

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

L'elenco risultante contiene solo "Erik".


Un'alternativa che sembra più familiare quando si è abituati a for loop sarebbe quella di mantenere un contatore ad hoc utilizzando un oggetto mutabile, ad esempio 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());

Si noti che l'utilizzo di quest'ultimo metodo su un flusso parallelo potrebbe interrompersi poiché gli elementi non verrebbero necessariamente elaborati "in ordine".

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

L'API Java 8 streams non ha le caratteristiche di ottenere l'indice di un elemento stream e la possibilità di comprimere i flussi insieme. Questo è un peccato, in quanto rende alcune applicazioni (come le sfide LINQ) più difficili di quanto non sarebbero altrimenti.

Ci sono spesso soluzioni alternative, tuttavia. Di solito questo può essere fatto "guidando" il flusso con un intervallo intero e sfruttando il fatto che gli elementi originali sono spesso in un array o in una raccolta accessibile per indice. Ad esempio, il problema Challenge 2 può essere risolto in questo modo:

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

Come ho detto sopra, questo sfrutta il fatto che l'origine dati (l'array dei nomi) è direttamente indicizzabile. Se non lo fosse, questa tecnica non funzionerebbe.

Ammetto che questo non soddisfa l'intento della Sfida 2. Tuttavia risolve il problema in modo ragionevolmente efficace.

MODIFICA

Il mio precedente esempio di codice ha usato flatMap per fondere il filtro e la mappa operazioni, ma questo era ingombrante e non ha fornito alcun vantaggio. Ho aggiornato l'esempio per il commento di Holger.

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

Dal guava 21, è possibile utilizzare

Streams.mapWithIndex()

Esempio (da documento ufficiale):

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

Ho usato la seguente soluzione nel mio progetto. Penso che sia meglio che usare oggetti mutabili o intervalli interi.

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

Oltre a protonpack, Seq di jOOλfornisce questa funzionalità (e per estensione librerie che si basano su di esso come cyclops-react, sono l'autore di questa libreria).

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

Seq supporta anche solo Seq.di (nomi) e costruirà un flusso JDK sotto le coperte.

L'equivalente simple-react sarebbe simile a

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

La versione simple-react è più adatta per l'elaborazione asincrona / concorrente.

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

Solo per completezza ecco la soluzione che coinvolge la mia libreria StreamEx :

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

Qui creiamo un EntryStream<Integer, String> che estende Stream<Entry<Integer, String>> e aggiunge alcune operazioni specifiche come filterKeyValue oppure values. Inoltre toList() viene utilizzata la scorciatoia.

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

Non esiste un modo per iterare su un Stream pur avendo accesso all'indice perché un Stream è diverso da qualsiasi Collection. Un Stream è semplicemente una pipeline per il trasporto di dati da un luogo all'altro, come indicato nella documentazione :

Nessun deposito. Un flusso non è una struttura dati che memorizza elementi; invece, portano valori da una sorgente (che potrebbe essere una struttura dati, un generatore, un canale IO, ecc.)

Di naturalmente, come sembra suggerire nella tua domanda, puoi sempre convertire il tuo Stream<V> in un Collection<V>, come un List<V>, in cui avrai accesso agli indici.

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

Con https://github.com/poetix/protonpack puoi farlo 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

Se non ti dispiace usare una libreria di terze parti, Eclipse Collections ha zipWithIndex e forEachWithIndex disponibile per l'uso in molti tipi. Ecco una serie di soluzioni a questa sfida sia per i tipi JDK che per i tipi Eclipse Collections usando 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);

Ecco una soluzione che utilizza invece forEachWithIndex.

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

Se si modificano i lambda in classi interne anonime sopra, tutti questi esempi di codice funzioneranno in Java 5 - 7 come bene.

Nota: {[20] } Sono un committer per le collezioni Eclipse

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

Con un elenco puoi provare

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

Uscita:

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

Ho trovato le soluzioni qui quando viene creato il flusso di list o array (e conosci la dimensione). Ma cosa succede se lo stream ha dimensioni sconosciute? In questo caso prova questa 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);
            }
        };
    }
}

Utilizzo:

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

Ecco il codice di AbacusUtil

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

Divulgazione: Sono lo sviluppatore di AbacusUtil.

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

Se si utilizza Vavr (precedentemente noto come Javaslang), è possibile sfruttare il metodo dedicato:

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

Se stampiamo il contenuto, vedremo qualcosa di interessante:

Stream((A, 0), ?)

Questo perché Streams sono pigri e non abbiamo idea degli elementi successivi nello stream.

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

È possibile creare una classe interna statica per incapsulare l'indicizzatore come dovevo fare nell'esempio seguente:

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

Questa domanda (Stream Way to get index of first element matching boolean ) ha contrassegnato la domanda corrente come un duplicato, quindi non posso rispondere lì; sto rispondendo qui.

Ecco una soluzione generica per ottenere l'indice corrispondente che non richiede una libreria esterna.

Se hai una lista.

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

E chiamalo così:

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

E se usi una raccolta, prova questa.

   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

Se stai cercando di ottenere un indice basato su un predicato, prova questo:

Se ti interessa solo il primo indice:

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

O se vuoi trovare più indici:

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

Aggiungi .orElse(-1); nel caso in cui desideri restituire un valore se non lo trova.

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

Un modo possibile è indicizzare ogni elemento sul flusso:

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'utilizzo di una classe anonima lungo un flusso non è ben utilizzato pur essendo molto utile.

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