Nikolay
Nikolay

Reputation: 1121

Java 8 list to map with stream

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

Answers (6)

akhil_mittal
akhil_mittal

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

Stuart Marks
Stuart Marks

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

njzk2
njzk2

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

Pepijn Schmitz
Pepijn Schmitz

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

Tagir Valeev
Tagir Valeev

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

Eran
Eran

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

Related Questions