据我所知,aggregate
是对fold
的概括,而reduce
又概括为combineByKey
。
类似aggregateByKey
是foldByKey
的概括,而reduceByKey
反过来是fold
的概括,而后者又是reduce
的概括。
但是,我很难找到这七种方法中的每种方法的简单例子,而这些方法又只能由它们表达而不是它们不那么通用的版本。例如,我发现http://blog.madhukaraphatak.com/spark-rdd-fold/给出了fold
的示例,但我也能够在同样的情况下使用reduce
。
到目前为止我发现了什么:
fold
的函数只需要是关联的,而reduce
的函数必须是可交换的:https://stackoverflow.com/a/25158790/4533188(但是,我仍然不知道任何好的简单例子。)而在https://stackoverflow.com/a/26635928/4533188中,我读到折叠需要两个属性来保持...... fold
超过subprocess.run('export FOO=BAR', shell=True)
),因为"添加所有元素并添加3"并使用3作为零值,但这会产生误导,因为每个分区都会添加3个,而不只是一次。根据我的理解,这根本不是subprocess.run()
的目的 - 它不是一个特征,而是实现它以实现非交换功能的必要性。这七种方法的简单例子是什么?
答案 0 :(得分:10)
让我们在逻辑上完成实际需要的工作。
首先,请注意,如果您的集合是无序的,任何上的(二进制)操作集必须是可交换的和关联的,或者您将根据具体情况获得不同的答案(任意的)你每次都要选择。由于reduce
,fold
和aggregate
都使用二元操作,如果您在无序(或被视为无序)的集合上使用这些内容,则所有内容都必须是可交换的和关联的。
reduce
是一个想法的实现,如果你可以采取两件事并将它们变成一件事,你可以将任意长的集合折叠成一个元素。只要你最终将它们全部配对并保持从左到右的顺序不变,那么关联性正是它与你如何配对并不重要的属性,所以这就是' s正是你需要的。
a b c d a b c d a b c d
a # b c d a # b c d a b # c d
(a#b) c # d (a#b) # c d a (b#c) d
(a#b) # (c#d) ((a#b)#c) # d a # ((b#c)#d)
只要操作(此处称为#
)是关联的,上述所有内容都是相同的。没有理由交换左边的东西和右边的东西,所以不的操作需要是可交换的(另外是:a + b == b + a; concat不是:ab!= ba)。
reduce
在数学上很简单,只需要关联操作
减少是有限的,因为它不会对空集合起作用,并且你无法改变类型。如果你按顺序工作,你可以使用一个新类型和旧类型的函数,并生成一个新类型的函数。这是顺序折叠(如果新类型在左侧,则向左折叠,如果在右侧则向右折叠)。关于此处的操作顺序没有选择,因此交换性和关联性以及一切都无关紧要。只有一种方法可以按顺序处理列表。 (如果你希望你的左折和右折总是相同的,那么操作必须是关联的和可交换的,但由于左右折叠通常不会被意外交换,所以这不是'非常重要的是确保。)
当您想要并行工作时,问题就出现了。你不能按顺序浏览你的收藏品;根据定义,这并不平行!所以你必须在多个位置插入新类型!让我们调用我们的折叠操作@
,我们会说新类型在左边。此外,我们会说我们始终使用相同的元素Z
。现在我们可以执行以下任何操作(以及更多):
a b c d a b c d a b c d
Z@a b c d Z@a b Z@c d Z@a Z@b Z@c Z@d
(Z@a) @ b c d (Z@a) @ b (Z@c) @ d
((Z@a)@b) @ c d
(((Z@a)@b)@c) @ d
现在我们收集了一种或多种新类型的东西。 (如果原始集合是空的,我们只需要Z
。)我们知道如何处理它!降低!因此,我们为新类型进行缩减操作(让我们称之为$
,并记住它必须是关联的),然后我们有聚合:
a b c d a b c d a b c d
Z@a b c d Z@a b Z@c d Z@a Z@b Z@c Z@d
(Z@a) @ b c d (Z@a) @ b (Z@c) @ d Z@a $ Z@b Z@c $ Z@d
((Z@a)@b) @ c d ((Z@a)@b) $ ((Z@c)@d) ((Z@a)$(Z@b)) $ ((Z@c)$(Z@d))
(((Z@a)@b)@c) @ d
现在,这些东西看起来都很不一样。我们怎样才能确保它们最终成为一样?没有一个概念可以描述这一点,但Z@
操作必须零 - 且$
和@
必须是同态的,因为我们需要(Z@a)@b == (Z@a)$(Z@b)
。这就是你需要的实际关系(它在技术上与半群同态非常相似)。即使一切都是联想和可交换的,也有各种各样的方式来挑选。例如,如果Z
是双值0.0
而@
实际上是+
,那么Z
就像零一样,@
是关联的和交换。但如果$
实际上是*
,也关联且可交换,那么一切都会出错:
(0.0+2) * (0.0+3) == 2.0 * 3.0 == 6.0
((0.0+2) + 3) == 2.0 + 3 == 5.0
非trival聚合的一个例子是构建一个集合,其中@
是"附加一个元素"运算符和$
是" concat两个集合"操作
aggregate
非常棘手,需要关联缩减操作,加上类似于零的值以及与reduce同形的类似折叠的操作
最重要的是,aggregate
但如果您实际上没有更改类型,则会有一种简化(不太通用的形式)。如果reduce
实际上是Z
并且是实际零,我们可以将其粘贴到我们想要的任何地方并使用reduce。再一次,我们在概念上不需要交换性;我们只是坚持一个或多个z
并减少,我们的z
和@
操作可以是相同的,即我们使用的原始$
在减少
#
如果我们只是从这里删除 a b c d () <- empty
z#a z#b z
z#a (z#b)#c
z#a ((z#b)#c)#d
(z#a)#((z#b)#c)#d
,则效果非常好,实际上相当于z
。但是,它的另一种方式也可以起作用。如果操作if (empty) z else reduce
也是可交换的,则和 #
实际上不是零,而只是占据z
的固定点(意思是#
,但z#z == z
不一定只是z#a
),那么你可以运行相同的东西,并且由于传播让你可以切换顺序,所以你在概念上可以重新排序所有z&#39 ;在一起开始,然后将它们合并在一起。
这是一个平行折叠,它实际上是一个与顺序折叠完全不同的野兽。
(请注意,a
和fold
都不是aggregate
的严格概括,即使是无序集合,其中操作必须是关联的和可交换的,因为某些操作没有合理的零!例如,以最短的长度减少字符串就像其最长的字符串一样,这在概念上并不存在,实际上是一种荒谬的内存浪费。)
reduce
需要关联缩减操作以及 零值或还原操作,即可换向加定点值强>
现在,你什么时候 使用并行折叠不是只是reduceOrElse(零)?实际上,从来没有,尽管它们可以存在。例如,如果你有一个戒指,你通常会有我们需要的固定点。例如,10%45 ==(10 * 10)%45,fold
在整数mod 45中是关联的和可交换的。因此,如果我们的集合是数字mod 45,我们可以用&#34折叠;零&#34; *
的{{1}}以及10
的操作,并且我们请将其并行化,同时仍然得到相同的结果。很奇怪。
但请注意, 只需将*
的零和操作插入fold
并获得完全相同的结果,因此aggregate
是 aggregate
的适当概括。
所以,底线:
fold
但需要一个真零或一个固定点,并且合并操作必须是可交换的。关于reduce
内容:它与此相同,只是它仅将其应用于与(可能重复的)键相关联的值集合。
如果Spark实际上需要交换性,而上述分析并不表明需要它,那么可以合理地考虑一个错误(或者至少是对实现的不必要的限制,假设byKey
和map
之类的操作filter
保留有序RDD的顺序。)
答案 1 :(得分:1)
传递给fold的函数必须是关联的,而reduce的函数必须是可交换的。
这是不正确的。关于RDD的fold
要求该函数也是可交换的。它与Iterable
fold
上的fold
操作不同,the official documentation中对此进行了详细描述:
这与针对非分布式实现的折叠操作略有不同 Scala等函数式语言中的集合。
此
fold
操作可能适用于 单独分区,然后将这些结果折叠到最终结果中,而不是 以某种定义的顺序依次将fold
应用于每个元素。对于功能 这不是可交换的,结果可能与应用于a的折叠的结果不同 非分布式收集。
正如您所看到的,合并部分值的顺序不是合同的一部分,因此用于fold
的函数必须是可交换的。
我读到更通用的方法可以更有效
从技术上讲,应该没有显着差异。对于reduce
vs *byKey
,您可以查看我对reduce() vs. fold() in Apache Spark和Why is the fold action necessary in Spark?
关于combineByKeyWithClassTag
方法,所有方法都使用相同的基本构造createCombiner
来实现,并且可以简化为三个简单的操作:
mergeValue
- 为给定分区创建“零”值mergeCombiners
- 将值合并到累加器UIAlertView
- 合并为每个分区创建的累加器。