Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to map elements to their index using streams?

I get a stream of some custom objects and I would like to create a map Map<Integer, MyObject> with index of each object as key. To give you a simple example:

Stream<String> myStream = Arrays.asList("one","two","three").stream();
Integer i = 0;
Map<Integer, String> result3 = myStream.collect(Collectors.toMap(x -> i++, x -> x));

Obviously, this doesn't compile because:

local variables referenced from a lambda expression must be final or effectively final

Is there a simple way to map elemnts of a stream to their indices so that the expected output for above example is something like:

{1=one, 2=two, 3=three}
like image 713
nopens Avatar asked Aug 24 '19 12:08

nopens


People also ask

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.

What is MAP method in stream?

The map() function is a method in the Stream class that represents a functional programming concept. In simple words, the map() is used to transform one object into other by applying a function. That's why the Stream. map(Function mapper) takes a function as an argument.

How do I iterate through a stream List?

You can use stream() method of the List interface which gives a stream to iterate using forEach method. In forEach method, we can use the lambda expression to iterate over all elements. The following code snippet shows the usage of streams to iterate over the list. list.


Video Answer


2 Answers

You can use an IntStream to solve this:

List<String> list = Arrays.asList("one","two","three");
Map<Integer, String> map = IntStream.range(0, list.size()).boxed()
        .collect(Collectors.toMap(Function.identity(), list::get));

You create an IntStream from 0 to list.size() - 1 (IntStream.range() excludes the last value from the stream) and map each index to the value in your list. The advantage of this solution is, that it will also work with parallel streams, which is not possible with the use of an AtomicInteger.

So the result in this case would be:

{0=one, 1=two, 2=three}

To start the first index at 1 you can just add 1 during collect:

List<String> list = Arrays.asList("one", "two", "three");
Map<Integer, String> map = IntStream.range(0, list.size()).boxed()
        .collect(Collectors.toMap(i -> i + 1, list::get));

This will result in this:

{1=one, 2=two, 3=three}
like image 147
Samuel Philipp Avatar answered Oct 21 '22 11:10

Samuel Philipp


You i variable is not effectively final.

You can use AtomicInteger as Integer wrapper:

Stream<String> myStream = Arrays.asList("one","two","three").stream();
AtomicInteger atomicInteger = new AtomicInteger(0);
Map<Integer, String> result3 = myStream.collect(Collectors.toMap(x -> atomicInteger.getAndIncrement(), Function.identity()));

I consider it a bit hacky because it only solves the problem of effectively final variable. Since it is a special ThreadSafe version it might introduce some overhead. Pure stream solution in the answer by Samuel Philipp might better fit your needs.

like image 27
Michał Krzywański Avatar answered Oct 21 '22 11:10

Michał Krzywański