package com.zyc.spark
 import com.zyc.utils.DateUtils
 import org.apache.spark.rdd.RDD
 import org.apache.spark.{Partitioner, SparkConf, SparkContext}/**
 * Created with IntelliJ IDEA.
 * Author: zyc2913@163.com
 * Date: 2020/9/25 16:30
 * Version: 1.0
 * Description: 转换算子的应用
 */
 object StudyTransformation {
   def main(args: Array[String]): Unit = {
     //配置spark环境
     val conf = new SparkConf()
       .setMaster("local[2]")
       .setAppName("demo2")
     val sc = new SparkContext(conf)    /**
      * 转换算子
      * RDD -> transformation -> RDD
      */
     /**
      * 1.map 对每个元素操作map
      * 1.1声明  def map(f: T => U): RDD[U]
      * 1.2参数  一个一元函数,参数是原RDD的元素类型,返回值可以改变类型
      * 1.3返回值  新的RDD,泛型是f的返回值类型
      * 1.4作用  将原RDD中的每个元素应用到f中,将返回值收集到一个新的RDD中
      * 1.5应用举例  收集日志文件中的ip,time(访问时间),rCode(登录标识码)
      */
       //通过读取文件创建RDD
     val rdd1:RDD[String] = sc.textFile("C:\\Users\\Administrator\\Desktop\\book\\logs\\access.log-20190926")
     val rdd2 = rdd1.map(line => {
       val str = line.split("\\s+")
       val ip = str(0) // 小标为0的字符串为ip
       val time = DateUtils.dateFormat(str(3).tail) //调用工具类的日期转换函数将下标为3的字符串的尾部进行转换
       var rCode = 0 //定义登录标识码初始值为0
       try {
         rCode = str(8).toInt
         // rCode的值是下标为8的字符串,将String类型转成Int型
       } catch {
         case e: Exception =>
       }
       (ip, time, rCode)  //最后一行是返回值
     })
     //rdd2.foreach(println)    /**
      * 2.filter 过滤器
      * 2.1声明  def filter(f: T => Boolean): RDD[T]
      * 2.2参数  一元函数,参数是原RDD中的元素,返回值是Boolean
      * 2.3返回值  和原RDD泛型一致的RDD
      * 2.4作用  过滤元素 (f返回True的元素将被留下)
      * 2.5应用举例  收集rCode为200和404的访问信息(ip, time, rCode)
      */
     rdd2.filter(_._3 == 200)  //留下rCode为200的函数返回值
      // .foreach(println)     //打印到控制台
     rdd2.filter(_._3 == 404)  //留下rCode为404的函数返回值
      // .foreach(println)     //打印到控制台    /**
      * 3.flatMap 二维转换成一维map
      * 3.1声明  def flatMap(f: T => TraversableOnce[U]): RDD[U]
      * 3.2参数  一个一元函数,参数是原RDD的元素类型,返回值是一个集合
      * 3.3返回值  是一个f的返回值泛型的RDD
      * 3.4作用  将二维集合变成一维集合
      * 3.5应用举例  统计所有ip中0到9的数字出现的次数
      */
     rdd2.flatMap(x => {
       val res1:String = x._1
       val res2:String = res1.replace(".", "")
       val res3:Array[String] = res2.split("")
       res3
     })
       .map((_,1)) //写成二元组的形式
       .reduceByKey(_+_) //根据key聚合统计value
       //.foreach(println)
     //上面的也可以写成下面的样子
     val rdd3:RDD[String] = rdd2.flatMap(_._1.replace(".", "").split(""))
       rdd3.map((_,1))
         .reduceByKey(_+_)
         //.foreach(println)    /**
      * 4.mapPartitions  按分区数map
      * 4.1声明  def mapPartitions( f: Iterator[T] => Iterator[U],preservesPartitioning: Boolean = false): RDD[U]
      * 4.2参数 第一个参数:一个一元函数,参数是一个原RDD泛型的迭代器,这个迭代器每次传入一个分区的全部元素,返回值也是一个迭代器,泛型不限
      * 4.3返回值  RDD[U]  泛型为f返回值泛型的RDD
      * 4.4作用 之前map是每次拿到一个元素,mapPartitions一次性拿到一个分区的所有元素,在将处理完的结果放回到新的RDD中
      */
     val rdd4:RDD[Int] = sc.makeRDD(1 to 10)  //1到10的数组
     //rdd4.map(_+1).foreach(println) //RDD每个元素+1,然后打印到控制台,运算了10次
     //rdd4.mapPartitions(iter => iter.map(_+1)).foreach(println) //RDD按分区数map,每个元素+1然后打印到控制台,运算了2次(设置的核心数为2,则默认分区数为2)
       rdd4.mapPartitions((iter:Iterator[Int]) => {
         Iterator(iter.toList.mkString("|")) //把数组转换成List,根据分区数分成了2个list,然后把元素按|分隔开
       })
      //.foreach(println) //打印结果为6|7|8|9|10  1|2|3|4|5    /**
      * 5.mapPartitionsWithIndex  按分区数的下标map  (下标默认从0开始,下标也叫索引)
      * 5.1声明  def mapPartitionsWithIndex(
      *            分区索引
      *          f: (Int, Iterator[T]) => Iterator[U],
      *          preservesPartitioning: Boolean = false): RDD[U]
      * 5.2参数   与mapPartitions类似,多了一个int类型的参数,接收传进来的分区索引
      * 5.3返回值  RDD[U]
      * 5.4作用  与mapPartitions类似
      */    rdd4.mapPartitionsWithIndex((index,iter) => {
       val tuple:(Int,String) = (index, iter.mkString("|")) //二元组类型(下标,按|分隔的数组)
       Iterator(tuple)
     })
       //.foreach(println) //打印结果:(0,1|2|3|4|5)   (1,6|7|8|9|10)   ( 设置的核心数为2,默认的分区数就为2,所以分区的下标就是0,1)    /**
      * 6.sample 计算机的随机算法,可以保证如果输入参数不同-->>输出结果不同
      *6.1声明  def sample(
      *        withReplacement: Boolean, //是否放回 true表示放回,取的值可能会重复;false表示不放回,取的值不会重复
      *        fraction: Double, //抽样比例 不是非常精准(可能多几个也可能少几个),即抽出的个数占总个数的比值
      *        seed: Long = Utils.random.nextLong //随机种子
      *        ): RDD[T]
      * 6.2参数
      * 6.3返回值 和原RDD类型相同的RDD
      * 6.4作用  在海量数据中进行抽样
      * 6.5应用举例:对一个数组(1到100)进行抽样
      */
     val rdd5:RDD[Int] = sc.makeRDD(1 to 100) //定义一个1到100的数组
     //rdd5.sample(false,0.1).foreach(println)   //从数组(1到100)中不放回(false)的取总个数的比例为0.1(百分之10)的元素(因为是比例所以不精准,可能是10个,可能是9个,也可能是11个)打印到控制台
     /*
     takeSample精确抽样;sample模糊抽样
      */
     //rdd5.takeSample(false,10).foreach(println)  //从数组中不放回的随机取10个元素打印到控制台    /**
      * 7.union   类似于集合中的并集 A ∪ B
      * 7.1声明   def union(other: RDD[T]): RDD[T]
      * 7.2参数    另外一个和原RDD类型相同的RDD
      * 7.3返回值  和原RDD类型相同
      * 7.4作用   合并RDD
      * 7.5应用举例:把两个数组合并到一起(并集)
      */
     val res1 = sc.makeRDD(1 to 5)
     val res2 = sc.makeRDD(3 to 7)
     //res1.union(res2).foreach(println) //将res1与res2合并(取并集有重复的元素)打印到控制台    /**
      * 8.intersection  类似于集合中的交集  A ∩ B
      * 8.1声明    def intersection(other: RDD[T]): RDD[T]
      * 8.2参数   另外一个和原RDD类型相同的RDD
      * 8.3返回值  和原RDD类型相同
      * 8.4作用  交集
      * 8.5应用举例:取两个数组中相同的元素(交集)
      */
     //res1.intersection(res2).foreach(println) //取res1与res2中的相同元素(交集)打印到控制台    /**
      * 9  distinct 去重
      * 9.1声明
      * 9.2参数
      * 9.3返回值
      * 9.4作用
      * 9.5应用举例:去除数组中的重复元素
      */
     //sc.makeRDD(Array(1,2,3,4,2,1,6,5,3,9)).distinct().foreach(println) //对数组去重后打印到控制台    /**
      * 10 partitionBy 适用于key-value型,该函数来自于PairRDDFunctions.scala
      * 10.1声明 def partitionBy(partitioner: Partitioner): RDD[(K, V)]
      * 10.2参数  是一个分区器对象的实例
      * 10.3返回值  和原RDD类型相同
      * 10.4作用   按照自定的分区器,对RDD中的元素进行分区
      */
     val rdd6:RDD[Int] = sc.makeRDD(1 to 10)
     val rdd7:RDD[(Int,Int)] = rdd6.map(x => (x, x))  //将数组转成key-value型
     rdd7.partitionBy(new Partitioner {
       override def numPartitions: Int = 2 //设置分区数为2      override def getPartition(key: Any): Int = key.asInstanceOf[Int] % 2  //将key分成奇数和偶数
     }).map(_._1)  //再转换成数组
       .mapPartitionsWithIndex((index,iter)=>Iterator((index,iter.mkString("|"))))  //按分区索引分,将元素用|分隔开
      // .foreach(println)  //打印结果(0,2|4|6|8|10)  (1,1|3|5|7|9)
   }}