【问题标题】:Using Java 8 streams, how to transform Map<String, Map<String, List<Person>>> to Map<Integer, Map<String, Integer>>?使用 Java 8 流,如何将 Map<String, Map<String, List<Person>>> 转换为 Map<Integer, Map<String, Integer>>?
【发布时间】:2018-05-12 22:04:56
【问题描述】:

所以我有一个Map&lt;String, Map&lt;String, List&lt;Person&gt;&gt;&gt;,我想将其转换为Map&lt;Integer, Map&lt;String, Integer&gt;&gt;,其中第一个整数键代表数字 1,2,3 .....(当前条目的索引取自第一个地图)第二个整数给了我List&lt;Person&gt; 的大小。 内部映射中的字符串保持不变....

首先我只是尝试用列表的大小替换地图中的第二个整数,但它不起作用.....

Map<String, Map<String, List<Person>>> map2 =...
Map<String, Map<String, Integer>> map = map2.entrySet().stream().collect(
                Collectors.groupingBy(p -> p.getKey(), Collectors.toMap(p -> p.getValue().getKey(), p->p.getValue().values().size())));

【问题讨论】:

  • 地图没有定义的迭代顺序,因此当前条目的索引是不确定的。你到底想做什么?

标签: java lambda java-8 java-stream


【解决方案1】:

根据定义,地图没有索引。您可能正在尝试根据 插入顺序 为地图的条目指定一个从 1 开始的数字。如果您使用的是LinkedHashMap,那么有一种方法可以做到这一点。

但是,HashMap 不保证地图的顺序。此外,它不保证订单会随着时间的推移保持不变;因此,给这些条目一个数字并不总是产生预期的结果。

也就是说,您可能只想将映射中的条目从1 编号到n,其中n 是映射的大小,并且不关心上述条目的插入顺序,即第一个插入的条目不一定必须编号为 1,第二个插入的条目不一定必须编号为 2 等等。

现在,解决方法:

您可以将List&lt;Map&lt;String, Integer&gt;&gt; 作为临时结果集,当您需要对条目进行编号时,只需遍历元素并使用i + 1 获取编号,其中i 是控制变量。

假设我们有这个列表的例子:

List<Map<String, Integer>> temporaryResult = myMap.values()
            .stream()
            .map(a -> a.entrySet()
                    .stream()
                    .collect(Collectors.toMap(Map.Entry::getKey,
                            c -> c.getValue().size()))
            )
            .collect(Collectors.toList());

然后我们可以通过以下方式获得Map&lt;Integer, Map&lt;String, Integer&gt;&gt;

  Map<Integer, Map<String, Integer>> resultSet =
                IntStream.range(0, temporaryResult.size())
                .mapToObj(i -> new AbstractMap.SimpleEntry<>(i + 1,
                        temporaryResult.get(i)))
                .collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey,
                        AbstractMap.SimpleEntry::getValue));

注意-

  1. 如果编号应基于插入顺序,则此代码仅在您使用的地图保持插入顺序时才会产生预期结果。
  2. 如果编号不一定基于映射中条目的插入顺序,则无论使用何种映射实现,此解决方案都可以工作。

【讨论】:

    【解决方案2】:

    我会使用列表来保持初始映射中条目的顺序,无需将索引存储为映射的键。

    这将遍历您初始映射的所有值,将Map 映射到列表中带有size() 的等待值,并将所有内容收集到List

    List<Map<String, Integer>> map = 
        map2.values()
            .stream()
            .map(m-> m.entrySet()
                      .stream()
                      .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().size())))
            .collect(Collectors.toList());
    

    如果您出于任何原因确实需要Map,以下代码也应该可以工作

    int i = 1;
    Map<Integer, Map<String, Integer>> newMap = new HashMap<>();
    for (Map.Entry entry : map2.entrySet()) {
        Map<String, Integer> map = entry.value().entrySet()
                  .stream()
                  .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().size())
        newMap.put(i++, map);
    }
    

    【讨论】:

    • 我尝试修改 for each 循环,但还不行:for(Map.Entry>> entry : map2.entrySet())
    • @YassinHajaj 你可以用for(Map.Entry&lt;String, Map&lt;String, List&lt;Person&gt;&gt;&gt; entry : map2.entrySet()){ Map&lt;String, Integer&gt; map = entry.getValue().entrySet() .stream() .collect(Collectors.toMap(Map.Entry::getKey, e -&gt; e.getValue().size())); newMap.put(i++, map); } 修复你的第二个代码 sn-p 虽然第一个代码 sn-p 仍然不正确。
    • 然后您可以通过使用toMap 而不是groupingBy 来修复第一个代码sn-p,并确保在正确的位置有括号以使其编译。
    猜你喜欢
    • 2019-12-06
    • 2023-03-26
    • 1970-01-01
    • 2022-09-30
    • 1970-01-01
    • 1970-01-01
    • 2017-01-28
    • 2017-03-21
    • 1970-01-01
    相关资源
    最近更新 更多