【问题标题】:Merge maps by key按键合并地图
【发布时间】:2011-12-06 23:59:59
【问题描述】:

假设我有两张地图:

val a = Map(1 -> "one", 2 -> "two", 3 -> "three")
val b = Map(1 -> "un", 2 -> "deux", 3 -> "trois")

我想按键合并这些映射,应用一些函数来收集值(在这种特殊情况下,我想将它们收集到一个序列中,给出:

val c = Map(1 -> Seq("one", "un"), 2 -> Seq("two", "deux"), 3 -> Seq("three", "trois"))

感觉应该有一种很好的惯用方式。

【问题讨论】:

  • 您应该包含信息,如何处理仅存在于一个地图中的元素,最好在示例数据中以便于测试,以避免歧义。

标签: scala functional-programming maps scalaz


【解决方案1】:

scala.collection.immutable.IntMap 有一个 intersectionWith 方法可以完全满足您的需求(我相信):

import scala.collection.immutable.IntMap

val a = IntMap(1 -> "one", 2 -> "two", 3 -> "three", 4 -> "four")
val b = IntMap(1 -> "un", 2 -> "deux", 3 -> "trois")

val merged = a.intersectionWith(b, (_, av, bv: String) => Seq(av, bv))

这会给你IntMap(1 -> List(one, un), 2 -> List(two, deux), 3 -> List(three, trois))。请注意,它正确地忽略了仅出现在 a 中的键。

附带说明:我经常发现自己需要 Scala 中 Haskell's Data.Map 中的 unionWithintersectionWith 等函数。我认为没有任何原则性的理由让它们只能在 IntMap 上使用,而不是在基本的 collection.Map 特征中使用。

【讨论】:

  • unionWith、interesectionWith 等看起来与我正在寻找的完全一样。可惜他们用错了语言!
  • 我刚刚测试了 scalaz 功能 intersectionWith 并发现,出现在 b 和 a 中的键都会被忽略。
  • 如果键是字符串类型怎么办?还是其他类型?
  • @mpr 然后你需要使用List(_) 对值进行映射,并与Scalaz 或Cats 中的映射的幺半群实例求和(或者当然只需编写你自己的intersectionWith从头开始)。
【解决方案2】:
val a = Map(1 -> "one", 2 -> "two", 3 -> "three")
val b = Map(1 -> "un", 2 -> "deux", 3 -> "trois")

val c = a.toList ++ b.toList
val d = c.groupBy(_._1).map{case(k, v) => k -> v.map(_._2).toSeq}
//res0: scala.collection.immutable.Map[Int,Seq[java.lang.String]] =
        //Map((2,List(two, deux)), (1,List(one, un), (3,List(three, trois)))

【讨论】:

  • 你介意为一个完整的 scala 新手解释一下 _._1 吗?
  • 地图是 Tuples2 的集合。例如: val tuple: Tuple3[Int, Int, String] = (100, 10, "one") ,如果你想得到一个字符串 "one" 你可以使用 tuple._3 。元组很有用,例如如果你想返回多个值
  • _._1 的第一部分(点前的下划线)是参数的匿名名称。例如:List(1,2,3,4).map(_.toDouble) 会将所有列表成员转换为 Double。就像i 中的for(i <- List(1,2,3,4)) ...
  • + 1 但您可以省略最后的 .toSeq 来简化,因为它没有任何用处
  • 这不能正确处理键在一个映射中但不在另一个映射中的情况,并且重建映射也使其比 intersectionWith 更昂贵,后者与元素总数成线性关系.
【解决方案3】:

Scalaz 为A 可用的任何类型|+| 添加了一个方法Semigroup[A]

如果您映射您的地图,以便每个值都是一个单元素序列,那么您可以非常简单地使用它:

scala> a.mapValues(Seq(_)) |+| b.mapValues(Seq(_))
res3: scala.collection.immutable.Map[Int,Seq[java.lang.String]] = Map(1 -> List(one, un), 2 -> List(two, deux), 3 -> List(three, trois))

【讨论】:

  • 实际上,我在真实案例中的值是序列,但我想通过构建另一个序列来组合它们,而不是通过将一个附加到另一个。
  • 我不确定我是否理解你,抱歉 - 你是否希望这些值是嵌套序列?
  • 是的,我想要嵌套序列,我可以通过将现有序列包装在 Seq 中来做到这一点,但这感觉有点像作弊 - 在其他情况下,我可能想要使用完全不同的组合器不适合半群结构 - 例如,给出值序列的交集的大小。
  • 我明白了,诚然,这只是我会考虑在这种特殊情况下使用的作弊手段,而不是一般的解决方案。
【解决方案4】:

Scala 2.13 开始,您可以使用groupMap,它(顾名思义)相当于groupBy,后跟map 值:

// val map1 = Map(1 -> "one", 2 -> "two",  3 -> "three")
// val map2 = Map(1 -> "un",  2 -> "deux", 3 -> "trois")
(map1.toSeq ++ map2).groupMap(_._1)(_._2)
// Map(1 -> List("one", "un"), 2 -> List("two", "deux"), 3 -> List("three", "trois"))

这个:

  • 将两个映射连接为一个元组序列 (List((1, "one"), (2, "two"), (3, "three")))。为简洁起见,map2隐式转换为Seq 以与map1.toSeq 的类型对齐 - 但您可以选择使用map2.toSeq 使其显式化。

    李>
  • groups 元素基于它们的第一个元组部分 (_._1)(groupMap 的组部分)

  • maps 将值分组到它们的第二个元组部分 (_._2)(组的映射部分Map

【讨论】:

    【解决方案5】:

    这是我在寻找其他解决方案之前的第一种方法:

    for (x <- a) yield 
      x._1 -> Seq (a.get (x._1), b.get (x._1)).flatten
    

    为了避免碰巧只存在于 a 或 b 中的元素,过滤器很方便:

    (for (x <- a) yield 
      x._1 -> Seq (a.get (x._1), b.get (x._1)).flatten).filter (_._2.size == 2)
    

    需要展平,因为 b.get (x._1) 返回一个选项。为了使 flatten 工作,第一个元素也必须是一个选项,所以我们不能在这里只使用 x._2 。

    对于序列,它也可以:

    scala> val b = Map (1 -> Seq(1, 11, 111), 2 -> Seq(2, 22), 3 -> Seq(33, 333), 5 -> Seq(55, 5, 5555))
    b: scala.collection.immutable.Map[Int,Seq[Int]] = Map(1 -> List(1, 11, 111), 2 -> List(2, 22), 3 -> List(33, 333), 5 -> List(55, 5, 5555))
    
    scala> val a = Map (1 -> Seq(1, 101), 2 -> Seq(2, 212, 222), 3 -> Seq (3, 3443), 4 -> (44, 4, 41214))
    a: scala.collection.immutable.Map[Int,ScalaObject with Equals] = Map(1 -> List(1, 101), 2 -> List(2, 212, 222), 3 -> List(3, 3443), 4 -> (44,4,41214))
    
    scala> (for (x <- a) yield x._1 -> Seq (a.get (x._1), b.get (x._1)).flatten).filter (_._2.size == 2) 
    res85: scala.collection.immutable.Map[Int,Seq[ScalaObject with Equals]] = Map(1 -> List(List(1, 101), List(1, 11, 111)), 2 -> List(List(2, 212, 222), List(2, 22)), 3 -> List(List(3, 3443), List(33, 333)))
    

    【讨论】:

      【解决方案6】:
      val fr = Map(1 -> "one", 2 -> "two", 3 -> "three")
      val en = Map(1 -> "un", 2 -> "deux", 3 -> "trois")
      
      def innerJoin[K, A, B](m1: Map[K, A], m2: Map[K, B]): Map[K, (A, B)] = {
        m1.flatMap{ case (k, a) => 
          m2.get(k).map(b => Map((k, (a, b)))).getOrElse(Map.empty[K, (A, B)])
        }
      }
      
      innerJoin(fr, en) // Map(1 -> ("one", "un"), 2 -> ("two", "deux"), 3 -> ("three", "trois")): Map[Int, (String, String)]
      

      【讨论】:

        【解决方案7】:

        所以我对这两种解决方案都不太满意(我想构建一个新类型,所以 semigroup 并不合适,Infinity 的解决方案似乎很复杂),所以我暂时采用了这个。我很高兴看到它有所改进:

        def merge[A,B,C](a : Map[A,B], b : Map[A,B])(c : (B,B) => C) = {
          for (
            key <- (a.keySet ++ b.keySet);
            aval <- a.get(key); bval <- b.get(key)
          ) yield c(aval, bval)
        }
        merge(a,b){Seq(_,_)}
        

        我希望在任一映射中都不存在键时不返回任何内容(这与其他解决方案不同),但是指定它的方式会很好。

        【讨论】:

        • 您已经实现了哈希联接。您可以为每种类型的连接编写不同的方法,例如左外连接、右外连接、外连接和内连接,这将在每种情况下为您提供所需的行为。
        • 请注意,IntMapintersectionWith 会处理您在此处指定的键仅出现在一个映射中的情况。
        • 一个更短更有效的替代方案是:def merge[A,B,C](a : Map[A,B], b : Map[A,B])(c : (B,B) =&gt; C) = { for((k,v1) &lt;-a; v2 &lt;- b.get(k)) yield (k, c(v1, v2)) } merge(a,b){Seq(_,_)}
        【解决方案8】:
        def merge[A,B,C,D](b : Map[A,B], c : Map[A,C])(d : (Option[B],Option[C]) => D): Map[A,D] = {
          (b.keySet ++ c.keySet).map(k => k -> d(b.get(k), c.get(k))).toMap
        }
        
        def optionSeqBiFunctionK[A]:(Option[A], Option[A]) => Seq[A] = _.toSeq ++ _.toSeq
        
        merge(a,b)(optionSeqBiFunctionK)
        
        

        【讨论】:

        • 请始终将您的答案放在上下文中,而不仅仅是粘贴代码。有关详细信息,请参阅here
        猜你喜欢
        • 2015-06-21
        • 2015-10-19
        • 2019-10-20
        • 1970-01-01
        • 1970-01-01
        • 2018-05-09
        • 2020-10-28
        • 2013-04-24
        • 2016-12-30
        相关资源
        最近更新 更多