50

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?

2
  • What do you mean by using streams? Commented Oct 15, 2015 at 2:08
  • 2
    return alphabet.stream()...collect(Collectors.toMap(...)); Commented Oct 15, 2015 at 2:09

7 Answers 7

82

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Also this assumes fast random access. Usually it's not a problem but better to mention this explicitly.
Why would AtomicInteger fail for a parallel stream? Isn't it thread safe and 'atomic'?
@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.
29

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()));

2 Comments

@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.
11

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));
}

1 Comment

getAndIncrement would probably be better. (And perhaps should use three-arg form of toMap to handle duplicates.)
8

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);

3 Comments

+1 because a) it's smart, but more importantly b) it works for collections that, unlike List, do not have a get(index) method.
I assume the put operation needs to have its parameters switched?
No ask was to keep character as key and index as value
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);

Comments

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())));

Comments

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)));

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.