Scala 如何按键对元组列表求和

Scala 如何按键对元组列表求和,scala,scala-collections,Scala,Scala Collections,我编写了以下代码以按键求和: // ("A", 1) ("A", 4) // ("B", 2) --> ("B", 2) // ("A", 3) def sumByKeys[A](tuples: List[(A, Long)]) : List[(A, Long)] = { tuples.groupBy(_._1).mapValues(_.map(_._2).sum).toList } 有更好的办法吗 更新:在末尾添加.toList我想这是最简单的不可变

我编写了以下代码以按键求和:

  // ("A", 1)     ("A", 4)
  // ("B", 2) --> ("B", 2)
  // ("A", 3)
  def sumByKeys[A](tuples: List[(A, Long)]) : List[(A, Long)] = {
    tuples.groupBy(_._1).mapValues(_.map(_._2).sum).toList
  }
有更好的办法吗


更新:在末尾添加
.toList

我想这是最简单的不可变形式,不需要在scala上使用任何额外的框架

UPD实际上忘记了最后的收费表。由于mapValues视图返回类型的不同,这使得性能方面的情况完全不同

你可以试试foldLeft,tailrec,一些可变的,它们有更好的性能

import annotation.tailrec

@tailrec
final def tailSum[A](tuples: List[(A, Long)], acc: Map[A, Long] = Map.empty[A, Long]): List[(A, Long)] = tuples match {
  case (k, v) :: tail => tailSum(tail, acc + (k -> (v + acc.get(k).getOrElse(0L))))
  case Nil => acc.toList
}

def foldLeftSum[A](tuples: List[(A, Long)]) = tuples.foldLeft(Map.empty[A, Long])({
  case (acc, (k, v)) => acc + (k -> (v + acc.get(k).getOrElse(0L)))
}).toList

def mutableSum[A](tuples: List[(A, Long)])  = {
  val m = scala.collection.mutable.Map.empty[A, Long].withDefault(_ => 0L)
  for ((k, v) <- tuples) m += (k -> (v + m(k)))
  m.toList
}

我所能想到的最好方法可以让您的性能稍好一点,并节省两个字符:

def sumByKeys[A](tuples: List[(A, Long)]) : List[(A, Long)] = {
  tuples.groupBy(_._1).mapValues(_.unzip._2.sum)
}
在我的带有Bask.ws基准测试的机器上,没有
解压
,需要11毫秒而不是13毫秒


编辑:事实上,我认为性能必须相同。。。不知道这些2ms来自何处

与您的解决方案非常相似:

def sumByKeys[A](tuples: List[(A, Long)]): List[(A, Long)] =
  tuples groupBy (_._1) map { case (k, v) => (k, v.map(_._2).sum) } toList

val l: List[(String, Long)] = List(("A", 1), ("B", 2), ("A", 3))

sumByKeys(l)
// result:
// List[(String, Long)] = List((A,4), (B,2))
有趣的是,在您的解决方案中,您使用了
defmapvalues[C](f:(B)⇒ C) :Map[A,C]
根据文档,它有“惰性”评估:“通过对每个检索到的值应用函数来转换此映射。”

另一方面
defmap[B](f:(A)⇒ B) :Map[B]
将构建新集合:“通过将函数应用于此不可变映射的所有元素来构建新集合。”

因此,根据您的需要,您可以懒洋洋地评估大地图,也可以急切地评估小地图。

使用reduce

def sumByKeys[A](tuples: List[(A, Long)]): List[(A, Long)] = {
  tuples groupBy(_._1) map { _._2 reduce { (a,b) => (a._1, a._2+b._2) } } toList
}
缩写

def sumByKeys[A](tuples: List[(A, Long)]): List[(A, Long)] = {
  tuples groupBy(_._1) map { case(k,v) => v reduce { (a,b) => (a._1, a._2+b._2) } } toList
}

我认为这是最好的,我不知道
sum
方法,可能会使用
foldLeft
来代替。这是Scalaz中
Map
半群的行为,因此它允许您编写
tuples.Map(Map()).suml.toList
,这并不一定更好。还要注意的是,您似乎在末尾缺少了一个
toList
。还要注意的是,您可以使用
foldMap
,而不是
map
+
suml
tuples.foldMap{map()}.toList
,但是这两种
scalaz
方法都非常低效-您将得到
N
Map
合并。还要注意的是
mapValues
返回一个视图,该视图将在每个
get
上再次应用转换。如果没有
sumByKeys
上的
toList
,您只会得到
mapValues
,这意味着您没有完成其他实现所做的大量工作。哇。你刚刚睁开了我的眼睛!我猜这是因为地图价值观?这使得完全不同的图片
mapValues
很奇怪,它实际上返回了一个视图,但在返回类型中没有指出。11毫秒太小了。最好至少增加5次基准参数(平均循环计数和元组计数)。这将提供更多恒定和可区分的值。不要因为mapValues而忘记toList
def sumByKeys[A](tuples: List[(A, Long)]): List[(A, Long)] = {
  tuples groupBy(_._1) map { case(k,v) => v reduce { (a,b) => (a._1, a._2+b._2) } } toList
}