通过聚合键将地图映射到单个地图

Map of maps to a single map by aggregating keys

我有这样一个结构:

Map<KeyType1, Map<KeyType2, List<ValueType>>>

还有一个 class 同时持有 KeyType1KeyType2,我们称它为 AggregatedKey。它可以使用其构造函数实例化:

public AggregatedKey(KeyType1 keyType1, KeyType2 keyType2)

我的目标是将上面的结构映射到如下内容:

Map<AggregatedKey, List<ValueType>>

所以,基本上,键应该映射到单个聚合键。

如何使用 Java 9 来实现?

这样就可以了

Map<KeyType1, Map<KeyType2, List<String>>> m = new HashMap<>();
Map<AggregatedKey, List<String>> result = new HashMap<>();
m.entrySet().forEach(entry -> {
    entry.getValue().entrySet().forEach(nestedEntry -> {
        result.put(new AggregatedKey(entry.getKey(), nestedEntry.getKey()), nestedEntry.getValue());
    });
});

不要忘记在 AggregatedKey 中实现 hashcode/equals,否则使用 result 地图会遇到一些麻烦。

这是其中一种方式:

public static void main(String[] args) {
    Map<String, Map<String, String>> map = new HashMap<>();
    Map<String, String> innerMap1 = new HashMap<>();
    Map<String, String> innerMap2 = new HashMap<>();
    innerMap1.put("k11", "v11");
    innerMap1.put("k12", "v12");
    innerMap1.put("k13", "v13");
    innerMap2.put("k21", "v22");
    innerMap2.put("k22", "v22");
    map.put("k1", innerMap1);
    map.put("k2", innerMap2);
    Map<String, String> result = map
            .entrySet()
            .stream()
            .flatMap(stringMapEntry ->
                stringMapEntry
                        .getValue()
                        .entrySet()
                        .stream()
                            .map(stringStringEntry ->
                                    new AbstractMap.SimpleEntry<String, String>(
                                            buildAggregatedKey(
                                                    stringMapEntry.getKey(), 
                                                    stringStringEntry.getKey()
                                            ), 
                                            stringStringEntry.getValue()
                                    )
                            )
            ).collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue));
    System.out.println(result);
}

private static String buildAggregatedKey(String key1, String key2){
   return  key1 + "_" + key2;
}

您在哪里更改此 buildAggregatedKey 以满足您的聚合逻辑。

您可以像这样使用流。

  • 首先流出外部映射的条目集
  • 然后调用 flatMap 流式传输内部地图的 entrySet
  • 使用 outerEntry.getKey()innerEntry.getKey() 创建 AggregatedKey 实例请注意,这需要 class 有一个接受密钥的构造函数。
  • 然后将该实例和内部映射 (List<ValueType>) 中的值放入 AbstractMap.SimpleEntry 实例中以传递给收集器。
  • 使用 SimpleEntry
  • 的键和值创建新地图

给定以下源图。

Map<KeyType1, Map<KeyType2, List<ValueType>>> map =
        new HashMap<>();  // contains the info to be remapped.

这是结果

Map<AggregatedKey, List<ValueType>> result = map.entrySet()
        .stream()
        .flatMap(outerEntry-> outerEntry
                        .getValue().entrySet().stream()
                        .map(innerEntry -> new AbstractMap.SimpleEntry<>(
                new AggregatedKey(outerEntry.getKey(),innerEntry.getKey()),
                                innerEntry.getValue())))
        
        .collect(Collectors.toMap(
                AbstractMap.SimpleEntry::getKey,
                AbstractMap.SimpleEntry::getValue));

    }
public static final class AggregatedKey<K1, K2> {

    private final K1 one;
    private final K2 two;

    public AggregatedKey(K1 one, K2 two) {
        this.one = one;
        this.two = two;
    }
}

public static <K1, K2, V> Map<AggregatedKey<K1, K2>, List<V>> convert1(Map<K1, Map<K2, List<V>>> map) {
    Map<AggregatedKey<K1, K2>, List<V>> res = new HashMap<>();

    for (Map.Entry<K1, Map<K2, List<V>>> one : map.entrySet())
        for (Map.Entry<K2, List<V>> two : one.getValue().entrySet())
            res.put(new AggregatedKey<>(one.getKey(), two.getKey()), two.getValue());

    return res;
}

public static <K1, K2, V> Map<AggregatedKey<K1, K2>, List<V>> convert2(Map<K1, Map<K2, List<V>>> map) {
    return map.entrySet().stream()
            .flatMap(e1 -> e1.getValue().entrySet().stream()
                    .map(e2 -> new AggregatedKey<>(new AggregatedKey<>(e1.getKey(), e2.getKey()), e2.getValue())))
            .collect(Collectors.toMap(tuple -> tuple.one, tuple -> tuple.two));
}

这是一个使用流的示例测试,其中第一步转换地图的内部元素,第二步收集到地图:

package prove.aggregatemap;

import org.junit.Assert;
import org.junit.Test;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class TestAggregator {

@Test
public void aggregate() {
    Map<String, List<String>> letter_map= Map.of("first", List.of("one","two","three"),
            "second", List.of("four","five","six"),
            "third", List.of("seven","eight","nine"));
    Map<String, List<String>> num_map= Map.of("first_num", List.of("1","2","3"), "second_num", List.of("4","5","6"), "third_num", List.of("7","8","9"));
    Map<String,Map<String,List<String>>> mapOfMaps=Map.of("letter",letter_map,"num",num_map);

    Map<AggregateKey, List<String>> result=mapOfMaps.entrySet().stream().flatMap(entry ->
       entry.getValue().entrySet().stream().collect(Collectors.toMap(
                inner_entry -> new AggregateKey(entry.getKey(), inner_entry.getKey()),
                inner_entry -> inner_entry.getValue())).entrySet().stream()
    ).collect(Collectors.toMap(entry->entry.getKey(),entry->entry.getValue()));
    Assert.assertEquals(List.of("one","two","three"),result.get(new AggregateKey("letter","first")));
    Assert.assertEquals(List.of("four","five","six"),result.get(new AggregateKey("letter","second")));
    Assert.assertEquals(List.of("seven","eight","nine"),result.get(new AggregateKey("letter","third")));
}

}