How to map elements to their index using streams?
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}
Guava has a static method Streams#mapWithIndex
Stream<String> myStream = Stream.of("one","two","three");
Map<Long, String> result3 = Streams.mapWithIndex(myStream, (s, i) -> Maps.immutableEntry(i + 1, s))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
// {1=one, 2=two, 3=three}
System.out.println(result3);
A clean solution not requiring random access source data, is
Map<Integer,String> result = Stream.of("one", "two", "three")
.collect(HashMap::new, (m,s) -> m.put(m.size() + 1, s),
(m1,m2) -> {
int offset = m1.size();
m2.forEach((i,s) -> m1.put(i + offset, s));
});
This also works with parallel streams.
In the unlikely case that this is a recurring task, it’s worth putting the logic into a reusable collector, including some optimizations:
public static <T> Collector<T,?,Map<Integer,T>> toIndexMap() {
return Collector.of(
HashMap::new,
(m,s) -> m.put(m.size() + 1, s),
(m1,m2) -> {
if(m1.isEmpty()) return m2;
if(!m2.isEmpty()) {
int offset = m1.size();
m2.forEach((i,s) -> m1.put(i + offset, s));
}
return m1;
});
}
Which can then be used like
Map<Integer,String> result = Stream.of("one", "two", "three")
.collect(MyCollectors.toIndexMap());
or
Map<Integer,Integer> result = IntStream.rangeClosed(1, 1000)
.boxed().parallel()
.collect(MyCollectors.toIndexMap());
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.