例如,假设我有
val list: List[(String, Double)]
带值
"04-03-1985", 1.5
"05-03-1985", 2.4
"05-03-1985", 1.3
我怎样才能制作新的名单
"04-03-1985", 1.5
"05-03-1985", 3.7
答案 0 :(得分:28)
这是一个单行。它不是特别易读,除非真正内化这些高阶函数的类型。
val s = Seq(("04-03-1985" -> 1.5),
("05-03-1985" -> 2.4),
("05-03-1985" -> 1.3))
s.groupBy(_._1).mapValues(_.map(_._2).sum)
// returns: Map(04-03-1985 -> 1.5, 05-03-1985 -> 3.7)
另一种方法是使用fold,
逐个添加键值对s.foldLeft(Map[String, Double]()) { case (m, (k, v)) =>
m + (k -> (v + m.getOrElse(k, 0d)))
}
在我看来,最容易理解的是理解能力
var m = Map[String, Double]()
for ((k, v) <- s) {
m += k -> (v + m.getOrElse(k, 0d))
}
使用Scalaz的Map的monoid类型类可能可以做得更好。
请注意,您可以使用Map[K, V]
和Seq[(K, V)]
方法在toSeq
和toMap
之间进行转换。
更新。在思考了一些之后,我认为自然抽象将是一种类型的“多图”转换,
def seqToMultimap[A, B](s: Seq[(A, B)]): Map[A, Seq[B]]
在个人图书馆中使用适当的隐式扩展,然后可以写:
s.toMultimap.mapValues(_.sum)
在我看来,这是最清楚的!
答案 1 :(得分:14)
使用Scalaz还有另一种可能性。
关键是要注意,如果M
是Monoid
,那么Map[T, M]
也是Monoid
。这意味着如果我有2个地图,m1
和m2
我可以添加它们,以便对于每个相似的键,元素将被添加到一起。
例如,Map[String, List[String]]
是一个Monoid,因为List[String]
是Monoid
。因此,在范围内给出适当的Monoid
实例,我应该能够:
val m1 = Map("a" -> List(1), "b" -> List(3))
val m2 = Map("a" -> List(2))
// |+| "adds" two elements of a Monoid together in Scalaz
m1 |+| m2 === Map("a" -> List(1, 2), "b" -> List(3))
对于您的问题,我们可以看到Map[String, Int]
是Monoid
,因为Monoid
类型有Int
个实例。让我们导入它:
implicit val mapMonoid = MapMonoid[String, Int]
然后,我需要一个函数reduceMonoid
,它将Traversable
和“{”添加“其元素”Monoid
。我只是在这里写下reduceMonoid
定义,为了完整实施,请参阅Essence of the Iterator Pattern上的帖子:
// T is a "Traversable"
def reduce[A, M : Monoid](reducer: A => M): T[A] => M
这两个定义在当前的Scalaz库中不存在,但它们并不难添加(基于现有的Monoid
和Traverse
类型类)。一旦我们拥有它们,您问题的解决方案就非常简单:
val s = Seq(("04-03-1985" -> 1.5),
("05-03-1985" -> 2.4),
("05-03-1985" -> 1.3))
// we just put each pair in its own map and we let the Monoid instance
// "add" the maps together
s.reduceMonoid(Map(_)) === Map("04-03-1985" -> 1.5,
"05-03-1985" -> 3.7)
如果你觉得上面的代码有点模糊(但真的很简洁,对吧?),我建议你查看github project for the EIP post并使用它。 One example显示了您问题的解决方案:
"I can build a map String->Int" >> {
val map1 = List("a" -> 1, "a" -> 2, "b" -> 3, "c" -> 4, "b" -> 5)
implicit val mapMonoid = MapMonoid[String, Int]
map1.reduceMonoid(Map(_)) must_== Map("a" -> 3, "b" -> 8, "c" -> 4)
}
答案 2 :(得分:9)
我一直使用Kipton的答案中的那种模式s.groupBy(_._1).mapValues(_.map(_._2).sum)
。它直接翻译我的思考过程,但遗憾的是并不总是很容易阅读。我发现尽可能使用案例类会让事情变得更好:
case class Data(date: String, amount: Double)
val t = s.map(t => (Data.apply _).tupled(t))
// List(Data(04-03-1985,1.5), Data(05-03-1985,2.4), Data(05-03-1985,1.3))
然后变成:
t.groupBy(_.date).mapValues{ group => group.map(_.amount).sum }
// Map(04-03-1985-> 1.5, 05-03-1985 -> 3.7)
我认为它比 fold 或 版本更具可读性。
答案 3 :(得分:3)
val s = List ( "04-03-1985" -> 1.5, "05-03-1985" -> 2.4, "05-03-1985" -> 1.3)
for { (key, xs) <- s.groupBy(_._1)
x = xs.map(_._2).sum
} yield (key, x)
答案 4 :(得分:0)
从Scala 2.13
开始,您可以使用groupMapReduce
方法,该方法(如其名称所示)等效于groupBy
后跟mapValues
和{{1} }步骤:
reduce
此:
// val l = List(("04-03-1985", 1.5), ("05-03-1985", 2.4), ("05-03-1985", 1.3))
l.groupMapReduce(_._1)(_._2)(_ + _).toList
// List(("04-03-1985", 1.5), ("05-03-1985", 3.7))
的元组的第一部分(group
)(组 MapReduce的组部分)
_._1
将每个分组的元组复制到第二部分(map
)(映射组 Map Reduce)
_._2
)中的值 reduce
进行求和(减少groupMap Reduce 的一部分)。
这是one-pass version可以翻译的内容:
_ + _