我还没有理解聚合函数:
例如,拥有:
val x = List(1,2,3,4,5,6)
val y = x.par.aggregate((0, 0))((x, y) => (x._1 + y, x._2 + 1), (x,y) => (x._1 + y._1, x._2 + y._2))
结果将是:(21,6)
好吧,我认为(x,y) => (x._1 + y._1, x._2 + y._2)
是为了并行得到结果,例如它将是(1 + 2,1 + 1),依此类推。
但是这一部分让我感到困惑:
(x, y) => (x._1 + y, x._2 + 1)
为什么x._1 + y
?这里x._2
是0
?
提前致谢。
答案 0 :(得分:35)
首先感谢Diego的回复,这有助于我在理解aggregate()函数时连接点。
让我承认我昨晚无法入睡,因为我无法理解聚合()在内部的运作方式,我今晚肯定会有良好的睡眠: - )
让我们开始理解它
val result = List(1,2,3,4,5,6,7,8,9,10).par.aggregate((0, 0))
(
(x, y) => (x._1 + y, x._2 + 1),
(x,y) =>(x._1 + y._1, x._2 + y._2)
)
结果:(Int,Int)=(55,10)
让我们独立理解所有3个部分:
Aggregate()以accumulators x的初始值开始,此处为(0,0)。第一个元组x._1(最初为0)用于计算总和,第二个元组x._2用于计算列表中元素的总数。
如果您知道foldLeft如何在scala中工作,那么应该很容易理解这一部分。上面的函数就像我们列表中的foldLeft(1,2,3,4 ... 10)一样。
Iteration# (x._1 + y, x._2 + 1)
1 (0+1, 0+1)
2 (1+2, 1+1)
3 (3+3, 2+1)
4 (6+4, 3+1)
. ....
. ....
10 (45+10, 9+1)
因此,在所有10次迭代之后,你将得到结果(55,10)。 如果您理解这一部分剩下的很容易,但对我而言,理解所有必需的计算是否已经完成然后使用第二部分即compop是最困难的部分 - 请继续关注: - )
第三部分是combOp,它结合了并行化过程中不同线程生成的结果,记得我们使用过' par'在我们的代码中启用列表的并行计算:
列表(1,2,3,4,5,6,7,8,9,10).par.aggregate(....)
Apache spark实际上是使用聚合函数来进行RDD的并行计算。
假设我们的列表(1,2,3,4,5,6,7,8,9,10)由3个并行线程计算。这里每个线程都在处理部分列表,然后我们的aggregate()combOp将使用以下代码组合每个线程的计算结果:
(x,y) =>(x._1 + y._1, x._2 + y._2)
原始清单:清单(1,2,3,4,5,6,7,8,9,10)
Thread1开始计算部分列表说(1,2,3,4),Thread2计算(5,6,7,8)和Thread3计算部分列表说(9,10)
在计算结束时,Thread-1结果为(10,4),Thread-2结果为(26,4),Thread-3结果为(19,2)。
在并行计算结束时,我们将((10,4),(26,4),(19,2))
Iteration# (x._1 + y._1, x._2 + y._2)
1 (0+10, 0+4)
2 (10+26, 4+4)
3 (36+19, 8+2)
是(55,10)。
最后让我重新尝试seqOp作业是计算列表的所有元素和列表总数的总和,而组合函数的工作是组合并行化过程中生成的不同部分结果。
我希望以上解释可以帮助您理解aggregate()。
答案 1 :(得分:21)
def aggregate[B](z: ⇒ B)(seqop: (B, A) ⇒ B, combop: (B, B) ⇒ B): B
将运算符的结果聚合到后续元素。
这是折叠和减少的更一般形式。它有类似的 语义,但不要求结果是超类型 元素类型。它遍历不同分区中的元素 顺序,使用seqop更新结果,然后应用 结合不同分区的结果。实施 此操作可以对任意数量的集合进行操作 分区,因此可以任意调用combop。
例如,人们可能想要处理一些元素然后生成 一套。在这种情况下,seqop将处理一个元素并将其追加 列表,而combop将连接两个不同的列表 分区在一起。初始值z将是一个空集。
pc.aggregate(Set[Int]())(_ += process(_), _ ++ _)
另一个例子是 从一组双打中计算几何平均数(一个会 通常需要大双打)。 B累计的类型 结果z的累计结果的初始值 分区 - 这通常是seqop的中性元素 运算符(例如,列表连接为Nil,或者为求和为0)和可以 不止一次评估一个运算符用于累积的seqop 在分区组合中的结果用于的关联运算符 合并来自不同分区的结果
在您的示例中B
是Tuple2[Int, Int]
。然后,方法seqop
从列表中获取单个元素,范围为y
,并将汇总B
更新为(x._1 + y, x._2 + 1)
。所以它增加了元组中的第二个元素。这有效地将元素的总和放入元组的第一个元素中,将元素的数量放入元组的第二个元素中。
方法combop
然后从每个并行执行线程获取结果并组合它们。添加组合提供的结果与按顺序在列表中运行的结果相同。
使用B
作为元组可能是令人困惑的一部分。您可以将问题分解为两个子问题,以更好地了解这是在做什么。 res0
是结果元组中的第一个元素,res1
是结果元组中的第二个元素。
// Sums all elements in parallel.
scala> x.par.aggregate(0)((x, y) => x + y, (x, y) => x + y)
res0: Int = 21
// Counts all elements in parallel.
scala> x.par.aggregate(0)((x, y) => x + 1, (x, y) => x + y)
res1: Int = 6
答案 2 :(得分:8)
聚合需要3个参数:种子值,计算函数和组合函数。
它的作用基本上是在多个线程中分割集合,使用计算函数计算部分结果,然后使用组合函数组合所有这些部分结果。
据我所知,你的示例函数将返回一对(a,b),其中a是列表中值的总和,b是列表中值的数量。的确,(21,6)。
这是如何工作的?种子值是(0,0)对。对于空列表,我们总和为0,项目数为0,所以这是正确的。
你的计算函数取一个(Int,Int)对x,它是你的部分结果,和一个Int y,它是列表中的下一个值。这是你的:
(x, y) => (x._1 + y, x._2 + 1)
实际上,我们想要的结果是将y的左元素(累加器)增加y,并将x(计数器)的右元素增加1。
你的组合函数采用(Int,Int)对x和(Int,Int)对y,它们是来自不同并行计算的两个部分结果,并将它们组合在一起:
(x,y) => (x._1 + y._1, x._2 + y._2)
实际上,我们独立地将对的左侧部分和对的右侧部分相加。
你的困惑来自于第一个函数中的x和y与第二个函数中的x和y不同。在第一个函数中,您有种子值类型的x和集合元素类型的y,并返回x类型的结果。在第二个函数中,您的两个参数都与种子值的类型相同。
希望它现在更清晰!
答案 3 :(得分:0)
添加到Rashmit答案。
见下面的例子:
val listP:ParSeq [Int] = List(1,2,3,4,5,6,7,8,9,10).par
val aggregateOp1 = listP.aggregate[String]("Aggregate-")((a, b) => a + b, (s1, s2) => {
println("Combiner called , if collections is processed parallel mode")
s1 + "," + s2
})
println(aggregateOp1)
OP:聚合-1,聚合-2,聚合-3,聚合-45,聚合-6,聚合-7,聚合-8,聚合-910
val list: Seq[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val aggregateOp2 = list.aggregate[String]("Aggregate-")((a, b) => a + b, (s1, s2) => {
println("Combiner called , if collections is processed parallel mode")
s1 + "," + s2
})
println(aggregateOp2)
}
OP:Aggregate-12345678910
在上面的例子中,仅当集合并行操作时才调用组合器操作
答案 4 :(得分:0)
def aggregate[B](z: ⇒ B)(seqop: (B, A) ⇒ B, combop: (B, B) ⇒ B): B
稍微降低一点:
聚合(累加器)(累加器+ first_elem_of_list,(seq1,seq2)=> seq1 + seq2)
现在看例子:
val x = List(1,2,3,4,5,6)
val y = x.par.aggregate((0, 0))((x, y) => (x._1 + y, x._2 + 1), (x,y) => (x._1 + y._1, x._2 + y._2))
在这里:
累加器为(0,0)
定义的列表是x
x的第一个元素是1
因此,对于每次迭代,我们将累加器加到累加器的位置1上以得到x的元素,以求和,累加器的位置2加1以得到计数。 (y是列表的元素)
(x, y) => (x._1 + y, x._2 + 1)
现在,由于这是并行实现,因此第一部分将产生一个元组列表,如(3,2)(7,2)和(11,2)。索引1 =总和,索引2 =用于生成总和的元素数。现在第二部分起作用了。每个序列的元素都以减少的方式添加。
(x,y) =>(x._1 + y._1, x._2 + y._2)
使用更有意义的变量进行重写:
val arr = Array(1,2,3,4,5,6)
arr.par.aggregate((0,0))((accumulator,list_elem)=>(accumulator._1+list_elem, accumulator._2+1), (seq1, seq2)=> (seq1._1+seq2._1, seq1._2+seq2._2))