Scala:用每个键的最大值合并地图列表的惯用方法?

Scala:用每个键的最大值合并地图列表的惯用方法?,scala,scala-collections,reduce,scalaz,Scala,Scala Collections,Reduce,Scalaz,我有一个Map[Int,Int]列表,它们都有相同的键(从1到20),我想将它们的内容合并到一个Map[Int,Int] 我从scalaz库中读到了有关使用|+|合并贴图的堆栈溢出 我提出了以下解决方案,但对我来说似乎很笨拙 val defaultMap = (2 to ceiling).map((_,0)).toMap val factors: Map[Int, Int] = (2 to ceiling). map(primeFactors(_)). foldRight(def

我有一个Map[Int,Int]列表,它们都有相同的键(从1到20),我想将它们的内容合并到一个Map[Int,Int]

我从scalaz库中读到了有关使用
|+|
合并贴图的堆栈溢出

我提出了以下解决方案,但对我来说似乎很笨拙

val defaultMap = (2 to ceiling).map((_,0)).toMap
val factors: Map[Int, Int] = (2 to ceiling). map(primeFactors(_)).
        foldRight(defaultMap)(mergeMaps(_, _))

def mergeMaps(xm: Map[Int, Int], ym: Map[Int, Int]): Map[Int,Int] = {
    def iter(acc: Map[Int,Int], other: Map[Int,Int], i: Int): Map[Int,Int] = {
      if (other.isEmpty) acc
      else iter(acc - i + (i -> math.max(acc(i), other(i))), other - i, i + 1)
    }
    iter(xm, ym, 2)
  }

def primeFactors(number: Int): Map[Int, Int] = {
  def iter(factors: Map[Int,Int], rem: Int, i: Int): Map[Int,Int] = {
    if (i > number) factors
    else if (rem % i == 0) iter(factors - i + (i -> (factors(i)+1)), rem / i, i)
    else iter(factors, rem, i + 1)
  }
  iter((2 to ceiling).map((_,0)).toMap, number, 2)
}
说明:
val factors
创建一个映射列表,每个映射表示2-20之间数字的基本因子;然后将这18个贴图折叠成一个单独的贴图,其中包含每个键的最大值

更新 使用@folone的建议,我最终得到了以下代码(与我的原始版本相比有了明显的改进,并且我不必将映射更改为HashMaps):


此解决方案不适用于一般的代码> MAP>/Cult>S,但是如果您使用的是<代码>不可变的。HashMap < /代码> s,您可以考虑:

创建一个新映射,该映射是此映射和参数哈希的合并 地图

如果两个关键点相同,则使用指定的冲突解决功能 一样。冲突解决功能将始终采用第一种方式 来自此哈希映射的参数和来自该哈希映射的第二个参数

合并方法的平均性能高于遍历 以及从头开始重建一个新的不可变哈希映射,或者说++

用例:

val m1 = immutable.HashMap[Int, Int](1 -> 2, 2 -> 3)
val m2 = immutable.HashMap[Int, Int](1 -> 3, 4 -> 5)
m1.merged(m2) {
  case ((k1, v1), (k2, v2)) => ((k1, math.max(v1, v2)))
}

正如标签所示,您可能对scalaz解决方案感兴趣。下面是:

> console
[info] Starting scala interpreter...
[info] 
Welcome to Scala version 2.10.0 (OpenJDK 64-Bit Server VM, Java 1.7.0_15).
Type in expressions to have them evaluated.
Type :help for more information.

scala> import scalaz._, Scalaz._, Tags._
import scalaz._
import Scalaz._
import Tags._
在最大操作下存在Ints的半群实例:

scala> Semigroup[Int @@ MaxVal]
res0: scalaz.Semigroup[scalaz.@@[Int,scalaz.Tags.MaxVal]] = scalaz.Semigroup$$anon$9@15a9a9c6
让我们使用它:

scala> val m1 = Map(1 -> 2, 2 -> 3) mapValues MaxVal
m1: scala.collection.immutable.Map[Int,scalaz.@@[Int,scalaz.Tags.MaxVal]] = Map(1 -> 2, 2 -> 3)

scala> val m2 = Map(1 -> 3, 4 -> 5) mapValues MaxVal
m2: scala.collection.immutable.Map[Int,scalaz.@@[Int,scalaz.Tags.MaxVal]] = Map(1 -> 3, 4 -> 5)

scala> m1 |+| m2
res1: scala.collection.immutable.Map[Int,scalaz.@@[Int,scalaz.Tags.MaxVal]] = Map(1 -> 3, 4 -> 5, 2 -> 3)

如果您对这种“标记”(即
@
事物)的工作原理感兴趣,这里有一个很好的解释:

开始
Scala 2.13
,另一种仅基于标准库的解决方案是在应用which(顾名思义)之前将
映射作为序列合并相当于一个
groupBy
,后跟一个映射和一个reduce step on value:

// val map1 = Map(1 -> 2, 2 -> 3)
// val map2 = Map(1 -> 3, 4 -> 5)
(map1.toSeq ++ map2).groupMapReduce(_._1)(_._2)(_ max _)
// Map[Int,Int] = Map(2 -> 3, 4 -> 5, 1 -> 3)
这:

  • 将两个映射连接为元组序列(
    List((1,2)、(2,3)、(1,3)、(4,5))
    )。为简洁起见,
    map2
    被隐式转换为
    Seq
    ,以采用
    map1.toSeq
    的类型,但您可以选择使用
    map2.toSeq
    使其显式化

  • group
    s基于元素的第一个元组部分的元素(group-part ofgroupMapReduce)

  • map
    s将分组值映射到它们的第二个元组部分(映射组的一部分mapReduce)

  • reduce
    s映射值(
    \max\u
    )取其最大值(减少组映射的一部分reduce

scala> val m1 = Map(1 -> 2, 2 -> 3) mapValues MaxVal
m1: scala.collection.immutable.Map[Int,scalaz.@@[Int,scalaz.Tags.MaxVal]] = Map(1 -> 2, 2 -> 3)

scala> val m2 = Map(1 -> 3, 4 -> 5) mapValues MaxVal
m2: scala.collection.immutable.Map[Int,scalaz.@@[Int,scalaz.Tags.MaxVal]] = Map(1 -> 3, 4 -> 5)

scala> m1 |+| m2
res1: scala.collection.immutable.Map[Int,scalaz.@@[Int,scalaz.Tags.MaxVal]] = Map(1 -> 3, 4 -> 5, 2 -> 3)
// val map1 = Map(1 -> 2, 2 -> 3)
// val map2 = Map(1 -> 3, 4 -> 5)
(map1.toSeq ++ map2).groupMapReduce(_._1)(_._2)(_ max _)
// Map[Int,Int] = Map(2 -> 3, 4 -> 5, 1 -> 3)