Scala Spark:通过按键获得TopN

Scala Spark:通过按键获得TopN,scala,apache-spark,Scala,Apache Spark,假设我有一个PairRDD(显然现实生活中的数据要多得多,假设有数百万条记录): 生成一个RDD的最有效方法是什么,每个键的得分最高2分 val top2ByKey = ... res3: Array[(String, Int)] = Array((a,4), (a,3), (b,4), (b,3)) 我认为这应该是相当有效的: 根据OP评论编辑: scores.mapValues(p => (p, p)).reduceByKey((u, v) => { val values

假设我有一个PairRDD(显然现实生活中的数据要多得多,假设有数百万条记录):

生成一个RDD的最有效方法是什么,每个键的得分最高2分

val top2ByKey = ...
res3: Array[(String, Int)] = Array((a,4), (a,3), (b,4), (b,3))

我认为这应该是相当有效的:

根据OP评论编辑:

scores.mapValues(p => (p, p)).reduceByKey((u, v) => {
  val values = List(u._1, u._2, v._1, v._2).sorted(Ordering[Int].reverse).distinct
  if (values.size > 1) (values(0), values(1))
  else (values(0), values(0))
}).collect().foreach(println)

从版本1.4开始,使用MLLib有一种内置的方法来实现这一点:


稍微修改您的输入数据

val scores = sc.parallelize(Array(
      ("a", 1),  
      ("a", 2), 
      ("a", 3), 
      ("b", 3), 
      ("b", 1), 
      ("a", 4),  
      ("b", 4), 
      ("b", 2),
      ("a", 6),
      ("b", 8)
    ))
我将一步一步地解释如何做到这一点:

1.按键分组以创建数组

scores.groupByKey().foreach(println)  
结果:

(b,CompactBuffer(3, 1, 4, 2, 8))
(a,CompactBuffer(1, 2, 3, 4, 6))
(b,List(8, 4, 3, 2, 1))
(a,List(6, 4, 3, 2, 1))
(a,List(6, 4))
(b,List(8, 4))
(b,8)
(b,4)
(a,6)
(a,4)
(a,6)
(a,4)
(b,8)
(b,4)
如您所见,每个值本身就是一个数字数组。CompactBuffer只是经过优化的阵列

2.对于每个键,对值包含的数字列表进行反向排序

scores.groupByKey().map({ case (k, numbers) => k -> numbers.toList.sorted(Ordering[Int].reverse)} ).foreach(println)
结果:

(b,CompactBuffer(3, 1, 4, 2, 8))
(a,CompactBuffer(1, 2, 3, 4, 6))
(b,List(8, 4, 3, 2, 1))
(a,List(6, 4, 3, 2, 1))
(a,List(6, 4))
(b,List(8, 4))
(b,8)
(b,4)
(a,6)
(a,4)
(a,6)
(a,4)
(b,8)
(b,4)
3.只保留第二步的前2个元素,它们将是列表中的前2个分数

scores.groupByKey().map({ case (k, numbers) => k -> numbers.toList.sorted(Ordering[Int].reverse).take(2)} ).foreach(println)
结果:

(b,CompactBuffer(3, 1, 4, 2, 8))
(a,CompactBuffer(1, 2, 3, 4, 6))
(b,List(8, 4, 3, 2, 1))
(a,List(6, 4, 3, 2, 1))
(a,List(6, 4))
(b,List(8, 4))
(b,8)
(b,4)
(a,6)
(a,4)
(a,6)
(a,4)
(b,8)
(b,4)
4.平面图,为每个关键点和最高分数创建新的成对RDD

scores.groupByKey().map({ case (k, numbers) => k -> numbers.toList.sorted(Ordering[Int].reverse).take(2)} ).flatMap({case (k, numbers) => numbers.map(k -> _)}).foreach(println)
结果:

(b,CompactBuffer(3, 1, 4, 2, 8))
(a,CompactBuffer(1, 2, 3, 4, 6))
(b,List(8, 4, 3, 2, 1))
(a,List(6, 4, 3, 2, 1))
(a,List(6, 4))
(b,List(8, 4))
(b,8)
(b,4)
(a,6)
(a,4)
(a,6)
(a,4)
(b,8)
(b,4)
5.可选步骤-如果需要,按键排序

scores.groupByKey().map({ case (k, numbers) => k -> numbers.toList.sorted(Ordering[Int].reverse).take(2)} ).flatMap({case (k, numbers) => numbers.map(k -> _)}).sortByKey(false).foreach(println)
结果:

(b,CompactBuffer(3, 1, 4, 2, 8))
(a,CompactBuffer(1, 2, 3, 4, 6))
(b,List(8, 4, 3, 2, 1))
(a,List(6, 4, 3, 2, 1))
(a,List(6, 4))
(b,List(8, 4))
(b,8)
(b,4)
(a,6)
(a,4)
(a,6)
(a,4)
(b,8)
(b,4)

希望,这个解释有助于理解逻辑。

这似乎不起作用?这是输出:Array[(String,(Int,Int))]=Array((a,(4,4)),(b,(4,4)))我通过调整user52045的答案:val scores=sc.parallelize(数组(((a),1),((a,2),((a,3),((b,3),((b,1),((a,4),((b,4),((b,2))来实现这一点scores.mapValues(p=>(p,p)).reduceByKey((u,v)=>{val values=List(u._1,u._2,v._1,v._1,v._2.).sorted(Ordering[Int].reverse).distinct(value(0),value(1)).collect()@michael_erasmus你是对的,我的代码中有一个bug。谢谢你修理它。有一件事你必须小心,因为如果列表中的所有元素都是相同的,那么你将摆脱双重感觉。这个解决方案对大型数据集有效吗?我的意思是:把所有的东西都分类,只得到几个最重要的元素似乎也一样exhausting@XiaoyuChen只需稍加修改即可获得任意数量的元素。将第一个映射替换为
mapValues(p=>Range(0,n).map(=>p))
并使用n元素序列而不是两元素元组。您好,欢迎使用堆栈溢出。请不要把代码当作答案。解释你的思路,以便我们更好地理解它。如果您有任何疑问,请阅读此文:谢谢。我相信分数。reduceByKey(+u)将折叠具有相同键的所有对,因此您将得到一个(a,N)和一个(b,M),其中N和M分别是所有a值和b值的总和。在这一点上,只有一个(a,N),没有任何分解量会返回(a,i)和(a,j),其中i和j是所有a对的两个最高值。