Spark计算单词

时间:2018-05-15 20:54:35

标签: scala apache-spark

我是这样的PairRDD(word,wordCount)。现在,我需要为每个单词计算单词总数中出现次数的百分比,获得这样的最终PairRDD(单词,(wordCount,百分比))。

我尝试过:

val pairs = .... .cache() // (word, wordCount)
val wordsCount = pairs.map(_._2).reduce(_+_)
pairs.map{
        case (word, count) => (word, (count, BigDecimal(count/wordsCount.toDouble * 100).setScale(3, BigDecimal.RoundingMode.HALF_UP).toDouble))
      }

但它看起来效率不高(我是初学者)。有没有更好的方法呢?

1 个答案:

答案 0 :(得分:3)

在计算wordsCount时,map(_._2).reduce(_ + _)更方便地表达aggregate

val pairs = .... .cache() // (word, wordCount)
val wordsCount = pairs.aggregate(0)((sum, pair) => sum + pair._2, _ + _)

第一个参数是初始总计数,应为零。接下来的两个参数位于单独的参数列表中,并为对RDD 的每个成员执行。第二组参数中的第一个((sum, pair) => sum + pair._2,称为序列运算符)将分区中的每个值添加到该分区的当前总和值;而第二个(_ + _,称为组合运算符)组合了来自不同分区的计数。主要好处是,此操作由每个分区并行执行,并保证不会对该数据进行昂贵的重新分区(a.k.a。 shuffling )。 (当需要在群集中的节点之间传输数据时,会发生重新分区,并且由于网络通信而导致数据非常慢。)

虽然aggregate不会影响数据的分区,但您应该注意,如果您的数据已分区,map操作将删除分区方案 - 因为的可能性对RDD 中的密钥可能会被转换更改。如果您对map转换的结果执行任何进一步的转换,这可能会导致后续的重新分区。也就是说,map操作后跟reduce不会导致重新分区,但由于额外的步骤(但不是很大),效果可能略低一些。

如果您担心总字数可能会溢出Int类型,则可以使用BigInt代替:

val wordsCount = pairs.aggregate(BigInt(0))((sum, pair) => sum + pair._2, _ + _)

至于使用单词count和百分比作为值生成对RDD ,您应该使用mapValues而不是map。同样mapValues将保留任何现有的分区方案(因为它保证密钥不会被转换更改),而map将删除它。此外,mapValues更简单,因为您不需要处理值:

pairs.mapValues(c => (c, c * 100.0 / wordsCount))

这应该为您的目的提供足够的准确性。在检索和输出值时,我只会担心舍入和使用BigDecimal。但是,如果您确实需要,您的代码将如下所示:

pairs.mapValues{c =>
  (c, BigDecimal(c * 100.0 / wordsCount).setScale(3, BigDecimal.RoundingMode.HALF_UP).toDouble))
}