【问题标题】:Scala merge two maps with different value typeScala合并两个具有不同值类型的映射
【发布时间】:2021-12-22 14:17:32
【问题描述】:

给定两张地图

  val m1 = Map("a" -> "a")
  val m2 = Map("a" -> 1)

作为合并这两者的结果,我想得到

Map("a" -> ("a",1))

我为此尝试了 cat semigroupal,它几乎完成了这项工作,但我必须自己为 semigroupal 创建实例,就像这样

Semigroupal.catsSemigroupalForMap[String]

然后将它们与product 方法合并。我需要做什么才能不手动创建它?例如。合并两个选项很简单

val noneInt: Option[Int] = None
val some3: Option[Int] = Some(3)
Semigroupal[Option].product(noneInt, some)

我想实现一个类似的代码来合并两个地图,但是在定义时

Semigroupal[Map]

编译器找不到任何隐含性。

【问题讨论】:

    标签: scala functional-programming scala-cats


    【解决方案1】:

    tupled 似乎可以解决问题:

    @ val mapA = Map(1 -> 2)
    mapA: Map[Int, Int] = Map(1 -> 2)
    
    @ val mapB = Map(1 -> "b")
    mapB: Map[Int, String] = Map(1 -> "b")
    
    @ (mapA, mapB).tupled
    res4: Map[Int, (Int, String)] = Map(1 -> (2, "b"))
    

    它可以通用编译:

    @ def mergeMaps[A, B, C](m1: Map[A, B], m2: Map[A, C]): Map[A, (B, C)] = (m1, m2).tupled
    defined function mergeMaps
    
    @ mergeMaps(mapA, mapB)
    res6: Map[Int, (Int, String)] = Map(1 -> (2, "b"))
    

    它会销毁不在两个映射中的键:

    @ val m1 = Map("a" -> "foo", "b" -> "bar")
    m1: Map[String, String] = Map("a" -> "foo", "b" -> "bar")
    
    @ val m2 = Map("a" -> 1, "c" -> 3)
    m2: Map[String, Int] = Map("a" -> 1, "c" -> 3)
    
    @ mergeMaps(m1, m2)
    res9: Map[String, (String, Int)] = Map("a" -> ("foo", 1))
    

    【讨论】:

    • 我确信它可以更简单:)
    【解决方案2】:

    这似乎是Align 的一个很好的用例

    import cats.data.Ior
    import cats.syntax.all._
    
    def combineMaps[K, A, B](m1: Map[K, A], m2: Map[K, B]): Map[K, (A, B)] =
      m1.align(m2).collect {
        case (key, Ior.Both(a, b)) => key -> (a, b)
      }
    

    可以这样使用:

    val m1 = Map("a" -> "foo", "b" -> "bar")
    val m2 = Map("a" -> 1, "c" -> 3)
    
    val result = combineMaps(m1, m2)
    // result: Map[String, (String, Int)] = Map("a" -> ("foo", 1))
    

    可以看到运行here的代码

    【讨论】:

      【解决方案3】:

      Vanilla Scala 也可以为您服务。要合并多个地图,您需要:

      • 将输入 Map 转换为元组序列并附加所有内容
      • groupBy 再次进入地图。这将累积来自同一个键的值。
      • 从输出地图中选择你想要的任何东西
      def merge(first: Map[String, Int], rest: Map[String, Int]*): Map[String, Seq[Int]] =
        rest
          .iterator
          .foldLeft(first.toSeq) { _ ++ _ } 
          .groupBy(_._1)
          .mapValues(pairs => pairs.map(_._2))
      
      val m1 = Map("b" -> 2)
      val m2 = Map("a" -> 1)
      val m3 = Map("a" -> 1)
      
      merge(m1, m2, m3)
      merge(m1, m2)
      merge(m1)
      
      

      【讨论】:

        猜你喜欢
        • 2018-03-21
        • 2018-02-23
        • 2015-01-27
        • 1970-01-01
        • 2022-01-18
        • 1970-01-01
        • 2023-03-07
        • 2018-02-28
        • 2016-06-08
        相关资源
        最近更新 更多