Is there a concise way to iterate over a stream whilst having access to the index in the stream?
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());
which seems rather disappointing compared to the LINQ example given there
string[] names = { "Sam", "Pamela", "Dave", "Pascal", "Erik" }; var nameList = names.Where((c, index) => c.Length <= index + 1).ToList();
Is there a more concise way?
Further it seems the zip has either moved or been removed...
Since Java 8, we can use the forEach() method to iterate over the elements of a list. This method is defined in the Iterable interface, and can accept Lambda expressions as a parameter.
Yes, streams are sometimes slower than loops, but they can also be equally fast; it depends on the circumstances. The point to take home is that sequential streams are no faster than loops.
Java Stream forEach() method is used to iterate over all the elements of the given Stream and to perform an Consumer action on each element of the Stream.
Create an AtomicInteger for index. Get the Stream from the array using Arrays. stream() method. Map each elements of the stream with an index associated with it using map() method where the index is fetched from the AtomicInteger by auto-incrementing index everytime with the help of getAndIncrement() method.
The Java 8 streams API lacks the features of getting the index of a stream element as well as the ability to zip streams together. This is unfortunate, as it makes certain applications (like the LINQ challenges) more difficult than they would be otherwise.
There are often workarounds, however. Usually this can be done by "driving" the stream with an integer range, and taking advantage of the fact that the original elements are often in an array or in a collection accessible by index. For example, the Challenge 2 problem can be solved this way:
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());
As I mentioned above, this takes advantage of the fact that the data source (the names array) is directly indexable. If it weren't, this technique wouldn't work.
I'll admit that this doesn't satisfy the intent of Challenge 2. Nonetheless it does solve the problem reasonably effectively.
EDIT
My previous code example used flatMap
to fuse the filter and map operations, but this was cumbersome and provided no advantage. I've updated the example per the comment from Holger.
The cleanest way is to start from a stream of 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());
The resulting list contains "Erik" only.
One alternative which looks more familiar when you are used to for loops would be to maintain an ad hoc counter using a mutable object, for example an 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());
Note that using the latter method on a parallel stream could break as the items would not necesarily be processed "in order".
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With