简体   繁体   中英

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.

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

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM