【问题标题】:flatmapping a nested Map in scala在 scala 中对嵌套 Map 进行平面映射
【发布时间】:2021-10-23 05:34:23
【问题描述】:

假设我将val someMap = Map[String -> Map[String -> String]] 定义为:

val someMap = 
Map( 
    ("a1" -> Map( ("b1" -> "c1"), ("b2" -> "c2") ) ),
    ("a2" -> Map( ("b3" -> "c3"), ("b4" -> "c4") ) ),
    ("a3" -> Map( ("b5" -> "c5"), ("b6" -> "c6") ) )   
)

我想把它展平成看起来像这样的东西

List(   
    ("a1","b1","c1"),("a1","b2","c2"),
    ("a2","b3","c3"),("a2","b4","c4"),
    ("a3","b5","c5"),("a3","b6","c6")
)

最有效的方法是什么?我正在考虑创建一些辅助函数来处理每个 (a_i -> Map(String,String)) 键值对并返回

def helper(key: String, values: Map[String -> String]): (String,String,String) 
= {val sublist = values.map(x => (key,x._1,x._2))
return sublist
}

然后将此函数平面映射到someMap。但这对我的新手 scala 来说似乎有些不必要,所以我想知道是否有更有效的方法来解析这个 Map。

【问题讨论】:

  • 你的意思是someMap.flatMap { case (a, m) => m.map { case (b, c) => (a, b, c) } }
  • for { (a, m) <- someMap; (b,c) <- m } yield (a,b,c)?

标签: scala hashmap


【解决方案1】:

无需创建辅助函数,只需编写嵌套 lambda:

val result = someMap.flatMap { case (k, v) => v.map { case (k1, v1) => (k, k1, v1) } }

或者

val y = someMap.flatMap(x => x._2.map(y => (x._1, y._1, y._2)))

【讨论】:

  • 也许是flatMap 而不是flatten
  • @LuisMiguelMejíaSuárez 为什么?
  • 因为它的工作原理是错误的:def flatten[B](implicit asIterable: A => scala.collection.IterableOnce[B]): CC[B] - 您将 lambda 作为显式传递的隐式传递,它应该用于将“嵌套映射”转换为“扁平映射”并且意外编译并返回 IterableOnce (而不是地图或列表)。在一般情况下,您不能相信这一点,这是一种滥用接口的 hack。
【解决方案2】:

既然你问的是效率,我能想到的最有效但最实用的方法是使用foldLeftfoldRight

你需要foldRight,因为::反向构造不可变列表。

someMap.foldRight(List.empty[(String, String, String)]) { case ((a, m), acc)  =>
  m.foldRight(acc) {
    case ((b, c), acc) => (a, b, c) :: acc
  }
}

这里,假设Map.iterator.reverse被高效实现,没有中间集合被创建。

或者,您可以使用foldLeft 然后reverse 结果:

someMap.foldLeft(List.empty[(String, String, String)]) { case (acc, (a, m))  =>
  m.foldLeft(acc) {
    case (acc, (b, c)) => (a, b, c) :: acc
  }
}.reverse

这种方式创建了一个单一的中间 List,但您不依赖反向迭代器的实现(foldLeft 使用正向迭代器)。


注意:像someMap.flatMap(x => x._2.map(y => (x._1, y._1, y._2))) 这样的一个衬垫效率较低,因为除了用于保存flatMap 的中间结果的临时缓冲区之外,它们还会为每个内部map 创建和丢弃额外的中间集合。


UPD

由于似乎有些混乱,我将澄清我的意思。以下是来自TraversibleLikemapflatMapfoldLeftfoldRight 的实现:

  def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That = {
    def builder = { // extracted to keep method size under 35 bytes, so that it can be JIT-inlined
      val b = bf(repr)
      b.sizeHint(this)
      b
    }
    val b = builder
    for (x <- this) b += f(x)
    b.result
  }

  def flatMap[B, That](f: A => GenTraversableOnce[B])(implicit bf: CanBuildFrom[Repr, B, That]): That = {
    def builder = bf(repr) // extracted to keep method size under 35 bytes, so that it can be JIT-inlined
    val b = builder
    for (x <- this) b ++= f(x).seq
    b.result
  }

  def foldLeft[B](z: B)(op: (B, A) => B): B = {
    var result = z
    this foreach (x => result = op(result, x))
    result
  }

  def foldRight[B](z: B)(op: (A, B) => B): B =
    reversed.foldLeft(z)((x, y) => op(y, x))

很明显mapflatMap 使用对应的builder 创建中间缓冲区,而foldLeftfoldRight 重复使用相同的用户提供的累加器对象,并且只使用迭代器。

【讨论】:

  • @LuisMiguelMejíaSuárez 感谢您指出List.empty。虽然,在这里,因为它只被调用一次,所以没关系。但是,关于flatMapfoldRight,我不同意你的看法。 foldRight 使用迭代器,根本不创建任何中间集合,而 flatMap 为其结果创建缓冲区,并且每个内部 map 调用创建自己的缓冲区。
  • 检查这个:someMap.iterator.flatMap(x =&gt; x._2.iterator.map(y =&gt; (x._1, y._1, y._2))).toList - 虽然你对中间集合的看法是对的,但我误解了你的意思。
  • @LuisMiguelMejíaSuárez,明白了。 toList 这里仍然使用中间可变缓冲区,如果 iterator.reversed 的特定 Map 实现与前向迭代器一样有效,那么 foldRight 将更有效,但我同意,如果你的方法实际上是相同的,如果不比foldLeft + reverse好。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-04-01
  • 1970-01-01
  • 2020-01-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多