【问题标题】:How to build an RDD where each element equals a sum of preceding input elements?如何构建一个RDD,其中每个元素等于前面输入元素的总和?
【发布时间】:2015-03-26 00:07:45
【问题描述】:

我有一个 RDD 集合中的数字列表。从这个列表中,我需要创建另一个 RDD 列表,其中每个元素等于它之前所有元素的总和。如何在 Spark 中构建这样的 RDD?

以下 Scala 代码说明了我试图在 Spark 中实现的目标:

object Test {

  def main(args: Array[String]) {

    val lst: List[Float] = List(1, 2, 3)
    val result = sum(List(), 0, lst)
    println(result)
  }

  def sum(acc: List[Float], runningSum: Float, list: List[Float]): List[Float] = {
    list match {
      case List() => acc.reverse
      case List(x, _*) => {
        val newSum = runningSum + x
        sum(newSum :: acc, newSum, list.tail)
      }

    }
  }

运行此结果:

List(1.0, 3.0, 6.0)

此示例的等效 Spark 代码是什么?

【问题讨论】:

  • 你不能用 RDD 来做这件事(至少,不能获得 RDD 的优势); RDD 的重点是并行处理其中的一部分,而在您的情况下,新列表的元素取决于原始列表的每个元素。
  • 明白,但我仍然需要从 RDD 中计算这个总和。在这种情况下你会怎么做?
  • 理想情况下,.collect() RDD 并在本地计算总和,然后在需要时再次 sc.parallelize。如果它太大而无法放入内存,我所能想到的就是找出某种方法来知道哪些元素比其他元素“更早”(也许通过给每个元素一个索引),@ 987654325@ RDD 本身以获得所有可能元素对,filter 取出第二个条目在第一个条目“之后”的那些对,然后 aggregateByKey 进行求和。它会起作用,但肯定会很慢。

标签: scala apache-spark


【解决方案1】:

正如其他人提到的那样,您的问题是 RDD 没有排序的概念,因此您必须提供“所有在它之前的元素”的定义作为输入的一部分,例如,作为行:

1 1.0 2 3.0 3 7.0

当然,我假设您无法将列表加载到驱动程序的内存中。鉴于您的问题类型,恐怕没有简单的解决方案,但我会尽力概述一个。

您的问题基本上需要顺序读取,但您可以分小块进行。例如,您可以通过调用 map(x => (x._1%10000, x._1, x._2)) 将列表划分为 10000 个项目的块。您可以根据内存限制调整的实际数字。

然后,您可以使用groupByKey() 方法将给定子列表中的所有项目放在一起。这会将整个子列表放在内存中的给定执行程序上。

然后,您可以像在示例中使用 sum() 方法一样对生成的子列表进行排序和计算。这会给你一个包含桶顺序、总和和列表的元组:

(1,3270,List(17, 12, 15)

您可以将此 RDD 映射到另外两个 RDD,首先,仅映射到 (1,3271) 以获取子列表的总和。其次,映射到 (1, 17), (1,12)... 元组,可以持久化。

您可以收集的第一张地图(它非常小,每 10000 个值只有 1 个元组)。一旦在驱动程序上,排序和求和以获得所有先前子列表的总和。把它作为一个新的 RDD 放回去,然后你可以用上面的第二个 RDD 做一个join。然后你可以将桶的总和(全局)添加到子列表(本地)的总和中,然后你可以将所有结果存储回磁盘。

总的来说,我所描述的是一种分而治之的方法。基本上将您的列表转到列表列表中,其中每个较小的列表都适合内存。

【讨论】:

    【解决方案2】:

    所以这在 Spark 中有点棘手,但一种选择是执行 mapPartitionsWithIndex 并计算每个分区的总和,然后将结果收集回驱动程序。然后,您可以使用它并执行另一个 mapPartitionsWithIndex 并使用先前分区的总和作为起点来计算总和,然后从这一点开始继续添加元素。这会有点慢,但我们不需要依赖收集或将所有元素带回驱动程序。

    例如

    val c = rdd.mapPartitionsWithIndex((f, i) => List((f, i.sum)).iterator).collect()
    val sumRdd = rdd.mapPartitionsWithIndex{(f, i) => 
      val previousElem = c.filter(_._1 < f).map(_._2).sum
      var e = previousElem
      i.map{ ne =>
        val ret = e
        e = e + ne
        ret
       }}
    

    【讨论】:

    • 你仍然使用collect...无论如何,通过阅读这段代码,很难理解解决方案真正试图实现的目标。恐怕功能代码清晰的所有优点都失去了。
    • 我们仍然使用收集,但是我们收集的元素数量要少得多返回给驱动程序。此解决方案的好处是处理的数据集太大而无法在单台机器上容纳,这可能就是您在 Spark 中工作的原因,但它肯定不太清楚。
    猜你喜欢
    • 1970-01-01
    • 2017-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-15
    • 2022-01-08
    • 2015-12-02
    • 1970-01-01
    相关资源
    最近更新 更多