【问题标题】:Is there an elegant way to reduce a stream of maps to one map有没有一种优雅的方法可以将地图流减少到一个地图
【发布时间】:2014-12-19 18:31:24
【问题描述】:

给定一些地图,是否有一种单行方式将它们的所有条目放入一张地图中?

忽略空值、覆盖条目等问题,我想编码的是:

public static <K, V> Map<K, V> reduce(Map<K, V>... maps) {
    return Arrays.stream(maps)
        .reduce(new HashMap<K, V>(), (a, b) -> a.putAll(b));
}

但这会产生编译错误,因为a.putAll(b)void。如果它返回this,它将起作用。

为了解决这个问题,我编写了代码:

public static <K, V> Map<K, V> reduce(Map<K, V>... maps) {
    return Arrays.stream(maps)
        .reduce(new HashMap<K, V>(), (a, b) -> {a.putAll(b); return a;});
}

它可以编译和工作,但它是一个丑陋的 lambda;编码return a; 感觉是多余的。

一种方法是重构一个实用方法:

public static <K, V> Map<K, V> reduce(Map<K, V> a, Map<K, V> b) {
    a.putAll(b);
    return a;
}

清理 lambda:

public static <K, V> Map<K, V> reduce(Map<K, V>... maps) {
    return Arrays.stream(maps)
       .reduce(new HashMap<K, V>(), (a, b) -> reduce(a, b));
}

但现在我有一个可重复使用但有点无用的实用方法。

有没有更优雅的方式来调用累加器上的方法在 lambda 中返回它?

【问题讨论】:

  • 不幸的是,reduce 的这种使用方式从根本上被破坏了:它可能适用于顺序执行,但它会在并行执行时中断。这是因为 reduce() 方法将其第一个(身份)参数视为可重用值。因此它适用于原始类型和不可变类型,但并行流上的可变值(如new HashMap&lt;&gt;())将由不同的线程同时发生变异。那不会有好的结局。答案是使用collect(),就像@Pshemo 的答案一样。

标签: java lambda java-8 reduce


【解决方案1】:

reduce 的工作原理与

U result = identity;
for (T element : this stream)
    result = accumulator.apply(result, element)
return result;

这意味着代表accumulator.apply 的lambda 需要return 结果(最终或中间结果)。

如果你想避免这种行为,请使用collect,它更像

R result = supplier.get();
for (T element : this stream)
    accumulator.accept(result, element);
return result;

所以表示accumulator.accept的lambda不需要return任何值,而是根据element修改result

例子:

public static <K, V> Map<K, V> reduce(Map<K, V>... maps) {
    return Arrays.stream(maps)
            .collect(HashMap::new, Map::putAll, Map::putAll);
            //                          ^            ^
            //                          |         collect results from parallel streams
            //                       collect results in single thread
}

【讨论】:

  • @Bohemian 我对实现不是特别熟悉,但reduce 应该是可并行的。明确要求它是关联的,因此实现可以代替(((a * b) * c) * d 执行(a * b) * (c * d)(其中一个线程获取ab 和另一个cd)。
  • @Bohemian 对于关联运算符,表达式中的运算顺序很重要,而不是时间。在我上面的示例中,如果* 表示字符串连接,无论您采用哪种方式,它仍然可以工作。 reduce 不适合普通的非交换散列函数(尽管可以工作,例如,Set.hashCode 的实现)。 foldLeft 将是合适的,但无论出于何种原因(还没有)。 (来自 Stuart Marks 本人:stackoverflow.com/questions/24308146/…
  • @Bohemian:当然,您必须为reduce 指定一个无副作用的associative 函数,以便通过并行减少获得一致的结果。如果你符合标准,就没有问题。令您惊讶的是:它适用于字符串连接,因为连接是关联的,即如果abc 都是@,则(a+b)+ca+(b+c) 具有相同的结果987654351@s。这意味着a+b+c+d 也与(a+b)+(c+d) 具有相同的结果,因此可以同时评估(a+b)(c+d)
  • @Bohemian:缩减是完全可并行的,只要你的缩减函数是关联的reducecollect 之间的区别在于 reduce 通过消耗 values 并产生新的 values 工作(想想 sum、max、min),而 collect通过将元素合并到可变结果容器(列表、集合、映射、数组等)中来工作
  • 如果两个或多个地图有重复的键会发生什么?
猜你喜欢
  • 2020-02-12
  • 1970-01-01
  • 1970-01-01
  • 2010-12-10
  • 2021-08-14
  • 1970-01-01
  • 2013-06-15
  • 2010-11-23
  • 2019-05-03
相关资源
最近更新 更多