如何使用java streams api将层级地图转换为平面地图

时间:2018-03-14 10:42:09

标签: java stream java-stream

所以,我有这个函数,它接受一个映射,其中一个名称与一个不同的索引之一相关联到一个数组中。索引号只会有一个与之关联的名称,因此没有重复项和空值,因此使用以下函数可以安全地展平层次结构。

public Map<Integer, Object> normalize( Map<Object, List<Integer>> hierarchalMap ) {

    Map<Integer, Object> normalizedMap = new HashMap<>();
    for (Map.Entry<Object, List<Integer>> entry : hierarchalMap.entrySet()) {
        for (Integer integer : entry.getValue()) {
            noramizedMap.put(integer, entry.getKey());
        }
    }
    return normalizedMap;
}

我试图将此功能更改为使用流API,而且我已经做到了这一点:

Map<Integer, Object> noramizedMap = new HashMap<>();
for (Map.Entry<Object, List<Integer>> entry : vars.entrySet()) {

    entry.getValue().forEach(e -> noramizedMap.put(e, entry.getValue()));

}

如果这是一些其他功能语言,我会做部分绑定或其他什么但是用java我尝试将外部循环解包到流中...收集我只是迷路了。

4 个答案:

答案 0 :(得分:3)

假设我的评论是正确的,您可以使用这样的Streams:

hierarchalMap.entrySet()
            .stream()
            .flatMap(entry -> entry.getValue()
                    .stream()
                    .map(i -> new AbstractMap.SimpleEntry<>(entry.getKey(), i)))
            .collect(Collectors.toMap(Entry::getValue, Entry::getKey));

这假设没有重复项,也没有空值。

答案 1 :(得分:2)

我认为这应该做你需要的:

<input
    ng-value="account.rowIsSelected"
    name="selectedAccount"
    ng-change="vm.selectAccount(account)"
    type="radio">

通常情况下,使用Java 8流时,必须将更多逻辑放入操作的“collect”部分,而不是使用另一种语言的等效结构,部分原因是缺少方便的元组类型。直观地说,创建一个对列表然后将它们收集到一个映射中似乎更合理,但最终会比在public Map<Integer, Object> normalizeJava8(Map<Object, List<Integer>> hierarchalMap ) { return hierarchalMap .entrySet() .stream() .collect( HashMap::new, (map, entry) -> entry.getValue().forEach(i -> map.put(i, entry.getKey())), HashMap::putAll); }

中放置更多的代码和计算密集度更高。

答案 2 :(得分:1)

使用流和Java 9:

Map<Integer, Object> normalizedMap = hierarchalMap.entrySet().stream()
    .flatMap(e -> e.getValue().stream().map(i -> Map.entry(i, e.getKey())))
    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

这几乎与this answer完全相同,只是我使用Map.entry()方法创建对并将整数作为键。

在没有溪流的情况下,这是另一种不那么冗长的方式:

Map<Integer, Object> normalizedMap = new HashMap<>();
hierarchalMap.forEach((k, v) -> v.forEach(i -> normalizedMap.put(i, k)));

答案 3 :(得分:0)

您可以在Java 8中使用两个便利收集器,它们不仅限于地图。

public static <T, K, V> Collector<T, ?, Map<K, V>> flatInverseMapping(Function<? super T, ? extends Stream<? extends K>> keyStreamFunction,
        Function<? super T, ? extends V> valueFunction) {
    return Collector.of(HashMap::new,
            (m, v) ->
                    keyStreamFunction.apply(v).forEach(innerV -> m.put(innerV, valueFunction.apply(v))),
            (m1, m2) -> {
                m1.putAll(m2);
                return m2;
            });
}

public static <T, K, V> Collector<T, ?, Map<K, V>> flatInverseMapping(Function<? super T, ? extends Collection<? extends K>> keyStreamFunction,
        Function<? super T, ? extends V> valueFunction) {
    return Collector.of(HashMap::new,
            (m, v) ->
                    keyStreamFunction.apply(v).forEach(innerV -> m.put(innerV, valueFunction.apply(v))),
            (m1, m2) -> {
                m1.putAll(m2);
                return m2;
            });
}

由于流和集合都具有forEach,因此除了输入对象外,这两种实现都相同。

为简要说明其工作方式,收集器的输出是由两个函数的输出定义的K和V(键和值)参数的映射。对于流中从输入对象派生的每个键值,将应用相同的值函数,以便在共享键之间使用一致的值反转映射。请注意,如果流中有多个项目可以解析为相同的键,则不会像正常的toMap实现那样抛出合并异常。 BiConsumer将需要更改为保持该行为:

(var1, var2) -> {
    Iterator<Map.Entry<K,V>> var3 = var2.entrySet().iterator();

    while(var3.hasNext()) {
        Map.Entry<K,V> var4 = var3.next();
        var1.merge(var4.getKey(), var4.getValue(), (v0, v1) -> {
            throw new IllegalStateException(String.format("Duplicate key %s", v0));
        });
    }
    return var1;
}

作为参考,它实际上是从Collectors.toMap代码复制的。