嵌套地图最内层地图上的mapValues

huy*_*hjl 2 scala

这个问题的灵感来自于我试图回答这个问题.

假设您有一系列数据(例如,可能来自CSV文件).groupBy可用于分析数据的某些方面,按列或列组合进行分组.例如:

val groups0: Map[String, Array[String]] = 
  seq.groupBy(row => row(0) + "-" + row(4))
Run Code Online (Sandbox Code Playgroud)

如果我想在组中创建子组,我可以做

val groups1: Map[String, Map[String, Array[String]]] = 
  groups0.mapValues(row => row.groupBy(_(1))
Run Code Online (Sandbox Code Playgroud)

如果我想再这样做一次真的很麻烦:

val groups2 = 
  groups1.mapValues(groups => groups.mapValues(row => row.groupBy(_(2)))
Run Code Online (Sandbox Code Playgroud)

所以这是我的问题,给出了一个任意的嵌套Map[K0, Map[K1, ..., Map[Kn, V]]],你如何编写一个mapValues函数,它采用一个f: (V) => B并应用于最里面的函数V来返回一个Map[K0, Map[K1, ..., Map[Kn, B]]]

小智 5

我的第一直觉说,以类型安全的方式处理任意嵌套是不可能的,但似乎你可以定义一些告诉编译器如何做的隐含.
本质上,"简单"映射器告诉它如何处理普通的非嵌套情况,而"wrappedMapper"告诉它如何向下钻取一个Map层:

  // trait to tell us how to map inside of a container.
  trait CanMapInner[WrappedV, WrappedB,V,B] {
    def mapInner(in: WrappedV, f: V => B): WrappedB
  }

  // simple base case (no nesting involved).
  implicit def getSimpleMapper[V,B] = new CanMapInner[V,B,V,B] {
    def mapInner(in: V, f: (V) => B): B = f(in)
  }

  // drill down one level of "Map".
  implicit def wrappedMapper[K,V,B,InnerV,InnerB]
    (implicit innerMapper: CanMapInner[InnerV,InnerB,V,B]) =
    new CanMapInner[Map[K,InnerV], Map[K,InnerB],V,B] {
      def mapInner(in: Map[K, InnerV], f: (V) => B): Map[K, InnerB] =
        in.mapValues(innerMapper.mapInner(_, f))
    }

  // the actual implementation.
  def deepMapValues[K,V,B,WrappedV,WrappedB](map: Map[K,WrappedV], f: V => B)
      (implicit mapper: CanMapInner[WrappedV,WrappedB,V,B]) = {
    map.mapValues(inner => mapper.mapInner(inner, f))
  }

  // testing with a simple map
  {
    val initMap = Map(1 -> "Hello", 2 -> "Goodbye")
    val newMap = deepMapValues(initMap, (s: String) => s.length)
    println(newMap) // Map(1 -> 5, 2 -> 7)
  }

  // testing with a nested map
  {
    val initMap = Map(1 -> Map("Hi" -> "Hello"), 2 -> Map("Bye" -> "Goodbye"))
    val newMap = deepMapValues(initMap, (s: String) => s.length)
    println(newMap) // Map(1 -> Map(Hi -> 5), 2 -> Map(Bye -> 7))
  }
Run Code Online (Sandbox Code Playgroud)

当然,在实际代码中,由于其简单性,模式匹配动态解决方案非常诱人.类型安全不是一切:)