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 ...
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.
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.
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")
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));
}
}
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.
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.
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());
});
}
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.
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);
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
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
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.
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.
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.
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;
}
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
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());
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();
}