【问题标题】:Transposing arbitrary collection-of-collections in Scala在 Scala 中转置任意集合
【发布时间】:2012-05-19 05:04:43
【问题描述】:

我必须经常在 Scala 中转置一个“矩形”集合,例如:映射列表、列表映射、映射映射、列表集合、集合映射等。可以统一看作是从特定域到共域的映射(例如:一个List[A]/Array[A]是从Int域到A共域的映射,Set[A]是一个映射从 A 域到布尔共域等),我想编写一个干净的通用函数来执行转置操作(例如:将列表映射转换为映射的转置列表)。但是,我遇到了麻烦,因为除了 () 运算符之外,Scala 似乎没有统一的 API 来将集合抽象地视为映射?

所以我最终为每种类型的集合编写了一个单独的转置,如下所示:

def transposeMapOfLists[A,B]( mapOfLists: Map[A,List[B]] ) : List[Map[A,B]] = {
  val k = ( mapOfLists keys ) toList
  val l = ( k map { mapOfLists(_) } ) transpose;
  l map {  v => ( k zip v ) toMap }
}

def transposeListOfMaps[A,B]( listOfMaps: List[Map[A,B]]) : Map[A,List[B]] = {
  val k = ( listOfMaps(0) keys ) toList
  val l = ( listOfMaps map { m => k map { m(_) } } ) transpose;
  ( k zip l ) toMap
}

def transposeMapOfMaps[A,B,C]( mapOfMaps: Map[A,Map[B,C]] ) : Map[B,Map[A,C]] = {
  val k = ( mapOfMaps keys ) toList
  val listOfMaps = k map { mapOfMaps(_) }
  val mapOfLists = transposeListOfMaps( listOfMaps )
  mapOfLists map { p => ( p._1, ( k zip p._2 ) toMap ) }
}

有人可以帮我将这些方法统一到一个通用的集合转置中吗?它还将帮助我(我相信其他人)在此过程中学习一些有用的 Scala 特性。

ps:我忽略了异常处理并假设输入集合是矩形的,即所有内部集合的域元素构成相同的集合。

【问题讨论】:

    标签: scala collections functional-programming generic-programming transpose


    【解决方案1】:

    我确信以下使用类型类的混乱版本可以清理很多,但它可以作为一个快速的概念验证。如果没有依赖的方法类型,我看不到一种简单的方法来获得正确的返回类型(我相信这是可能的),所以你必须使用-Xexperimental

    trait Mapping[A, B, C] {
      type M[D] <: PartialFunction[A, D]
      def domain(c: C): Seq[A]
      def fromPairs[D](ps: Seq[(A, D)]): M[D]
      def codomain(c: C)(implicit ev: C <:< PartialFunction[A, B]) =
        domain(c).map(c)
      def toPairs(c: C)(implicit ev: C <:< PartialFunction[A, B]) =
        domain(c).map(a => (a, c(a)))
    }
    
    implicit def seqMapping[A, B <: Seq[A]] = new Mapping[Int, A, B] {
      type M[C] = Seq[C]
      def domain(c: B) = 0 until c.size
      def fromPairs[C](ps: Seq[(Int, C)]) = ps.sortBy(_._1).map(_._2)
    }
    
    implicit def mapMapping[A, B, C <: Map[A, B]] = new Mapping[A, B, C] {
      type M[D] = Map[A, D]
      def domain(c: C) = c.keys.toSeq
      def fromPairs[D](ps: Seq[(A, D)]) = ps.toMap
    }
    
    def transpose[A, B, C, M, N](m: M)(implicit
      pev: M <:< PartialFunction[A, N],
      qev: N <:< PartialFunction[B, C],
      mev: Mapping[A, N, M],
      nev: Mapping[B, C, N]
    ) = nev.fromPairs(nev.domain(mev.codomain(m).head).map(b =>
        b -> mev.fromPairs(mev.toPairs(m).map { case (a, c) => a -> c(b) })
    ))
    

    现在进行一些测试:

    scala> println(transpose(List(Map("a" -> 1, "b" -> 13), Map("b" -> 99, "a" -> 14))))
    Map(a -> Vector(1, 14), b -> Vector(13, 99))
    
    scala> println(transpose(Map('a' -> List(1, 2, 3), 'z' -> List(4, 5, 6))))
    Vector(Map(a -> 1, z -> 4), Map(a -> 2, z -> 5), Map(a -> 3, z -> 6))
    
    scala> println(transpose(Map("x" -> Map(4 -> 'a, 99 -> 'z), "y" -> Map(4 -> 'b, 99 -> 's))))
    Map(4 -> Map(x -> 'a, y -> 'b), 99 -> Map(x -> 'z, y -> 's))
    

    所以它按预期工作。

    【讨论】:

    • 谢谢 - 这非常有用!我花了很长时间才明白你做了什么,因为我不熟悉你使用的 Scala 的一些高级特性(这是我现在更详细地学习这些特性的一个很好的借口!)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-15
    • 1970-01-01
    • 2019-10-25
    • 2012-07-25
    • 2012-11-29
    • 2012-08-23
    相关资源
    最近更新 更多