聚合函数不这样做(除了它是一个非常通用的函数,并且可以用来这样做)。你想要groupBy。至少接近。当您从Seq[(String, String)] 开始时,您通过获取元组中的第一项(即(String, String) => String),它将返回Map[String, Seq[(String, String)])进行分组。然后,您必须丢弃 Seq[String, String)] 值中的第一个参数。
所以
list.groupBy(_._1).mapValues(_.map(_._2))
你会得到一个Map[String, Seq[(String, String)]。如果您想要 Seq 而不是 Map,请在结果上调用 toSeq。不过,我认为您不能保证生成的 Seq 中的顺序
聚合是一个更难的函数。
首先考虑 reduceLeft 和 reduceRight。
让as 是A 类型元素的非空序列as = Seq(a1, ... an),而f: (A,A) => A 是将A 类型的两个元素组合成一个的某种方式。我会将它记为二元运算符@、a1 @ a2 而不是f(a1, a2)。 as.reduceLeft(@) 将计算 (((a1 @ a2) @ a3)... @ an)。 reduceRight 将括号放在相反的位置,(a1 @ (a2 @... @ an))))。如果@ 恰好是关联的,则不会关心括号。可以将其计算为(a1 @... @ ap) @ (ap+1 @...@an)(两个大括号内也会有括号,但我们不要在意)。然后可以并行执行这两个部分,而 reduceLeft 或 reduceRight 中的嵌套括号强制执行完全顺序计算。但是只有当@ 被认为是关联的并且reduceLeft 方法不知道这一点时才可能进行并行计算。
仍然可以有方法reduce,其调用者将负责确保操作是关联的。然后reduce 将按照它认为合适的方式对调用进行排序,可能并行进行。确实有这样的方法。
然而,各种 reduce 方法都有一个限制。 Seq 的元素只能组合成相同类型的结果:@ 必须是 (A,A) => A。但是一个更普遍的问题是将它们组合成一个B。一个以B 类型的值b 开始,并将其与序列的每个元素组合。运算符@ 是(B,A) => B,计算(((b @ a1) @ a2) ... @ an)。 foldLeft 就是这样做的。 foldRight 做同样的事情,但从 an 开始。在那里,@ 操作没有机会关联。当一个人写b @ a1 @ a2 时,它一定意味着(b @ a1) @ a2,因为(a1 @ a2) 将是错误的类型。所以 foldLeft 和 foldRight 必须是顺序的。
然而,假设每个A 都可以变成B,让我们用! 来写它,a! 的类型是B。此外假设有一个+ 操作(B,B) => B,并且@ 使得b @ a 实际上是b + a!。与其用@组合元素,不如先用!将它们全部转换为B,然后用+组合它们。那将是as.map(!).reduceLeft(+)。如果+ 是关联的,那么可以使用reduce 来完成,而不是顺序的:as.map(!).reduce(+)。可能有一个假设的方法 as.associativeFold(b, !, +)。
Aggregate 非常接近这一点。然而,可能有一种比b+a! 更有效的方法来实现b@a 例如,如果类型B 是List[A],而b@a 是a::b,那么a! 将是a::Nil 和 b1 + b2 将是 b2 ::: b1。 a::b 比 (a::Nil):::b 好得多。要从关联性中受益,但仍使用@,首先将b + a1! + ... + an! 拆分为(b + a1! + ap!) + (ap+1! + ..+ an!),然后返回使用@ 和(b @ a1 @ an) + (ap+1! @ @ an)。一个仍然需要!在 ap+1 上,因为必须从一些 b 开始。 + 仍然是必要的,出现在括号之间。为此,可以将as.associativeFold(!, +) 更改为as.optimizedAssociativeFold(b, !, @, +)。
返回+。 + 是关联的,或者等效地,(B, +) 是一个半群。实际上,编程中使用的大多数半群恰好也是幺半群,即它们在 B 中包含一个中性元素 z(对于 零),因此对于每个 b、z + b = b + z = b。在这种情况下,有意义的! 操作很可能是a! = z @ a。此外,z 是一个中性元素b @ a1 ..@ an = (b + z) @ a1 @ an,即b + (z + a1 @ an)。所以总是可以用 z 开始聚合。如果需要b,则在最后执行b + result。有了所有这些假设,我们可以做 as.aggregate(z, @, +)。这就是aggregate 所做的。 @ 是 seqop 参数(应用于序列 z @ a1 @ a2 @ ap),+ 是 combop(应用于已经部分合并的结果,如在(z + a1@...@ap) + (z + ap+1@...@an))。
总而言之,as.aggregate(z)(seqop, combop) 计算与 as.foldLeft(z)( seqop) 相同的东西,前提是
-
(B, combop, z) 是一个幺半群
seqop(b,a) = combop(b, seqop(z,a))
聚合实现可以使用组合的关联性来对计算进行分组(但是不交换元素,+ 不必是可交换的, ::: 不是)。它可以并行运行它们。
最后,使用aggregate 解决最初的问题留给读者作为练习。提示:使用foldLeft 实现,然后找到满足上述条件的z 和combo。