Reputation: 1121
I have a List<Item>
collection.
I need to convert it into Map<Integer, Item>
The key of the map must be the index of the item in the collection.
I can not figure it out how to do this with streams.
Something like:
items.stream().collect(Collectors.toMap(...));
Any help?
As this question is identified as possible duplicate I need to add that my concrete problem was - how to get the position of the item in the list and put it as a key value
Upvotes: 50
Views: 78638
Reputation: 24197
This is updated answer and has none of the problems mentioned in comments.
Map<Integer,Item> outputMap = IntStream.range(0,inputList.size()).boxed().collect(Collectors.toMap(Function.identity(), i->inputList.get(i)));
Upvotes: 7
Reputation: 132610
Eran's answer is usually the best approach for random-access lists.
If your List
isn't random access, or if you have a Stream
instead of a List
, you can use forEachOrdered
:
Stream<Item> stream = ... ;
Map<Integer, Item> map = new HashMap<>();
AtomicInteger index = new AtomicInteger();
stream.forEachOrdered(item -> map.put(index.getAndIncrement(), item));
This is safe, if the stream is parallel, even though the destination map is thread-unsafe and is operated upon as a side effect. The forEachOrdered
guarantees that items are processed one-at-a-time, in order. For this reason it's unlikely that any speedup will result from running in parallel. (There might be some speedup if there are expensive operations in the pipeline before the forEachOrdered
.)
Upvotes: 1
Reputation: 39403
Using a third party library (protonpack for example, but there are others) you can zip
the value with its index and voila:
StreamUtils.zipWithIndex(items.stream())
.collect(Collectors.toMap(Indexed::getIndex, Indexed::getValue));
although getIndex
returns a long
, so you may need to cast it using something similar to:
i -> Integer.valueOf((int) i.getIndex())
Upvotes: 1
Reputation: 2283
Don't feel like you have to do everything in/with the stream. I would just do:
AtomicInteger index = new AtomicInteger();
items.stream().collect(Collectors.toMap(i -> index.getAndIncrement(), i -> i));
As long as you don't parallelise the stream this will work and it avoids potentially expensive and/or problematic (in the case of duplicates) get()
and indexOf()
operations.
(You cannot use a regular int
variable in place of the AtomicInteger
because variables used from outside a lambda expression must be effectively final. Note that when uncontested (as in this case), AtomicInteger
is very fast and won't pose a performance problem. But if it worries you you can use a non-thread-safe counter.)
Upvotes: 11
Reputation: 100329
One more solution just for completeness is to use custom collector:
public static <T> Collector<T, ?, Map<Integer, T>> toMap() {
return Collector.of(HashMap::new, (map, t) -> map.put(map.size(), t),
(m1, m2) -> {
int s = m1.size();
m2.forEach((k, v) -> m1.put(k+s, v));
return m1;
});
}
Usage:
Map<Integer, Item> map = items.stream().collect(toMap());
This solution is parallel-friendly and does not depend on the source (you can use list without random access or Files.lines()
or whatever).
Upvotes: 13
Reputation: 394146
You can create a Stream
of the indices using an IntStream
and then convert them to a Map
:
Map<Integer,Item> map =
IntStream.range(0,items.size())
.boxed()
.collect(Collectors.toMap (i -> i, i -> items.get(i)));
Upvotes: 50