Java 8 list to map with stream

74,354

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())
Share:
74,354
Nikolay
Author by

Nikolay

Updated on July 09, 2022

Comments

  • Nikolay
    Nikolay almost 2 years

    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

  • Misha
    Misha over 8 years
    This will fail if an Item is repeated in the list.
  • Holger
    Holger over 8 years
    Don’t do this with large lists. Unless you want to learn by example, what O(n²) means…
  • Holger
    Holger over 8 years
    You call List.get() expensive but recommend using an AtomicInteger?
  • Pepijn Schmitz
    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
    Holger over 8 years
    Only if you use LinkedList which is not really useful. On the other hand, AtomicInteger being O(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
    Pepijn Schmitz over 8 years
    @Holger The OP doesn't specify the List implementation. You seem to be prejudiced against LinkedList, but of course in reality there is nothing wrong with it and the List 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
    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
    Holger over 8 years
    I’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 an ArrayList, however for large lists, the insane memory overhead of LinkedList will counter-act it. LinkedList wins only in O(…) comparisons which ignore memory effects
  • Pepijn Schmitz
    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
    Boris the Spider over 8 years
    list.indexOf(i) is slow. I wouldn't suggest this approach.
  • Reinstate Monica
    Reinstate Monica over 8 years
    LinkedList.size() is O(1).
  • njzk2
    njzk2 over 8 years
    that stops working as soon as items is no longer a List. And is highly inefficient if items is, for example, a LinkedList instead of an ArrayList.
  • njzk2
    njzk2 over 8 years
    there are too many constraints and inefficiencies with this approach for it to be a useful solution
  • njzk2
    njzk2 over 8 years
    this 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
    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
    njzk2 over 8 years
    I guess that makes sense. I will be researching into how collectors guarantee the order of the stream after parallelization happens.
  • Tagir Valeev
    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
    Jean-François Savard over 8 years
    Using @TagirValeev 's library, it would be as simple as : EntryStream.of(items).toMap();.
  • njzk2
    njzk2 over 8 years
    thanks. It appears that the stream is partitioned in substrings, not subsequences, so that works indeed!
  • Reinstate Monica
    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
    Pepijn Schmitz over 8 years
    @Solomonoff'sSecret LinkedList.get() is O(n). But you said size(), not get().
  • Reinstate Monica
    Reinstate Monica over 8 years
    @PepijnSchmitz Of course. Obviously I repeatedly can't tell the difference between the words "size" and "get"...
  • Tagir Valeev
    Tagir Valeev over 8 years
    @Jean-FrançoisSavard, not to mention that zipWithIndex creates the stream which cannot be parallelized at all.
  • njzk2
    njzk2 over 8 years
    @TagirValeev and you are confident that EntryStream can?
  • Tagir Valeev
    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
    njzk2 over 8 years
    @TagirValeev the same comment applies, then: it only works with List, and its complexity depends on the access time of get(i) of the list's implementation.
  • Stuart Marks
    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
    Tagir Valeev over 8 years
    Why so complex? With forEachOrdered you don't need AtomicInteger, simply use stream.forEachOrdered(item -> map.put(map.size(), item)). Reading non-volatile field HashMap.size which is updated anyways is no worse than using CAS in AtomicInteger.
  • Stuart Marks
    Stuart Marks over 8 years
    @TagirValeev I suppose so. My main point is the use of forEachOrdered which hadn't been mentioned.
  • Tagir Valeev
    Tagir Valeev over 8 years
    Sure it's quite short solution, though using the Collector proposed in my version is more conceptually correct. Actually the best solution would be to use toList() and write a special adapter (based on AbstractMap<Integer, T>) which adapts a List<T> to Map<Integer, T>. Storing them into HashMap is just a waste of time and memory.