wake-up-neo.com

Gibt es eine kurze Möglichkeit, einen Stream mit Indizes in Java 8 zu durchlaufen?

Gibt es eine kurze Möglichkeit, einen Stream zu durchlaufen, während Sie auf den Index im Stream zugreifen können?

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

was im Vergleich zu dem dort angegebenen LINQ-Beispiel eher 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?

Weiterhin scheint es, als sei der Zip entweder verschoben oder entfernt worden ...

309
Graeme Moss

Der sauberste Weg ist, von einem Strom von Indizes auszugehen:

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 Ihnen vertraut erscheint, wenn Sie für Schleifen gewöhnt sind, wäre die Verwaltung eines Ad-hoc-Zählers mithilfe eines veränderbaren Objekts, beispielsweise eines 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());

Beachten Sie, dassdie Verwendung der letzteren Methode für einen parallelen Stream möglicherweise beschädigt wird, da die Elemente nicht notwendigerweise "in der Reihenfolge"verarbeitet werden.

358
assylias

Die Java 8-Streams-API verfügt nicht über die Funktionen zum Abrufen des Index eines Streamelements sowie die Möglichkeit, Streams zusammenzippen. Dies ist bedauerlich, da bestimmte Anwendungen (wie die LINQ-Herausforderungen) schwieriger werden, als dies sonst der Fall wäre.

Es gibt jedoch häufig Problemumgehungen. Normalerweise kann dies durch "Treiben" des Streams mit einem ganzzahligen Bereich und durch Ausnutzung der Tatsache erreicht werden, dass sich die ursprünglichen Elemente häufig in einem Array oder in einer durch den Index zugänglichen Sammlung befinden. Zum Beispiel kann das Challenge 2-Problem auf diese Weise gelöst werden:

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

Wie oben erwähnt, nutzt dies die Tatsache, dass die Datenquelle (das Namensarray) direkt indexierbar ist. Wenn nicht, würde diese Technik nicht funktionieren.

Ich gebe zu, dass dies die Absicht von Herausforderung 2 nicht erfüllt. Trotzdem löst es das Problem einigermaßen effektiv.

EDIT

In meinem vorherigen Codebeispiel wurde flatMap verwendet, um die Filter- und Mapoperationen zu verschmelzen. Dies war jedoch umständlich und bot keinen Vorteil. Ich habe das Beispiel per Kommentar von Holger aktualisiert.

61
Stuart Marks

Seit Guave 21 kannst du verwenden

Streams.mapWithIndex()

Beispiel (aus offiziellem Dokument ):

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

Ich habe die folgende Lösung in meinem Projekt verwendet. Ich denke, es ist besser als veränderliche Objekte oder ganzzahlige Bereiche.

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

Neben dem Protonpack bietet jOOλs Seq diese Funktionalität (und durch Erweiterungsbibliotheken, die darauf aufbauen wie cyclops -rea , 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 nur Seq.of (Namen) und baut einen JDK-Stream unter den Covers auf.

Das einfach reagierende Ä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/gleichzeitige Verarbeitung ausgelegt.

13
John McClean

Der Vollständigkeit halber hier die Lösung mit meiner StreamEx library:

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

Hier erstellen wir ein EntryStream<Integer, String> , das Stream<Entry<Integer, String>> erweitert und einige spezifische Operationen wie filterKeyValue oder values hinzufügt. Auch die toList() - Verknüpfung wird verwendet.

12
Tagir Valeev

Ich habe die Lösungen hier gefunden, wenn der Stream aus Liste oder Array erstellt wurde (und Sie kennen die Größe). Was aber, wenn Stream eine unbekannte Größe hat? In diesem Fall versuchen Sie diese 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);
            }
        };
    }
}

Verwendungszweck:

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

Es gibt keine Möglichkeit, eine Stream während des Zugriffs auf den Index zu durchlaufen, da eine Stream sich von einer Collection unterscheidet. Eine Stream ist lediglich eine Pipeline zum Übertragen von Daten von einem Ort zum anderen, wie in der Dokumentation angegeben :

Kein Speicher. Ein Stream ist keine Datenstruktur, in der Elemente gespeichert werden. Stattdessen tragen sie Werte aus einer Quelle (die eine Datenstruktur, ein Generator, ein IO -Kanal usw. sein könnte) durch eine Pipeline von Rechenoperationen.

Da Sie in Ihrer Frage scheinbar angedeutet werden, können Sie Ihren Stream<V> natürlich in einen Collection<V> konvertieren, beispielsweise einen List<V>, in dem Sie Zugriff auf die Indizes haben.

4
Josh M

Mit https://github.com/poetix/protonpack Können Sie das tun:

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

Wenn es Ihnen nichts ausmacht, die Bibliothek eines Drittanbieters zu verwenden, stehen Eclipse Collections für zipWithIndex und forEachWithIndex zur Verwendung für viele Typen zur Verfügung. Hier finden Sie eine Reihe von Lösungen für diese Herausforderung für JDK-Typen und Eclipse-Auflistungstypen, die zipWithIndex verwenden.

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

Hier ist eine Lösung, die stattdessen forEachWithIndex verwendet.

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

Wenn Sie die Lambdas in anonyme innere Klassen ändern, funktionieren alle diese Codebeispiele auch in Java 5 - 7.

Hinweis: Ich bin ein Committer für Eclipse Collections

3
Donald Raab

Mit einer Liste kannst du es versuchen

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

Ausgabe:

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

Wenn Sie Vavr (früher bekannt als Javaslang) verwenden, können Sie die dedizierte Methode nutzen:

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

Wenn wir den Inhalt ausdrucken, sehen wir etwas Interessantes:

Stream((A, 0), ?)

Dies liegt daran, dass Streams faul ist und wir keine Ahnung von den nächsten Elementen im Stream haben.

2

Hier ist der Code von AbacusUtil

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

Offenlegung : Ich bin der Entwickler von AbacusUtil.

1
user_3380739

Wenn Sie versuchen, einen Index basierend auf einem Prädikat zu erhalten, versuchen Sie Folgendes:

Wenn Sie sich nur für den ersten Index interessieren:

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

Oder wenn Sie mehrere Indizes suchen möchten:

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

Fügen Sie .orElse(-1); hinzu, falls Sie einen Wert zurückgeben möchten, falls er nicht gefunden wird.

0
live-love

Diese Frage ( Stream Way, um den Index des ersten Elements zu erhalten, der mit boolean übereinstimmt - /) hat die aktuelle Frage als Duplikat markiert, sodass ich sie dort nicht beantworten kann. Ich beantworte es hier.

Hier ist eine generische Lösung, um den passenden Index abzurufen, für den keine externe Bibliothek erforderlich ist.

Wenn Sie eine Liste haben.

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

Und nenne es so:

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

Wenn Sie eine Sammlung verwenden, probieren Sie diese aus.

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

AUSGABE: 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

Sie können IntStream.iterate() verwenden, um den Index zu erhalten:

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

Dies funktioniert nur für Java 9 und höher. In Java 8 können Sie dies verwenden:

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

Sie können eine statische innere Klasse erstellen, um den Indexer einzukapseln.

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