用spark求每一个学科最受欢迎的N个老师(分组Topn,多种方法)

第一种方法:笨重方法 通过groupby 然后排序

object _02_FavTeacherTopn {
  def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtils.getSparkContext
    val rdd1: RDD[String] = sc.textFile("data\\anli\\fav_teacher\\teacher.dat")
    //http://spark.doit.cn/laozhao
    val rdd2: RDD[((String, String), Int)] = rdd1.map(line => {
     // ArrayBuffer(http:, , spark.doit.cn, laozhang)
      val arr: Array[String] = line.split("/")
      val arr2 = arr(2).split("[.]")
      ((arr2(0),arr(3)),1)
    })
    //对key先进行聚合  这样可以使数据量大幅度的减少
    val rdd3: RDD[((String, String), Int)] = rdd2.reduceByKey(_ + _)
    val rdd4: RDD[(String, Iterable[((String, String), Int)])] = rdd3.groupBy(tp => {
      tp._1._1
    })
    val rdd5: RDD[List[((String, String), Int)]] = rdd4.map(tp => {
      //tolist操作  如果一个学科下面有很多的老师   会造成内存溢出
      val tuples: List[((String, String), Int)] = tp._2.toList.sortBy(tp => {
        -tp._2
      })
      tuples
    })
    val rdd6: RDD[List[((String, String), Int)]] = rdd5.map(line => {
      //(line(0), line(1))
      val tuples = line.take(3)
      tuples
    })
    rdd6.foreach(println(_))
    sc.stop()
  }
}

第二种方法:聚合之后 先进行过滤,得到每一组的数值,然后利用takeOrdered取出topn

object _03_FavTeacherTopn {
  def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtils.getSparkContext
    val rdd1: RDD[String] = sc.textFile("data\\anli\\fav_teacher\\teacher.dat")
    //http://spark.doit.cn/laozhao
    val rdd2: RDD[((String, String), Int)] = rdd1.map(line => {
     // ArrayBuffer(http:, , spark.doit.cn, laozhang)
      val arr: Array[String] = line.split("/")
      val arr2 = arr(2).split("[.]")
      ((arr2(0),arr(3)),1)
    })
    //对key先进行聚合  这样可以使数据量大幅度的减少
    val rdd3: RDD[((String, String), Int)] = rdd2.reduceByKey(_ + _)
    //首先,得到总的科目
    val array: Array[String] = rdd3.map(_._1._1).distinct().collect()
    //传入自定义的排序规则
   /* implicit  def  myOrdering = {
      new Ordering[((String, String), Int)] {
        override def compare(x: ((String, String), Int), y: ((String, String), Int)): Int = {
          - x._2 + y._2
        }
      }
    }*/
    implicit val ord: Ordering[((String, String), Int)] = Ordering[Int].on[((String, String), Int)](t => -t._2)
    //进行缓存,方便多次进行复用
    rdd3.persist(StorageLevel.MEMORY_AND_DISK_SER)
    for (elem <- array) {
      val rdd4: RDD[((String, String), Int)] = rdd3.filter(tp => {
        tp._1._1 == elem
      })
      val rdd5: Array[((String, String), Int)] = rdd4.takeOrdered(3)
      rdd5.foreach(println(_))
      println("**********************")
    }
    //println(array.toBuffer)
    sc.stop()
  }
}

第三种方法:聚合之后 进行分组 与第一种方法的不同之处在于 迭代器不转换为集合后再进行排序,而是利用treeSet集合,能够自动排序

object _04_FavTeacherTopn {
  def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtils.getSparkContext
    val rdd1: RDD[String] = sc.textFile("data\\anli\\fav_teacher\\teacher.dat")
    //http://spark.doit.cn/laozhao
    val rdd2: RDD[((String, String), Int)] = rdd1.map(line => {
     // ArrayBuffer(http:, , spark.doit.cn, laozhang)
      val arr: Array[String] = line.split("/")
      val arr2 = arr(2).split("[.]")
      ((arr2(0),arr(3)),1)
    })
    //对key先进行聚合  这样可以使数据量大幅度的减少
    val rdd3: RDD[((String, String), Int)] = rdd2.reduceByKey(_ + _)

    implicit val ord: Ordering[((String, String), Int)] = Ordering[Int].on[((String, String), Int)](t => -t._2)

    val rdd4: RDD[(String, Iterable[((String, String), Int)])] = rdd3.groupBy(_._1._1)

    val rdd5: RDD[(String, mutable.TreeSet[((String, String), Int)])] = rdd4.mapValues(iters => {
      //仅仅对排序方法进行了修改,利用了TreeSet集合的特性,先分组  再排序
      val set = new mutable.TreeSet[((String, String), Int)]
      for (elem <- iters) {
        set += elem
        if (set.size > 3) {
          set -= set.last
        }
      }
      set
    })
    rdd5.foreach(println(_))
    sc.stop()
  }
}

第四种方法:聚合之后 不进行分组 而是进行分区 与第一种方法的不同之处在于 迭代器不转换为集合后再进行排序,而是利用treeSet集合,能够自动排序

object _05_FavTeacherTopn {
  def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtils.getSparkContext
    val rdd1: RDD[String] = sc.textFile("data\\anli\\fav_teacher\\teacher.dat")
    //http://spark.doit.cn/laozhao
    val rdd2: RDD[((String, String), Int)] = rdd1.map(line => {
     // ArrayBuffer(http:, , spark.doit.cn, laozhang)
      val arr: Array[String] = line.split("/")
      val arr2 = arr(2).split("[.]")
      ((arr2(0),arr(3)),1)
    })
    //对key先进行聚合  这样可以使数据量大幅度的减少
    val rdd3: RDD[((String, String), Int)] = rdd2.reduceByKey(_ + _)

    //获取课程的集合
    val list: List[String] = rdd3.map(_._1._1).distinct().collect().toList

    implicit val ord: Ordering[((String, String), Int)] = Ordering[Int].on[((String, String), Int)](t => -t._2)

    val rdd4: RDD[((String, String), Int)] = rdd3.partitionBy(new MyPartitioner(list))

    val rdd5: RDD[((String, String), Int)] = rdd4.mapPartitions(iters => {
      //仅仅对排序方法进行了修改,利用了TreeSet集合的特性,先分组  再排序
      val set = new mutable.TreeSet[((String, String), Int)]
      for (tp <- iters) {
        set += tp
        if (set.size > 3) {
          set -= set.last
        }
      }
      set.iterator
    })
    rdd5.foreach(println(_))
    sc.stop()
  }
}

第五种方法:自定义分区器,保证一个分区内有且仅有一个学科的数据,在调用reduceByKey时就使用自定义的分区器

object _06_FavTeacherTopn {
  def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtils.getSparkContext
    val rdd1: RDD[String] = sc.textFile("data\\anli\\fav_teacher\\teacher.dat")
    //http://spark.doit.cn/laozhao
    val rdd2: RDD[((String, String), Int)] = rdd1.map(line => {
     // ArrayBuffer(http:, , spark.doit.cn, laozhang)
      val arr: Array[String] = line.split("/")
      val arr2 = arr(2).split("[.]")
      ((arr2(0),arr(3)),1)
    })
    //获取课程的集合
    val list: List[String] = rdd2.map(_._1._1).distinct().collect().toList

    //对key先进行聚合  这样可以使数据量大幅度的减少
    val rdd3: RDD[((String, String), Int)] = rdd2.reduceByKey(new MyPartitioner(list),_ + _)
    implicit val ord: Ordering[((String, String), Int)] = Ordering[Int].on[((String, String), Int)](t => -t._2)

    val rdd4: RDD[((String, String), Int)] = rdd3.mapPartitions(iters => {
      //仅仅对排序方法进行了修改,利用了TreeSet集合的特性,先分组  再排序
      val set = new mutable.TreeSet[((String, String), Int)]
      for (tp <- iters) {
        set += tp
        if (set.size > 3) {
          set -= set.last
        }
      }
      set.iterator
    })
    rdd4.foreach(println(_))
    sc.stop()
  }
}
//通过构造器传入课程的集合
class MyPartitioner(val list:List[String]) extends Partitioner{

  //通过hashMap
  private val hashMap = new mutable.HashMap[String, Int]()
  var i:Int = 0
  for (elem <- list) {
    hashMap ++= Map((elem, i))
    i += 1
  }
  //自定义分区的个数为课程的个数
  override def numPartitions: Int = list.size
  //自定义分区的规则
  override def getPartition(key: Any): Int = {
    val sub: String = key.asInstanceOf[(String, String)]._1

    val maybeInt: Option[Int] = hashMap.get(sub)
    val value: Int = maybeInt.get
    value
  }
}
//工具类
object SparkUtils {

  Logger.getLogger("org").setLevel(Level.ERROR)
  def getSparkContext: SparkContext = {

    val conf: SparkConf = new SparkConf()
    conf.setAppName(this.getClass.getSimpleName)
      .setMaster("local[*]")
    val sc: SparkContext = new SparkContext(conf)
    sc
  }

}

/**
 * 手动生成大量的数据
 */
object _01_GenerateTeacherData {
  def main(args: Array[String]): Unit = {

    //http://spark.doit.cn/tom
    val cou = Array("spark", "flink", "java", "hadoop")
    val tea = Array("tom", "rose", "jack", "hank")

    val writer: PrintWriter = new PrintWriter(new File("data\\anli\\fav_teacher\\teacher.dat"))
    val ran = new Random()
    for (i <- 1 to 100000){
      val i = ran.nextInt(cou.length)
      val cou1: String = cou(i)
      val j = ran.nextInt(tea.length)
      val tea1: String = tea(j)
      writer.println(s"http://$cou1.study.cn/$tea1")
    }
    writer.close()
  }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Spark RDD中分组TopN案例是指在一个RDD中,根据某个键值进行分组,然后对每个组内的数据进行排序,取出每个组内的前N个数据。这种操作在数据分析和处理中非常常见,可以用于统计每个地区的销售额排名前N的产品、每个用户的消费排名前N的商品等。 优化方面,可以考虑使用Spark SQL或DataFrame来实现分组TopN操作,因为它们提供了更高级的API和优化技术,可以更快速地处理大规模数据。另外,可以使用分布式缓存技术将数据缓存到内存中,以加快数据访问速度。还可以使用分区和并行计算等技术来提高计算效率。 ### 回答2: Spark RDD中分组取Top N的案例可以是对一个大数据集中的用户数据进行分组,然后取每个组中消费金额最高的前N个用户。这个案例可以通过以下步骤来实现: 1. 将用户数据载入Spark RDD中,每个数据记录包含用户ID和消费金额。 2. 使用groupBy函数将RDD按照用户ID进行分组,得到一个以用户ID为key,包含相同用户ID的数据记录的value的RDD。 3. 对每个分组的value调用top函数,指定N的值,以获取每个分组中消费金额最高的前N个用户。 4. 可以将每个分组中Top N的用户使用flatMap函数展开为多个记录,并可以添加一个新的字段表示该记录属于哪个分组。 5. 最后,可以使用collect函数将结果转化为数组或者保存到文件或数据库中。 在这个案例中,进行优化的关键是减少数据的传输和处理开销。可以使用缓存或持久化函数对RDD进行优化,以减少重复计算。另外,可以使用并行操作来加速计算,如使用并行的排序算法,或向集群中的多个节点分发计算任务。 对于分组取Top N的优化,还可以考虑使用局部聚合和全局聚合的策略。首先对每个分组内的数据进行局部聚合,例如计算每个分组的前M个最大值。然后,对所有分组的局部聚合结果进行全局聚合,例如计算所有分组的前K个最大值。 另一个优化策略是使用采样技术,例如随机采样或分层采样,以减少需要处理的数据量。 最后,还可以考虑使用Spark的其他高级功能,如Broadcast变量共享数据,使用累加器进行计数或统计等,来进一步提高性能和效率。 ### 回答3: Spark RDD 是 Spark 提供的一种基于内存的分布式数据处理模型,其核心数据结构是弹性分布式数据集(RDD)。 在 Spark RDD 中,分组TopN 是一种常见的需,即对 RDD 中的数据按某个字段进行分组,并取出每个分组中字段值最大的前 N 个数据。 下面以一个示例来说明分组TopN 的用法和优化方法: 假设有一个包含学生信息的 RDD,其中每条数据都包括学生的学科和分数,我们希望对每个学科取出分数最高的前 3 名学生。 ```python # 创建示例数据 data = [ ("语文", 80), ("数学", 90), ("语文", 85), ("数学", 95), ("语文", 75), ("数学", 92), ("英语", 88) ] rdd = sc.parallelize(data) # 分组TopN top3 = rdd.groupByKey().mapValues(lambda x: sorted(x, reverse=True)[:3]) # 输出结果 for subject, scores in top3.collect(): print(subject, scores) # 输出结果: # 数学 [95, 92, 90] # 语文 [85, 80, 75] # 英语 [88] ``` 在上述代码中,我们先使用 `groupByKey()` 对 RDD 进行分组操作,然后使用 `mapValues()` 对每个分组内的数据进行排序并取前 3 个值。 这种方式的优化点在于,通过将分组操作和取 TopN 操作分开,可以减轻数据倾斜的问题。同时,对每个分组进行排序会占用大量计算资源,可以考虑将数据转换为 Pair RDD,并利用 Spark 提供的 `top()` 算子来优化取 TopN 的操作。 ```python # 转换为 Pair RDD pair_rdd = rdd.map(lambda x: (x[0], x[1])) # 分组并取TopN,使用top()算子代替排序操作 top3 = pair_rdd.groupByKey().mapValues(lambda x: sorted(x, reverse=True)).mapValues(lambda x: x[:3]) # 输出结果 for subject, scores in top3.collect(): print(subject, scores) # 输出结果: # 数学 [95, 92, 90] # 语文 [85, 80, 75] # 英语 [88] ``` 通过以上优化,我们可以更好地处理大规模数据集下的分组TopN 的需,提高计算性能和资源利用率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值