Java 8 list to map with stream
Solution 1
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)));
Solution 2
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).
Solution 3
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.)
Solution 4
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)));
Solution 5
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())
Nikolay
Updated on July 09, 2022Comments
-
Nikolay almost 2 years
I have a
List<Item>
collection. I need to convert it intoMap<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
-
Misha over 8 yearsThis will fail if an
Item
is repeated in the list. -
Holger over 8 yearsDon’t do this with large lists. Unless you want to learn by example, what
O(n²)
means… -
Holger over 8 yearsYou call
List.get()
expensive but recommend using anAtomicInteger
? -
Pepijn Schmitz over 8 years@Holger No. I call
List.get()
potentially expensive.AtomicInteger
is O(1),List.get()
could be anything up to O(n). -
Holger over 8 yearsOnly if you use
LinkedList
which is not really useful. On the other hand,AtomicInteger
beingO(1)
isn’t really relevant when it comes to the hidden cost of thread safety in an operation that you admit yourself, doesn’t work in parallel. If you start your answer with “Don't feel like you have to do everything in/with the stream”, why don’t you provide a stream-less alternative, like a straight-forward loop? That would be better than presenting a discouraged stream usage… -
Pepijn Schmitz over 8 years@Holger The OP doesn't specify the
List
implementation. You seem to be prejudiced againstLinkedList
, but of course in reality there is nothing wrong with it and theList
could easily be one, or possibly even another implementation which is even more expensive. Why second guess it? This way will always be the fastest. -
Pepijn Schmitz over 8 years@Holger The reason for the stream is that the OP specifically asked for it: " I can not figure it out how to do this with streams." Regarding the
AtomicInteger
: when uncontested it is practically as fast as incrementing a regular variable; there is no "hidden cost". -
Holger over 8 yearsI’m not prejudicing against
LinkedList
as it already exists for more than fifteen years now, which is enough time to determine that it is not useful in real life. The theoretical advantage is only one operation, inserting at an arbitrary index, but since it has to allocate memory for that and update half a dozen node references, that advantage doesn’t really materialize. It would require very large lists to outplay anArrayList
, however for large lists, the insane memory overhead ofLinkedList
will counter-act it.LinkedList
wins only inO(…)
comparisons which ignore memory effects -
Pepijn Schmitz over 8 years@Holger That's just more theoretical and irrelevant second guessing. The point is that it shouldn't matter which implementation of
List
the OP uses, and with this solution, it doesn't. -
Boris the Spider over 8 years
list.indexOf(i)
is slow. I wouldn't suggest this approach. -
Reinstate Monica over 8 yearsLinkedList.size() is O(1).
-
njzk2 over 8 yearsthat stops working as soon as
items
is no longer aList
. And is highly inefficient ifitems
is, for example, aLinkedList
instead of anArrayList
. -
njzk2 over 8 yearsthere are too many constraints and inefficiencies with this approach for it to be a useful solution
-
njzk2 over 8 yearsthis works iff a/ the combiner is guaranteed to be called with m1 and m2 in the right order b/ each accumulating map is called on a continuous sequence of items. This breaks if for example, odd values are accumulated in a map and even values in another one. I haven't found any source that suggests this could not happen.
-
Tagir Valeev over 8 years@njzk2, nevertheless this could not happen if your stream is ordered. This way all the existing collectors (like
toList()
) actually work. -
njzk2 over 8 yearsI guess that makes sense. I will be researching into how collectors guarantee the order of the stream after parallelization happens.
-
Tagir Valeev over 8 years@njzk2, the Collector contract is described in API docs. I fulfill the contract. When correct map and new element is passed to the accumulator, it produces new correct map. When two correct maps are passed to combiner, it produces new correct map. Just fulfill the contract, and you'll get the correct result. That's the beauty of interfaces.
-
Jean-François Savard over 8 yearsUsing @TagirValeev 's library, it would be as simple as :
EntryStream.of(items).toMap();
. -
njzk2 over 8 yearsthanks. It appears that the stream is partitioned in substrings, not subsequences, so that works indeed!
-
Reinstate Monica over 8 years@PepijnSchmitz You said "List.get() could be anything up to O(n)" and Holger replied "Only if you used LinkedList", which I thought implied (s)he thought LinkedList.get() was O(n). In fact it is, but more specifically it's also O(1). If I misunderstood, apologies.
-
Pepijn Schmitz over 8 years@Solomonoff'sSecret LinkedList.get() is O(n). But you said size(), not get().
-
Reinstate Monica over 8 years@PepijnSchmitz Of course. Obviously I repeatedly can't tell the difference between the words "size" and "get"...
-
Tagir Valeev over 8 years@Jean-FrançoisSavard, not to mention that zipWithIndex creates the stream which cannot be parallelized at all.
-
njzk2 over 8 years@TagirValeev and you are confident that
EntryStream
can? -
Tagir Valeev over 8 years@njzk2, surely it is. It however relies on fast random access as documentation says. Internally it's similar to Eran solution (which is also parallelizable and works with reasonable speed only for random access source). In contrast protonpack solution need no random access (roughly speaking it's closer to Pepijn Schmitz answer).
-
njzk2 over 8 years@TagirValeev the same comment applies, then: it only works with
List
, and its complexity depends on the access time ofget(i)
of the list's implementation. -
Stuart Marks over 8 years@njzk2 See my answer if you don't have a
List
or if it's expensive to access by index. -
Tagir Valeev over 8 yearsWhy so complex? With
forEachOrdered
you don't needAtomicInteger
, simply usestream.forEachOrdered(item -> map.put(map.size(), item))
. Reading non-volatile fieldHashMap.size
which is updated anyways is no worse than using CAS inAtomicInteger
. -
Stuart Marks over 8 years@TagirValeev I suppose so. My main point is the use of
forEachOrdered
which hadn't been mentioned. -
Tagir Valeev over 8 yearsSure it's quite short solution, though using the
Collector
proposed in my version is more conceptually correct. Actually the best solution would be to usetoList()
and write a special adapter (based onAbstractMap<Integer, T>
) which adapts aList<T>
toMap<Integer, T>
. Storing them intoHashMap
is just a waste of time and memory.