如何使用流将列表转换为带有索引的地图 - Java 8?

Posted

技术标签:

【中文标题】如何使用流将列表转换为带有索引的地图 - Java 8?【英文标题】:How to convert List to Map with indexes using stream - Java 8? 【发布时间】:2016-01-13 07:45:51 【问题描述】:

我已经创建了计算字母表中每个字符的方法。我正在学习流(函数式编程)并尝试尽可能多地使用它们,但在这种情况下我不知道该怎么做:

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;

那么,如何使用 Java 8 的流重写它?

【问题讨论】:

使用流是什么意思? return alphabet.stream()...collect(Collectors.toMap(...)); 【参考方案1】:

避免使用状态索引计数器,例如其他答案中介绍的基于 AtomicInteger 的解决方案。如果流是并行的,它们将失败。相反,流过索引:

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

以上假设传入列表不应该有重复的字符,因为它是一个字母表。如果您有可能重复元素,那么多个元素将映射到同一个键,然后您需要指定merge function。例如,您可以使用(a,b) -&gt; b(a,b) -&gt;a 作为toMap 方法的第三个参数。

【讨论】:

这也假设快速随机访问。通常这不是问题,但最好明确提及。 为什么AtomicInteger 会因并行流而失败?它不是线程安全的和“原子的”吗? @Gevorg AtomicInteger 的线程安全性将保证每个数字都只发出一次。问题是在并行流下,它们会被乱序分配给字母。【参考方案2】:

最好使用Function.identity() 代替i-&gt;i,因为根据this 问题的答案:

在当前的 JRE 实现中,Function.identity() 将始终 每次出现标识符时返回相同的实例-> identifier 不仅会创建自己的实例,甚至还会有一个 不同的实现类。

IntStream.range(0, alphabet.size())
         .boxed()
         .collect(toMap(alphabet::get, Function.identity()));

【讨论】:

***.com/questions/28032827/… @abaines 如上述问题的答案中所述:As of the current JRE implementation, Function.identity() will always return the same instance while each occurrence of identifier -&gt; identifier will not only create its own instance but even have a distinct implementation class.【参考方案3】:

在 Java 8 中使用带有 AtomicInteger 的流:

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

【讨论】:

getAndIncrement 可能会更好。 (也许应该使用toMap 的三参数形式来处理重复项。)【参考方案4】:

您可以将流收集到地图并使用地图大小作为索引。

alphabet.stream()
    .collect(HashMap::new, (map, ch) -> map.put(ch, map.size()), Map::putAll);

【讨论】:

+1 因为 a) 它很聪明,但更重要的是 b) 它适用于与 List 不同,没有 get(index) 方法的集合。 我认为put 操作需要切换其参数?【参考方案5】:

使用AtomicInteger,这个方法是stateless

    AtomicInteger counter = new AtomicInteger();
    Map<Character, Integer> map = characters.stream()
            .collect(Collectors.toMap((c) -> c, (c) -> counter.incrementAndGet()));
    System.out.println(map);

【讨论】:

【参考方案6】:

如果你在 Map 中有重复的值,你可以这样做

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

如果您需要特定的 Map/List 实现,也可以这样:

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

【讨论】:

【参考方案7】:

这是一种将索引收集为列表的方法,即 Map<Integer>> 例如 ["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())));

【讨论】:

以上是关于如何使用流将列表转换为带有索引的地图 - Java 8?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用Java8流将列表列表转换为单个列表[重复]

使用流将对象列表转换为从 toString 方法获得的字符串

如何将 java 映射展平为列表,以便列表交替键和值?

有没有办法使用 Java 流将十六进制字符串转换为字节?

如何将地图 List<Map<String, String>> myList 列表转换为 Java 中的 Spark Dataframe?

如何在 Java 8 中对对象列表进行分页?