繁体   English   中英

聚合scala函数说明

[英]Explanation of the aggregate scala function

我还没有理解聚合函数:

例如,具有:

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._20

提前致谢。

首先感谢 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 个部分:

  1. 累加器的初始值:此处为元组(0,0)
  2. seqop :它的工作原理类似于 foldLeft,初始值为 0
  3. combop :它结合了通过并行化生成的结果(这部分我很难理解)

让我们独立理解所有 3 个部分:

第 1 部分:初始元组 (0,0)

Aggregate() 从累加器 x 的初始值开始,这里是 (0,0)。 第一个元组 x._1 最初为 0 用于计算总和,第二个元组 x._2 用于计算列表中元素的总数。

第二部分:(x, y) => (x._1 + y, x._2 + 1)

如果您知道 foldLeft 在 Scala 中是如何工作的,那么这部分应该很容易理解。 上面的函数就像我们 List(1,2,3,4...10) 上的 foldLeft 一样。

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 - 敬请期待:-)

第 3 部分:(x,y) =>(x._1 + y._1, x._2 + y._2)

那么这第三部分是combOp,它结合了并行化过程中不同线程生成的结果,请记住我们在代码中使用了'par'来启用列表的并行计算:

列表(1,2,3,4,5,6,7,8,9,10).par.aggregate(....)

Apache spark 有效地使用聚合函数来进行 RDD 的并行计算。

假设我们的 List(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 的工作是计算列表中所有元素的总和和列表总数,而 combine 函数的工作是合并并行化期间生成的不同部分结果。

我希望上面的解释能帮助你理解聚合()。

文档

def aggregate[B](z: ⇒ B)(seqop: (B, A) ⇒ B, combop: (B, B) ⇒ B): B

聚合将运算符应用于后续元素的结果。

这是折叠和减少的更一般形式。 它具有相似的语义,但不要求结果是元素类型的超类型。 它依次遍历不同分区中的元素,使用seqop更新结果,然后对来自不同分区的结果应用combo。 此操作的实现可能对任意数量的集合分区进行操作,因此可以调用任意次数的组合。

例如,人们可能想要处理一些元素,然后生成一个 Set。 在这种情况下,seqop 将处理一个元素并将其附加到列表中,而 combop 会将来自不同分区的两个列表连接在一起。 初始值 z 将是一个空集。

pc.aggregate(Set[Int]())(_ += process(_), _ ++ _)

另一个例子是从一组双精度数中计算几何平均值(为此通常需要大双精度数)。 B 累积结果的类型 z 分区累积结果的初始值 - 这通常是 seqop 运算符的中性元素(例如,Nil 表示列表连接或 0 表示求和),并且可以多次评估 seqop 运算符用于在分区组合中累积结果 关联运算符用于组合来自不同分区的结果

在您的示例中, BTuple2[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

聚合采用 3 个参数:种子值、计算函数和组合函数。

它所做的基本上是将集合拆分为多个线程,使用计算函数计算部分结果,然后使用组合函数组合所有这些部分结果。

据我所知,您的示例函数将返回一对 (a, b),其中 a 是列表中值的总和,b 是列表中值的数量。 事实上,(21, 6)。

这是如何运作的? 种子值是 (0,0) 对。 对于一个空列表,我们有一个总和为 0 并且项目数为 0,所以这是正确的。

您的计算函数采用 (Int, Int) 对 x(您的部分结果)和 Int y(列表中的下一个值)。 这是你的:

(x, y) => (x._1 + y, x._2 + 1)

实际上,我们想要的结果是将 x 的左侧元素(累加器)增加 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 类型的结果。 在第二个函数中,您的两个参数都与您的种子值类型相同。

希望现在更清楚了!

添加到 Rashmit 答案。

  • 仅当以并行模式处理集合时才调用 ComboOp。

见下面的例子:

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 : Aggregate-1,Aggregate-2,Aggregate-3,Aggregate-45,Aggregate-6,Aggregate-7,Aggregate-8,Aggregate-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)

}

操作:聚合 12345678910

在上面的例子中,只有在并行操作集合时才会调用组合器操作

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

因此,对于每次迭代,我们采用累加器并将 x 的元素添加到累加器的位置 1 以获得总和,并将累加器的位置 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))

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM