【问题标题】:Combine values with same keys in Scala在 Scala 中将值与相同的键组合
【发布时间】:2014-03-06 09:24:04
【问题描述】:

我目前有 2 个列表 List('a','b','a') 和 List(45,65,12),第二个列表中的更多元素和元素通过键链接到第一个列表中的元素价值关系。我想通过添加相应的值来组合具有相同键的元素并创建一个应该看起来像 Map('a'-> 57,'b'->65) as 57 = 45 + 12 的映射。

我目前已将其实现为

val keys = List('a','b','a')
val values = List(45,65,12)
val finalMap:Map(char:Int) =
  scala.collection.mutable.Map().withDefaultValue(0)
  0 until keys.length map (w => finalMap(keys(w)) += values(w))

我觉得应该有比我做的更好的方法(功能方法)来创建所需的地图。我怎样才能改进我的代码并以更实用的方式做同样的事情?

【问题讨论】:

标签: scala functional-programming scala-2.8


【解决方案1】:
val m = keys.zip(values).groupBy(_._1).mapValues(l => l.map(_._2).sum)

编辑:为了解释代码的工作原理,zip 将两个输入序列的对应元素配对,所以

keys.zip(values) = List((a, 45), (b, 65), (a, 12))

现在您想将具有相同第一个元素的所有对组合在一起。这可以通过groupBy 来完成:

keys.zip(values).groupBy(_._1) = Map((a, List((a, 45), (a, 12))), (b, List((b, 65))))

groupBy 返回一个映射,其键是要分组的类型,其值是输入序列中具有相同键的元素的列表。

此映射的键是keys 中的字符,值是来自keysvalues 的关联对列表。由于键是您想要在输出映射中的键,因此您只需将值从 List[Char, Int] 转换为 List[Int]

您可以通过将列表中每对的第二个元素的值相加来做到这一点。

您可以使用 map 例如从每对中提取值

List((a, 45), (a, 12)).map(_._2) = List(45,12)

现在您可以使用sum 对这些值求和:

List(45, 12).sum = 57

您可以使用mapValues 将此转换应用于地图中的所有值,以获得您想要的结果。

【讨论】:

  • 嗨@Lee - 解释你的解决方案总是一个好主意,这样总 n00bs 可以从你的好主意中学到一两件事...... :) 即,不要只发布代码 -简要解释一下为什么这样做更好以及它是如何工作的等:)
  • @TarynEast - 抱歉,我在那里有点懒惰。我已经添加了解释。
  • 一些小的调整:(keys,values).zipped.groupBy(_._1).mapValues(_.map(_._2).sum)
【解决方案2】:

我打算为 Lee 的第一个版本 +1,但 mapValues 是一个视图,而 ell 在我看来总是一个。只是不要显得小气。

scala> (keys zip values) groupBy (_._1) map { case (k,v) => (k, (v map (_._2)).sum) }
res0: scala.collection.immutable.Map[Char,Int] = Map(b -> 65, a -> 57)

嘿,折叠的答案消失了。 SO不能眨眼,动作太快了。

无论如何我都会为 Lee 的打字速度 +1。

编辑:解释mapValues 是一个视图:

scala> keys.zip(values).groupBy(_._1).mapValues(l => l.map { v =>
     | println("OK mapping")
     | v._2
     | }.sum)
OK mapping
OK mapping
OK mapping
res2: scala.collection.immutable.Map[Char,Int] = Map(b -> 65, a -> 57)

scala> res2('a')   // recomputes
OK mapping
OK mapping
res4: Int = 57

有时这正是您想要的,但往往令人惊讶。我认为有一个puzzler

【讨论】:

  • @AmigoNico 你在问“mapValues 是一个视图”吗?正如 scaladoc 所说,该函数在您请求之前不会应用,因此每次查找都会产生最终的映射和总和。
【解决方案3】:

您实际上走上了合理有效的功能解决方案的正确轨道。如果我们只是切换到不可变集合并在键值 zip 上使用折叠,我们会得到:

( Map[Char,Int]() /: (keys,values).zipped ) ( (m,kv) =>
  m + ( kv._1 -> ( m.getOrElse( kv._1, 0 ) + kv._2 ) )
)

或者你可以使用withDefaultValue 0,就像你做的那样,如果你希望最终的地图有这个默认值。请注意,.zippedzip 更快,因为它不会创建中间集合。而groupBy 将创建许多其他中间集合。当然,它可能不值得优化,如果是,你可以做得比这更好,但我想告诉你,你的思路并不遥远。

【讨论】:

  • 它是如何工作的,这种语法与我目前看到的不同。
  • 你可以写case (m,(k,v))而不是case (m,kv),让丑陋的下划线神奇地消失:m + ( k -> ( m.getOrElse(k,0) + v ) )
  • 完整的声明会是什么样子,因为( Map[Char,Int]() /: (keys,values).zipped ) ( (m,(k,v)) => m + ( k -> ( m.getOrElse( k, 0 ) + v ) )) 给了我一个错误。
  • Kevin,我希望你是对的,但是foldLeft 接受一个带有两个参数的函数,所以我认为我们不能在这里使用 PartialFunction。没有?
  • “这种语法不同”——实际上,它并不是真正的语法。 /: 只是另一种方法,也称为foldLeftThis article 可以帮助您了解它的作用。基本上,每当你初始化一个可变变量并为集合的每个元素更新它时,你都可以用折叠做同样的事情。
猜你喜欢
  • 2013-02-01
  • 1970-01-01
  • 2023-03-28
  • 1970-01-01
  • 1970-01-01
  • 2020-12-29
  • 1970-01-01
  • 2021-05-14
  • 1970-01-01
相关资源
最近更新 更多