514 Stimmen

Gibt es eine prägnante Möglichkeit, über einen Stream mit Indizes in Java 8 zu iterieren?

Gibt es einen prägnanten Weg, über einen Stream zu iterieren und dabei Zugriff auf den Index im Stream zu haben?

String[] 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());

was im Vergleich zum dort gegebenen LINQ-Beispiel recht enttäuschend erscheint

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

Gibt es einen prägnanteren Weg?

Zudem scheint es, dass zip entweder verschoben oder entfernt wurde...

574voto

assylias Punkte 308529

Der sauberste Weg ist, von einem Strom von Indizes aus zu beginnen:

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

Die resultierende Liste enthält nur "Erik".


Eine Alternative, die vertrauter aussieht, wenn Sie an for-Schleifen gewöhnt sind, besteht darin, einen Ad-hoc-Zähler unter Verwendung eines veränderbaren Objekts, z. B. eines AtomicInteger, beizubehalten:

String[] names = {"Sam", "Pamela", "Dave", "Pascal", "Erik"};
AtomicInteger index = new AtomicInteger();
List list = Arrays.stream(names)
                          .filter(n -> n.length() <= index.incrementAndGet())
                          .collect(Collectors.toList());

Beachten Sie, dass die Verwendung der letzteren Methode auf einem parallelen Strom brechen könnte, da die Elemente nicht unbedingt "in der Reihenfolge" verarbeitet würden.

95voto

Stuart Marks Punkte 119555

Die Java 8 Streams-API bietet nicht die Möglichkeit, den Index eines Streamelements zu erhalten, sowie die Fähigkeit, Streams miteinander zu verbinden. Das ist bedauerlich, da es bestimmte Anwendungen (wie die LINQ-Herausforderungen) schwieriger macht, als sie es sonst wären.

Oft gibt es jedoch Workarounds. Normalerweise kann dies erreicht werden, indem der Stream mit einem Integer-Bereich "gesteuert" wird und davon profitiert wird, dass die Original-Elemente oft in einem Array oder einer nach Index zugänglichen Sammlung sind. Zum Beispiel kann das Problem der Challenge 2 auf diese Weise gelöst werden:

String[] 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());

Wie oben erwähnt, wird hier von der Tatsache profitiert, dass die Datenquelle (das Namensarray) direkt indexiert werden kann. Wenn das nicht der Fall wäre, würde diese Technik nicht funktionieren.

Ich gebe zu, dass dies nicht dem Zweck der Challenge 2 entspricht. Dennoch löst es das Problem recht effektiv.

Bearbeitung

In meinem vorherigen Codebeispiel habe ich flatMap verwendet, um die Filter- und Map-Operationen zu vereinen, aber das war umständlich und brachte keinen Vorteil. Ich habe das Beispiel gemäß dem Kommentar von Holger aktualisiert.

71voto

numéro6 Punkte 3592

Seit Guave 21 können Sie verwenden

Streams.mapWithIndex()

Beispiel (aus dem offiziellen Dokument):

Streams.mapWithIndex(
    Stream.of("a", "b", "c"),
    (str, index) -> str + ":" + index)
) // wird Stream.of("a:0", "b:1", "c:2") zurückgeben

29voto

user1195526 Punkte 532

Ich habe die folgende Lösung in meinem Projekt verwendet. Ich denke, es ist besser als die Verwendung von veränderbaren Objekten oder ganzzahligen Bereichen.

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() { }

    /**
     * Konvertiert einen {@link java.util.Iterator} in einen {@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);
    }

    /**
     * Verknüpft den angegebenen Stream mit seinen Indizes.
     */
    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());
            }
        });
    }

    /**
     * Gibt einen Stream zurück, der aus den Ergebnissen der Anwendung der gegebenen Zwei-Argumente-Funktion auf die Elemente dieses Streams besteht.
     * Das erste Argument der Funktion ist der Elementindex und das zweite Argument - der Elementwert.
     */
    public static  Stream mapWithIndex(Stream stream, BiFunction 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));
    }
}

14voto

John McClean Punkte 5017

Zusätzlich zu protonpack, jOO's Seq bietet diese Funktionalität (und bibliotheken, die auf ihr aufbauen, wie cyclops-react, ich bin der Autor dieser Bibliothek).

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

Seq unterstützt auch einfach Seq.of(names) und wird einen JDK Stream intern erstellen.

Das simple-react-Äquivalent würde ähnlich aussehen

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

Die simple-react-Version ist eher für asynchrone / parallele Verarbeitung geeignet.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X