Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a concise way to iterate over a stream with indices in Java 8?

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...

like image 382
Graeme Moss Avatar asked Aug 31 '13 19:08

Graeme Moss


People also ask

Which method is used to iterate over each element of stream in Java?

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.

Is Java 8 stream faster than for loop?

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.

Which method is used to iterate over each element of the stream?

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.

How do you index a stream map?

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.


2 Answers

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.

like image 32
Stuart Marks Avatar answered Oct 05 '22 23:10

Stuart Marks


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".

like image 83
assylias Avatar answered Oct 06 '22 00:10

assylias