在Scala中反转嵌套Map

Den*_*VDB 2 dictionary scala

我有一张类型的地图Map[A, Map[B, C]].

如何将其反转为具有类型的地图Map[B, Map[A, C]]

Tra*_*own 6

您可以通过多种方式定义此操作.我会介绍一些我发现最清楚的东西.对于第一个实现,我将从一个辅助方法开始:

def flattenNestedMap[A, B, C](nested: Map[A, Map[B, C]]): Map[(A, B), C] =
  for {
    (a, innerMap) <- nested
    (b, c)        <- innerMap
  } yield (a, b) -> c
Run Code Online (Sandbox Code Playgroud)

这会将嵌套地图展平为从对到值的地图.接下来我们可以定义另一个帮助操作,它几乎可以满足我们的需要.

def groupByBs[A, B, C](flattened: Map[(A, B), C]): Map[B, Map[(A, B), C]] =
  flattened.groupBy(_._1._2)
Run Code Online (Sandbox Code Playgroud)

现在我们只需B要从内部映射中的键中删除冗余:

def invert[A, B, C](nested: Map[A, Map[B, C]]): Map[B, Map[A, C]] =
  groupByBs(flattenNestedMap(nested)).mapValues(
    _.map {
      case ((a, _), c) => a -> c
    }
  )
Run Code Online (Sandbox Code Playgroud)

(注意,这mapValues是懒惰的,这意味着每次使用时都会重新计算结果.一般来说,这不是问题,并且有简单的解决方法,但它们与问题无关.)

我们完成了:

scala> invert(Map(1 -> Map(2 -> 3), 10 -> Map(2 -> 4)))
res0: Map[Int,Map[Int,Int]] = Map(2 -> Map(1 -> 3, 10 -> 4))
Run Code Online (Sandbox Code Playgroud)

您也可以跳过辅助方法并将操作链接起来invert.我发现它们更清晰一点,但这是一种风格问题.

或者你可以使用几个折叠:

def invert[A, B, C](nested: Map[A, Map[B, C]]): Map[B, Map[A, C]] =
  nested.foldLeft(Map.empty[B, Map[A, C]]) {
    case (acc, (a, innerMap)) =>
      innerMap.foldLeft(acc) {
        case (innerAcc, (b, c)) =>
          innerAcc.updated(b, innerAcc.getOrElse(b, Map.empty).updated(a, c))
      }
  }
Run Code Online (Sandbox Code Playgroud)

哪个做同样的事情:

scala> invert(Map(1 -> Map(2 -> 3), 10 -> Map(2 -> 4)))
res1: Map[Int,Map[Int,Int]] = Map(2 -> Map(1 -> 3, 10 -> 4))
Run Code Online (Sandbox Code Playgroud)

foldLeft版本具有更直接命令式版本的更多形状 - 我们(功能上)迭代外部和内部地图的键值对并构建结果.在我的脑海中,我猜它也有点效率,但我不确定,并且不太重要,所以我建议选择你个人觉得更清楚的那个.