47

I've created method whih numerating each character of alphabet. I'm learning streams(functional programming) and try to use them as often as possible, but I don't know how to do it in this case:

private Map<Character, Integer> numerateAlphabet(List<Character> alphabet) {
    Map<Character, Integer> m = new HashMap<>();
    for (int i = 0; i < alphabet.size(); i++)
        m.put(alphabet.get(i), i);
    return m;
}

So, how to rewrite it using streams of Java 8?

Misha
  • 27,433
  • 6
  • 62
  • 78
Letfar
  • 3,253
  • 6
  • 25
  • 35

7 Answers7

78

Avoid stateful index counters like the AtomicInteger-based solutions presented in other answers. They will fail if the stream were parallel. Instead, stream over indexes:

IntStream.range(0, alphabet.size())
         .boxed()
         .collect(toMap(alphabet::get, i -> i));

Above assumes that the incoming list is not supposed to have duplicate characters since it's an alphabet. If you have possibility of duplicate elements then multiple elements will map to same key and then you need to specify merge function. For example you can use (a,b) -> b or (a,b) ->a as the third parameter to toMap method.

akhil_mittal
  • 23,309
  • 7
  • 96
  • 95
Misha
  • 27,433
  • 6
  • 62
  • 78
  • 2
    Also this assumes fast random access. Usually it's not a problem but better to mention this explicitly. – Tagir Valeev Oct 16 '15 at 05:49
  • Why would `AtomicInteger` fail for a parallel stream? Isn't it thread safe and 'atomic'? – Marsellus Wallace Feb 08 '18 at 21:27
  • 3
    @Gevorg Thread safety of `AtomicInteger` will guarantee that every number will be emitted exactly once. The problem is that under parallel stream they will be assigned to letters out of order. – Misha Feb 08 '18 at 22:16
28

It is better to use Function.identity() in place of i->i because as per answer for this question:

As of the current JRE implementation, Function.identity() will always return the same instance while each occurrence of identifier -> identifier will not only create its own instance but even have a distinct implementation class.

IntStream.range(0, alphabet.size())
         .boxed()
         .collect(toMap(alphabet::get, Function.identity()));
akhil_mittal
  • 23,309
  • 7
  • 96
  • 95
  • 1
    https://stackoverflow.com/questions/28032827/java-8-lambdas-function-identity-or-t-t – akhil_mittal Sep 02 '20 at 04:21
  • 1
    @abaines as mentioned in the answer for the above question: `As of the current JRE implementation, Function.identity() will always return the same instance while each occurrence of identifier -> identifier will not only create its own instance but even have a distinct implementation class.` – akhil_mittal Sep 30 '20 at 01:24
10

Using streams with AtomicInteger in Java 8:

private Map<Character, Integer> numerateAlphabet(List<Character> alphabet) {
    AtomicInteger index = new AtomicInteger();
    return alphabet.stream().collect(
            Collectors.toMap(s -> s, s -> index.getAndIncrement(), (oldV, newV)->newV));
}
ashiquzzaman33
  • 5,781
  • 5
  • 31
  • 42
7

You can collect the stream to a map and use the map size as index.

alphabet.stream()
    .collect(HashMap::new, (map, ch) -> map.put(ch, map.size()), Map::putAll);
codeAvax
  • 81
  • 1
  • 2
1

using AtomicInteger, this method is stateless

    AtomicInteger counter = new AtomicInteger();
    Map<Character, Integer> map = characters.stream()
            .collect(Collectors.toMap((c) -> c, (c) -> counter.incrementAndGet()));
    System.out.println(map);
Saravana
  • 12,647
  • 2
  • 39
  • 57
1

This is a way to collect index as a list ie Map<String,List<Integer>> for example ["A","B","A"] = "A"->[0,2],"B"->[1]

    AtomicInteger index = new AtomicInteger();
    Map<String,List<Integer>> map = basket.stream().collect(Collectors.groupingBy(String::valueOf,Collectors.mapping(i->index.getAndIncrement(),Collectors.toList())));
tanson
  • 82
  • 3
0

If you have duplicate values in Map, you can do something like this

Map<Object, Deque<Integer>> result = IntStream.range(0, source.size()).boxed()
.collect(Collectors.groupingBy(source::get, Collectors.toList()));

or like this if you need specific Map/List implementation:

Map<Object, Deque<Integer>> result = IntStream.range(0, source.size()).boxed()
.collect(Collectors.groupingBy(source::get, IdentityHashMap::new, Collectors.toCollection(ArrayDeque::new)));
Bojan Vukasovic
  • 2,054
  • 22
  • 43