Pepe
Pepe

Reputation: 311

Streams use for map computation from list with counter

I have the following for loop which I would like to replace by a simple Java 8 stream statement:

List<String> words = new ArrayList<>("a", "b", "c");
Map<String, Long> wordToNumber = new LinkedHashMap<>();
Long index = 1L;

for (String word : words) {
  wordToNumber.put(word, index++);
}

I basically want a sorted map (by insertion order) of each word to its number (which is incremented at each for loop by 1), but done simpler, if possible with Java 8 streams.

Upvotes: 0

Views: 496

Answers (4)

dreamcrash
dreamcrash

Reputation: 51423

I basically want a sorted map (by insertion order) of each word to its number (which is incremented at each for loop by 1), but done simpler, if possible with Java 8 streams.

You can do it concisely using the following Stream:

AtomicLong index = new AtomicLong(1);
words.stream().forEach(word -> wordToNumber.put(word, index.getAndIncrement()));

Personally, I think that either

Map<String, Long> wordToNumber = new LinkedHashMap<>();
for(int i = 0; i < words.size(); i++){
    wordToNumber.put(words.get(i), (long) (i + 1));
}

or

Map<String, Long> wordToNumber = new LinkedHashMap<>();
for (String word : words) {
    wordToNumber.put(word, index++);
}

is simpler enough.

Upvotes: 1

Erik
Erik

Reputation: 648

A slightly different solution. The Integer::max is the merge function which gets called if the same word appears twice. In this case it picks the last position since that effectively what the code sample in the question does.

@Test
public void testWordPosition() {
    List<String> words = Arrays.asList("a", "b", "c", "b");
    AtomicInteger index = new AtomicInteger();
    Map<String, Integer> map = words.stream()
            .map(w -> new AbstractMap.SimpleEntry<>(w, index.incrementAndGet()))
            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, Integer::max));
    System.out.println(map);
}

Output:

{a=1, b=4, c=3}

Edit:

Incorporating Alex's suggestions in the comments, it becomes:

@Test
public void testWordPosition() {
    List<String> words = Arrays.asList("a", "b", "c", "b");
    AtomicLong index = new AtomicLong();
    Map<String, Long> map = words.stream()
            .collect(Collectors.toMap(w -> w, w -> index.incrementAndGet(), Long::max));
    System.out.println(map);
}

Upvotes: 1

Nowhere Man
Nowhere Man

Reputation: 19545

The following should work (though it's not clear why Long is needed because the size of List is int)

Map<String, Long> map = IntStream.range(0, words.size())
    .boxed().collect(Collectors.toMap(words::get, Long::valueOf));

The code above works if there's no duplicate in the words list.

If duplicate words are possible, a merge function needs to be provided to select which index should be stored in the map (first or last)

Map<String, Long> map = IntStream.range(0, words.size())
    .boxed().collect(
        Collectors.toMap(words::get, Long::valueOf, 
        (w1, w2) -> w2, // keep the index of the last word as in the initial code
        LinkedHashMap::new // keep insertion order
    ));

Similarly, the map can be built by streaming words and using external variable to increment the index (AtomicLong and getAndIncrement() may be used instead of long[]):

long[] index = {1L};
Map<String, Long> map = words.stream()
    .collect(
        Collectors.toMap(word -> word, word -> index[0]++, 
        (w1, w2) -> w2, // keep the index of the last word
        LinkedHashMap::new // keep insertion order
    ));

Upvotes: 1

Eugene
Eugene

Reputation: 120848

   Map<String, Long> wordToNumber = 
   IntStream.range(0, words.size())
            .boxed()
            .collect(Collectors.toMap(
                    words::get,
                    x -> Long.valueOf(x) + 1,
                    (left, right) -> { throw new RuntimeException();},
                    LinkedHashMap::new
            ));

You can replace that (left, right) -> { throw new RuntimeException();} depending on how you want to merge two elements.

Upvotes: 2

Related Questions