【问题标题】:Invert a Map with redundant values to produce a multimap反转具有冗余值的 Map 以生成多图
【发布时间】:2021-04-05 00:09:34
【问题描述】:

给定一张这样的地图,我们有一年中每周每天的频率计数:

Map.of(
    DayOfWeek.MONDAY , 52 ,
    DayOfWeek.TUESDAY , 52 ,
    DayOfWeek.WEDNESDAY, 53 ,
    DayOfWeek.THURSDAY , 53 ,
    DayOfWeek.FRIDAY , 52 ,
    DayOfWeek.SATURDAY , 52 ,
    DayOfWeek.SUNDAY , 52 
)

…或作为文本:

{周一=52,周二=52,周三=53,周四=53,周五=52,周六=52,周日=52}

...我如何反转以生成不同数字的multimap,每个数字都导致拥有该数字的DayOfWeek 的集合(列表?集合?)?

结果应该等同于这段代码的结果:

Map.of(
    53 , List.of( DayOfWeek.WEDNESDAY , DayOfWeek.THURSDAY ) ,
    52 , List.of( DayOfWeek.MONDAY , DayOfWeek.TUESDAY , DayOfWeek.FRIDAY , DayOfWeek.SATURDAY , DayOfWeek.SUNDAY ) 
)

我想直接使用 Java 生成结果 multimap,而不需要额外的库,例如 Eclipse CollectionsGoogle Guava。这些库可能会使这更容易,但我很想知道是否可以使用仅使用内置 Java 的解决方案。否则,我的问题与Guava: construct a Multimap by inverting a Map 完全相同。鉴于现代 Java 中的新流和多地图功能,我希望现在可以做到这一点,而当时还没有。

我看到了与此类似的各种现有问题。但没有一个适合我的情况,这似乎是一种相当普遍的情况。例如,this Question 忽略了原始值是冗余/多重的问题,因此需要一个多重映射。其他如thisthis 涉及Google Guava。

【问题讨论】:

标签: java dictionary collections multimap multi-mapping


【解决方案1】:

以下作品使用 Java 9 或更高版本:

@Test
void invertMap()
{
    Map<DayOfWeek, Integer> map = Map.of(
            DayOfWeek.MONDAY, 52,
            DayOfWeek.TUESDAY, 52,
            DayOfWeek.WEDNESDAY, 53,
            DayOfWeek.THURSDAY, 53,
            DayOfWeek.FRIDAY, 52,
            DayOfWeek.SATURDAY, 52,
            DayOfWeek.SUNDAY, 52
    );

    Map<Integer, Set<DayOfWeek>> flipped = new TreeMap<>();
    map.forEach((dow, count) ->
            flipped.computeIfAbsent(count, (key) ->
                    EnumSet.noneOf(DayOfWeek.class)).add(dow));

    Map<Integer, Set<DayOfWeek>> flippedStream = map.entrySet().stream()
           .collect(Collectors.groupingBy(
                    Map.Entry::getValue, 
                    TreeMap::new,
                    Collectors.mapping(
                            Map.Entry::getKey,
                            Collectors.toCollection(
                                    () -> EnumSet.noneOf(DayOfWeek.class)))));

    Map<Integer, Set<DayOfWeek>> expected = Map.of(
            53, EnumSet.of(
                    DayOfWeek.WEDNESDAY, 
                    DayOfWeek.THURSDAY),
            52, EnumSet.of(
                    DayOfWeek.MONDAY, 
                    DayOfWeek.TUESDAY, 
                    DayOfWeek.FRIDAY, 
                    DayOfWeek.SATURDAY, 
                    DayOfWeek.SUNDAY)
    );
    Assert.assertEquals(expected, flipped);
    Assert.assertEquals(expected, flippedStream);
}

如果您愿意使用第三方库,以下代码将适用于Eclipse Collections

@Test
void invertEclipseCollectionsMap()
{
    MutableMap<DayOfWeek, Integer> map =
            Maps.mutable.<DayOfWeek, Integer>empty()
                    .withKeyValue(DayOfWeek.MONDAY, 52)
                    .withKeyValue(DayOfWeek.TUESDAY, 52)
                    .withKeyValue(DayOfWeek.WEDNESDAY, 53)
                    .withKeyValue(DayOfWeek.THURSDAY, 53)
                    .withKeyValue(DayOfWeek.FRIDAY, 52)
                    .withKeyValue(DayOfWeek.SATURDAY, 52)
                    .withKeyValue(DayOfWeek.SUNDAY, 52);

    SetMultimap<Integer, DayOfWeek> flipped = map.flip();

    Assert.assertEquals(flipped.get(52), Set.of(
            DayOfWeek.MONDAY,
            DayOfWeek.TUESDAY,
            DayOfWeek.FRIDAY,
            DayOfWeek.SATURDAY,
            DayOfWeek.SUNDAY));
    Assert.assertEquals(flipped.get(53), Set.of(
            DayOfWeek.WEDNESDAY,
            DayOfWeek.THURSDAY));
}

注意:我是 Eclipse Collections 的提交者。

【讨论】:

  • 好东西。两点:(a)new TreeMap&lt;&gt;()new HashMap&lt;&gt;() 工作得更好,因为它将数字(新键)按顺序排列。 (b) 有没有办法使用EnumSet 而不是HashSet,因为DayOfWeek 是一个枚举?问题是EnumSet 类不提供构造函数。我试过EnumSet.noneOf,但得到错误“类 EnumSet 中的方法 noneOf 不能应用于给定类型;”。
  • 很好,谢谢。我只是对尝试将 EnumSet 工厂方法硬塞到流语法中感到好奇。
  • 我正在为此编写一个流示例,现在主要是出于我自己的好奇心,因为我知道这是可能的。我是 Eclipse Collections 框架的创建者,该框架还内置了将 Map 翻转为 Multimap 的方法,因此在纯 Java 代码中执行此操作对我来说是一个很好的 Streams 复习。
  • 我以为你的名字很熟悉。但我没有认出你戴着面具。随意附加 Eclipse Collections 解决方案的示例。我并不反对 Google Guava 或 Eclipse Collections,我只是想知道内置 Java 解决方案是否可行。
  • 我添加了 Stream 版本,以便您进行比较。我将 Map 从 HashMap 更改为 TreeMap 并将 EnumSet 包含在两个解决方案中。我会看看我明天是否能找到一些时间来附加一个 Eclipse Collections 解决方案。
【解决方案2】:

使用流,您可以将地图拆分为其条目,然后翻转条目和组:

numberOfDaysInYear.entrySet().stream()
  .collect(groupingBy(Map.Entry::getValue), mapping(Map.Entry::getKey, toList()));

根据您更新后的 cmets 要求优化实际上不在您的原始问题中,

numberOfDaysInYear.entrySet().stream()
  .collect(groupingBy(
    Map.Entry::getValue,
    TreeMap::new,
    mapping(Map.Entry::getKey, toCollection(() -> EnumSet.of(DayOfWeek.class)))
  ));

【讨论】:

  • 实际上,原始问题明确指出多图值可以是任何集合、列表或集合。因此,您的两种解决方案都有效。谢谢。
【解决方案3】:

Collectors.toMap

在这种情况下,您可以使用方法Collectors.toMap​(keyMapper,valueMapper,mergeFunction) 并生成多图,其中的值可以是listset

  1. Multimap 值为List
    Map<Integer, List<DayOfWeek>> inverted = map.entrySet().stream()
            .collect(Collectors.toMap(
                    // key of the new map
                    entry -> entry.getValue(),
                    // value of the new map
                    entry -> List.of(entry.getKey()),
                    // merging two values, i.e. lists
                    (list1, list2) -> {
                        List<DayOfWeek> list = new ArrayList<>();
                        list.addAll(list1);
                        list.addAll(list2);
                        return list;
                    }));
    
  2. Multimap 值为Set
    Map<Integer, Set<DayOfWeek>> inverted = map.entrySet().stream()
            .collect(Collectors.toMap(
                    // key of the new map
                    entry -> entry.getValue(),
                    // value of the new map
                    entry -> Set.of(entry.getKey()),
                    // merging two values, i.e. sets
                    (set1, set2) -> {
                        Set<DayOfWeek> set = new HashSet<>();
                        set.addAll(set1);
                        set.addAll(set2);
                        return set;
                    }));
    

另见:Collect a list of ids based on multiple fields

【讨论】:

    【解决方案4】:

    请参考以下代码:

    @Test
    void testMap() {
        Map<DayOfWeek, Integer> map = new HashMap<>();
        map.put(DayOfWeek.MONDAY, 52);
        map.put(DayOfWeek.TUESDAY, 52);
        map.put(DayOfWeek.WEDNESDAY, 53);
        map.put(DayOfWeek.THURSDAY, 53);
        map.put(DayOfWeek.FRIDAY, 52);
        map.put(DayOfWeek.SATURDAY, 52);
        map.put(DayOfWeek.SUNDAY, 52);
    
        Map<Integer, List<DayOfWeek>> result = new HashMap<>();
    
        for (Map.Entry<DayOfWeek, Integer> entry : map.entrySet()) {
            if (result.containsKey(entry.getValue())) {
                List list = result.get(entry.getValue());
                list.add(entry.getKey());
                result.put(entry.getValue(), list);
            } else {
                List list = new ArrayList();
                list.add(entry.getKey());
                result.put(entry.getValue(), list);
            }
        }
        System.out.println(result);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-17
      • 1970-01-01
      • 2020-12-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多