流用于从带有计数器的列表进行地图计算

Streams use for map computation from list with counter

我有以下 for 循环,我想用一个简单的 Java 8 流语句替换它:

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

我基本上想要每个单词与其编号(在每个 for 循环中递增 1)的排序映射(按插入顺序),但做得更简单,如果可能的话使用 Java 8 个流。

以下应该有效(虽然不清楚为什么需要 Long 因为 List 的大小是 int

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

如果 words 列表中没有重复项,则上面的代码有效。

如果可能有重复的词,需要提供一个合并功能select 哪个索引应该存储在地图中(第一个或最后一个)

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

类似地,可以通过流式传输 words 和使用外部变量来增加索引来构建地图(可以使用 AtomicLonggetAndIncrement() 代替 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
            ));

您可以替换 (left, right) -> { throw new RuntimeException();},具体取决于您希望如何 合并 两个元素。

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.

您可以使用以下Stream:

简洁地完成
AtomicLong index = new AtomicLong(1);
words.stream().forEach(word -> wordToNumber.put(word, index.getAndIncrement()));

个人认为

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

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

比较简单。

一个略有不同的解决方案。 Integer::max 是合并函数,如果同一个词出现两次,则会调用该函数。在这种情况下,它会选择最后一个位置,因为这实际上是问题中的代码示例所做的。

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

输出:

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

编辑:

结合Alex在评论中的建议,变成:

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