第 11 章 数据结构 之 集合操作

一、集合元素的映射 — map映射操作

1、map映射操作
1. 这里说的map映射不是map集合,而是map的一种高阶函数用法
2. 在Scala中可以通过map映射操作来解决将集合中的每一个元素通过指定功能(函数)映射(转换)成新
的结果集合,这里其实就是所谓的将函数作为参数传递给另外一个函数,这是函数式编程的特点。

以 HashSet 为例说明

1. def map[B](f: (A) ⇒ B): HashSet[B]   // map函数的签名
2. 这个就是map映射函数,集合类型都有
3. [B] 是泛型
4. map是一个高阶函数(可以接受一个函数的函数,就是高阶函数),可以接收 函数 f: (A) => B
5. HashSet[B] 就是返回新的集合

提出问题

	将List(3,5,7) 中的所有元素都 * 2 ,将其结果放到一个新的集合中返回,即返回一个新的List(6,10,14), 
编写程序实现.

高阶函数的基本使用与上面问题传统方式实现功能的示例代码:

package com.lj.scala.GatherOperate

/**
  * @author Administrator
  * @create 2020-03-16
  */
object MapTest01 {

    def main(args: Array[String]): Unit = {

        /*
        问题:
            将List(3,5,7) 中的所有元素都 * 2 ,将其结果放到一个新的集合中返回,即返回一个新的List(6,10,14), 编写程序实现.
         */

        /**
          * 一、传统方式实现上面问题功能:
          * 1. 优点
          *     处理问题的方法比较直接,好理解
          * 2. 缺点
          *     a): 不够简洁,高效
          *     b): 没有体现函数式编程的特点,即 集合 -> 函数 -> 新的集合 ...
          *     c): 不利于复杂业务处理的需求
          */


        val list01 = List(3, 5, 7)  // 默认是不可变的列表List
        var list02 = List[Int]()

        // 遍历list01
        for (ele <- list01) {
            list02 = list02 :+ ele * 2  // list02每遍历一个元素就会重新指向一个新的地址,所以不够高效
        }
        println("传统方式实现问题功能list02:" + list02)


        /**
          * 二、高阶函数的基本使用
          * 使用高阶函数:
          * 1. 下划线使用方式:getDouble(getRes _, 2.8),函数与下划线之间需要有空格;
          * 2. 这里的下划线(_): 表示向函数getRes传入的参数,也可以省略。
          */

        val res = getDouble(getRes _, 2.8)
        println("使用高阶函数获取res的值:" + res)

        /**
          * 在Scala中,可以把一个函数直接赋值给一个变量但是不执行函数
          * 1. val sayHi = sayHello _
          *     a):将函数sayHello赋值给变量sayHi,此时sayHi就是一个函数
          *     b): 注意赋值方式是:函数名 空格 下划线
          *
          * 2. val sayHi = sayHello()
          *     注意后面的不能是“()”,否则就会执行sayHello()这个函数,将返回值给sayHi,此时sayHi就是一个变量了
          */

        val sayHi = sayHello _
        println("执行函数sayHi:" + sayHi())  // 此时调用sayHi函数时,需要添加“()”,才会执行函数!!!

        /**
          * 三、高阶函数可以接收一个没有输入,返回有Unit的函数
          */

        getInfo(info)

    }

    /**
      * 说明:
      * 1. getDouble就是一个高阶函数;
      * 2. “fun: Double => Double” 表示一个函数,该函数接收一个Double类型的数据,并返回一个Double类型的数据;
      * 3. “num: Double” 是普通参数;
      * 4. “fun(num)” 在“getDouble”中执行传入的函数。
      */
    def getDouble(fun: Double => Double, num: Double) = {  // fun 这个名字可以随便取名
        fun(num)
    }

    // 普通函数,接收一个Double类型的数据,并返回一个Double类型的数据
    def getRes(num: Double): Double = {
        num * 3 + 5
    }

    def sayHello(): String = {
        println("Say Hello ...")
        "Say Hi!!!"
    }

    /**
      * getInfo就是一个高阶函数,此时它是接收一个没有输入,返回为Unit的函数
      * 注意:这个“()”可以省略,但是getInfo中调用的方法fun的括号也需要省略,
      *      如果不省略“()”,getInfo中调用的方法fun的括号也不能省略。
      */
    def getInfo(fun: () => Unit): Unit = {
        fun()
    }

    def info(): Unit = {
        println("生活在中国,相比国外人身还是很安全!")
    }

}
=========================运行结果================================
传统方式实现问题功能list02:List(6, 10, 14)
使用高阶函数获取res的值:13.399999999999999
Say Hello ...
执行函数sayHi:Say Hi!!!
生活在中国,相比国外人身还是很安全!
=========================运行结果================================

高阶函数解决上述问题的示例代码:

package com.lj.scala.GatherOperate

/**
  * @author Administrator
  * @create 2020-03-16
  */
object MapTest02 {

    def main(args: Array[String]): Unit = {

        /*
        问题:
            将List(3,5,7) 中的所有元素都 * 2 ,将其结果放到一个新的集合中返回,即返回一个新的List(6,10,14), 编写程序实现.
         */

        val list01 = List(3, 5, 7)

        /**
          * 此时就是用到了map这个高阶函数,它会遍历list01中的每个元素,并调用multiple函数,
          * 然后将函数返回值返回给变量list02
          *
          */
        val list02 = list01.map(multiple)
        println("list02:" + list02)  // List(6, 10, 14)

        // 深刻理解map映射函数的机制-模拟实现
        val myList = MyList(list01)
        val list03 = myList.map(multiple)
        println("list03:" + list03)

        /*
        练习:
            将val names = List("Alice", "Bob", "Nick") 中的所有单词,全部转成字母大写,返回到新的List集合中
         */

        val names = List("Alice", "Bob", "Nick")
        val name_lists = names.map(upper)
        println("name_lists:" + name_lists)

    }

    def multiple(n: Int): Int = {
        println("multiple被调用了...")
        n * 2
    }

    def upper(str: String): String = {
        str.toUpperCase
    }

}


// 深刻理解map映射函数的机制-模拟实现
class MyList {

    var list01 = List[Int]()
    var list02 = List[Int]()

    def this(list: List[Int]) {
        this()
        this.list01 = list
    }

    // map 函数
    def map(fun: Int => Int): List[Int] = {

        // 遍历集合
        for (ele <- this.list01) {
            list02 = list02 :+ fun(ele)
        }
        list02
    }

}

// 伴生对象
object MyList {

    def apply(): MyList = {
        new MyList()
    }

    def apply(list: List[Int]): MyList = {
        new MyList(list)
    }

}
=========================运行结果================================
multiple被调用了...
multiple被调用了...
multiple被调用了...
list02:List(6, 10, 14)
multiple被调用了...
multiple被调用了...
multiple被调用了...
list03:List(6, 10, 14)
name_lists:List(ALICE, BOB, NICK)
=========================运行结果================================
2、Flatmap映射:Flat即压扁,压平,扁平化映射与集合元素的过滤-Filter

Flatmap映射介绍

Flatmap:flat即压扁,压平,扁平化,效果就是将集合中的每个元素的子元素映射到某个函数并返回新的集合。

Filter介绍

Filter:将符合要求的数据(筛选)放置到新的集合中

示例代码:

package com.lj.scala.GatherOperate

/**
  * @author Administrator
  * @create 2020-03-16
  */
object FlatMapTest01 {

    def main(args: Array[String]): Unit = {

        /*
        需求:
            将List集合中的所有元素进行扁平化操作,即把所有元素的打散!!!
         */

        /**
          * Flatmap映射:
          * 1. 可以发现flatmap将List集合中的元素拆成了单个字符进行返回
          */

        val names = List("Alice", "Bob", "Nick")
        val name_list01 = names.flatMap(upper)
        println("name_list:" + name_list01)  // name_list:List(A, L, I, C, E, B, O, B, N, I, C, K)

        /**
          * 集合元素的过滤-filter
          */

        // 保留首字母是A的元素
        val name_list02 = names.filter(startA)
        println("name_list02:" + name_list02)  // name_list02:List(Alice)

    }

    def upper(str: String): String = {
        str.toUpperCase
    }

    def startA(str: String): Boolean ={
        str.startsWith("A")
    }

}

二、化简(reduce)

介绍

化简:将二元函数(有两个形参的函数就是二元函数)引用于集合中的函数,。

示例代码:

package com.lj.scala.GatherOperate

import java.awt.im.InputMethodRequests

/**
  * @author Administrator
  * @create 2020-03-16
  */
object ReduceTest01 {

    def main(args: Array[String]): Unit = {

        /*
        需求:val list01 = List(1, 20, 30, 4 ,5) , 求出list的和.
         */

        /**
          * 说明: reduceLeft
          *     1. def reduceLeft[B >: A](@deprecatedName('f) op: (B, A) => B): B
          *     2. reduceLeft(f) 接收的函数需要的形式为 op: (B, A) => B): B
          *     3. reduceleft(f) 的运行规则是 从左边开始执行将得到的结果返回给第一个参数
          *     4. 然后继续和下一个元素运行,将得到的结果继续返回给第一个参数,继续...
          *        即: //((((1 + 20)  + 30) + 4) + 5) = 60
          *
          * 说明:reduceRight
          *      reduceRight与reduceLeft非常相似,reduceRight是从集合的右边开始遍历元素,然后元素传给函数
          *      右边开始的第一个形参,接着与第二传入的元素进行运算,接着会将结果再次传给函数右边开始的第一个
          *      形参,依次类推...,而reduceLeft正好与它相反,遍历元素是从左边开始的,元素传入函数也是从左边
          *      第一个开始的。
          */

        val list01 = List(1, 20, 30, 4 ,5)
        val count_list01 = list01.reduceLeft(listSum)  // 60
        println("List元素之和count_list01:" + count_list01)

        val count_list02 = list01.reduceRight(listSum)  // 60
        println("List元素之和count_list02:" + count_list02)

        /*
        练习:val list01 = List(1, 20, 30, 4 ,5) , 求出list的最小值.
         */

        val min_value = list01.reduceLeft(minNum)
        println("list01的最小值:" + min_value)  // 1

    }

    def listSum(n1: Int, n2: Int): Int = {
        n1 + n2
    }

    def minNum(n1: Int, n2: Int): Int = {
        if (n1 > n2) {
            n2
        } else {
            n1
        }
    }

}

三、折叠(fold)

基本介绍

1. fold函数将上一步返回的值作为函数的第一个参数继续传递参与运算,直到list中的所有元素被遍历。

2. 可以把reduceLeft看做简化版的foldLeft。
	如何理解:
		def reduceLeft[B >: A](@deprecatedName('f) op: (B, A) => B): B =
			if (isEmpty) throw new UnsupportedOperationException("empty.reduceLeft")
			else tail.foldLeft[B](head)(op)
	可以看到. reduceLeft就是调用的foldLeft[B](head),并且是默认从集合的head元素开始操作的。
3. 相关函数:fold,foldLeft,foldRight,可以参考reduce的相关方法理解

示例代码:

package com.lj.scala.GatherOperate

/**
  * @author Administrator
  * @create 2020-03-16
  */
object FoldTest01 {

    def main(args: Array[String]): Unit = {

        /**
          * 折叠:fold
          * 1. list01.foldLeft(5)(minus)  // foldLeft缩写是:“/:”
          *     可以理解为list01的最左边添加一个元素5,即list01 = List(5, 1, 2, 3, 12),然后
          *     执行list01.reduceLeft(minus), 跟reduceLeft函数很像。
          * 2. list01.foldRight(5)(minus)   // foldRight 缩写是:“:\”
          *     可以理解为list01的最右边添加一个元素5,即list01 = List(1, 2, 3, 12, 5), 然后
          *     执行list01.reduceRight(minus), 跟reduceRight函数很像。
          */

        val list01 = List(1, 2, 3, 12)
        val res01 = list01.foldLeft(5)(minus)  // 函数的柯里化
        // 缩写 foldLeft
        // val res01 = (5 /: list01)(minus)
        println("res01:" + res01)  // -13
        val res02 = list01.foldRight(5)(minus)  // 函数的柯里化
        // 缩写 foldRight
        // val res02 = (list01 :\ 5)(minus)
        println("res01:" + res02)  // -5


    }

    def minus(n1: Int, n2: Int): Int = {
        n1 - n2
    }

}

四、扫描(scan)

基本介绍

扫描,即对某个集合的所有元素做fold操作,但是会把产生的所有中间结果放置于一个集合中保存

示例代码:

package com.lj.scala.GatherOperate

/**
  * @author Administrator
  * @create 2020-03-16
  */
object ScanTest01 {

    def main(args: Array[String]): Unit = {

        /**
          * 说明:Scan
          *     1. scan, scanRight, scanLeft;
          *     2. 默认的scan就是scanLeft,这两个函数一样的功能;
          *     3. scanLeft是将元素5放置于左边第一个位置,接着遍历列表list01的元素与5进行运算
          *        然后将结果返回,并放置在左边开始第二位置,接着再将结果与下一个元素进行运算并
          *        返回,依次类推...,最后返回一个集合(IndexSeq[Int])。
          *     4. scanRight与scanLeft正好相反,它是将元素5放置在右边的第一个位置,接着遍历列表
          *        list01的元素与5进行运算然后将结果返回,并放置在右边开始第二个位置,接着再将结
          *        果与下一个元素进行运算并返回,依次类推...,最后返回一个集合(IndexSeq[Int])。
          *
          */

        val list01 = List(1, 2, 3, 4, 5)
        val scan_res01 = list01.scan(5)(minus)  // IndexSeq[Int]
        println("scan_res01:" + scan_res01)  // scan_res01:List(5, 4, 2, -1, -5, -10)
        val scan_res02 = list01.scanLeft(5)(minus)
        println("scan_res02:" + scan_res02)  // scan_res02:List(5, 4, 2, -1, -5, -10)
        val scan_res03 = list01.scanRight(5)(minus)
        println("scan_res03:" + scan_res03)  // scan_res03:List(-2, 3, -1, 4, 0, 5)

    }

    def minus(n1: Int, n2: Int): Int = {
        n1 - n2
    }

}

五、扩展 - 拉链(合并)

基本介绍

在开发中,当我们需要将两个集合进行 对偶元组合并,可以使用拉链。

注意事项

1. 拉链的本质就是两个集合的合并操作,合并后每个元素是一个 对偶元组;
2. 如果两个集合个数不对应,会造成数据丢失;
3. 集合不限于List, 也可以是其它集合比如Array;
4. 如果要取出合并后的各个对偶元组的数据,可以遍历。

示例代码:

package com.lj.scala.GatherOperate

/**
  * @author Administrator
  * @create 2020-03-16
  */
object ZipTest01 {

    def main(args: Array[String]): Unit = {

        val list01 = List("Tom", "Jack", "Leo")
        val list02 =List(18, 22, 25)

        // 拉链(合并)
        val zip_list = list01.zip(list02)  // 对偶元组
        println("zip_list:" + zip_list)  // zip_list:List((Tom,18), (Jack,22), (Leo,25))

        // 遍历
        for (tuple <- zip_list) {
            println(tuple._1 + " --> " + tuple._2)  // 取出时按照元组的方式取出数据
        }
        /*
        结果:Tom --> 18
             Jack --> 22
             Leo --> 25
         */

    }

}

六、扩展-迭代器

基本说明

通过iterator方法从集合获得一个迭代器,通过while循环和for表达式对集合进行遍历.(使用迭代器来遍历)

示例代码:

package com.lj.scala.GatherOperate

/**
  * @author Administrator
  * @create 2020-03-16
  */
object IteratorTest01 {

    def main(args: Array[String]): Unit = {

        val list01 = List(3, 15, 35, 23, 15)
        val iterator01 = list01.iterator   // 得到迭代器

        /*
        // Iterator的继承关系
        def iterator: Iterator[A] = new AbstractIterator[A] {
            var these = self
            def hasNext: Boolean = !these.isEmpty
            def next(): A =
              if (hasNext) {
                val result = these.head; these = these.tail; result
              } else Iterator.empty.next()

            override def toList: List[A] = {

              val xs = these.toList
              these = these.take(0)
              xs
            }
        }
        
        1. iterator 的构建实际是 AbstractIterator 的一个匿名子类,该子类提供了
              def iterator: Iterator[A] = new AbstractIterator[A] {
                 var these = self
                 def hasNext: Boolean = !these.isEmpty
                 def next(): A =
         2. 该AbstractIterator 子类提供了  hasNext next 等方法.
         3. 因此,我们可以使用 while的方式,使用hasNext next 方法变量
         
         */

        // while遍历
        var index = 0
        while(iterator01.hasNext) {
            println(s"while遍历iterator01的第 $index 个值:" + iterator01.next())
            index += 1
        }
        
        println("-------------------华丽分隔符----------------------")

        // 注意:迭代器Iterator遍历完一次,就没有值了!!!
        // for遍历
        index = 0
        for (ele <- iterator01) {
            println(s"for遍历iterator01的第 $index 个值:" + ele)
            index += 1
        }


    }

}
=========================运行结果================================
while遍历iterator01的第 0 个值:3
while遍历iterator01的第 1 个值:15
while遍历iterator01的第 2 个值:35
while遍历iterator01的第 3 个值:23
while遍历iterator01的第 4 个值:15
-------------------华丽分隔符----------------------
=========================运行结果================================

七、扩展-流 Stream

基本说明

	stream是一个集合。这个集合,可以用于存放无穷多个元素,但是这无穷个元素并不会一次性生产出来,
而是需要用到多大的区间,就会动态的生产,末尾元素遵循lazy规则(即:要使用结果才进行计算的) 。

创建Stream对象

def numsForm(n: BigInt) : Stream[BigInt] = n #:: numsForm(n + 1)
val stream1 = numsForm(1)

说明

1. Stream 集合存放的数据类型是BigInt;
2. numsForm是自定义的一个函数,函数名是程序员指定的;
3. 创建集合的第一个元素是n , 后续元素生成的规则是n + 1;
4. 后续元素生成的规则是可以程序员指定的 ,比如 numsForm( n * 4)...。

示例代码:

package com.lj.scala.GatherOperate

/**
  * @author Administrator
  * @create 2020-03-16
  */
object StreamTest01 {

    def main(args: Array[String]): Unit = {

        // 创建Stream
        def numsForm(n: BigInt): Stream[BigInt] = n #:: numsForm(n * 2)
        val stream01 = numsForm(1)
        println("stream01:" + stream01)  // stream01:Stream(1, ?)
        // 取出第一个元素
        println("第一个元素:" + stream01.head)  // 1

        // 当对流执行tail操作时,就会生成一个新的数据。
        println("执行tail操作:" + stream01.tail)  // Stream(2, ?)
        println("执行tail操作之后:" + stream01)  // Stream(1, 2, ?)

        // 注意:如果使用流集合,就不能使用last属性,如果使用last集合就会进行无限循环
        // println("使用last属性:" + stream01.last)

        // 使用map映射stream的元素并进行一些计算
        // 创建Stream
        def getNums(n: BigInt): Stream[BigInt] = n #:: getNums(n * 2 / 3)
        // 运算
        println("map映射stream运算:" + getNums(7).map(multi))   // Stream(49, ?)

    }

    def multi(x: BigInt): BigInt = {
        x * x
    }

}

八、扩展-视图 View

基本介绍

Stream的懒加载特性,也可以对其他集合应用view方法来得到类似的效果,具有如下特点:
	1. view方法产出一个总是被懒执行的集合。
	2. view不会缓存数据,每次都要重新计算,比如遍历View时。

示例代码:

package com.lj.scala.GatherOperate

/**
  * @author Administrator
  * @create 2020-03-16
  */
object ViewTest01 {

    def main(args: Array[String]): Unit = {

        /*
          需求:找到1-100 中,数字倒序排列 和它本身相同的所有数。(1 2, 11, 22, 33 ...)
         */

        // 使用view来完成需求
        // 说明:使用view时,只有我们使用到结果的时候才会执行

        val res = (1 to 100).view.filter(reverseEquals)
        println("res:" + res)  // res:SeqViewF(...)  可以看到此时没有执行

        // 遍历
        for (ele <- res) {
            print(ele + "-")  // 1-2-3-4-5-6-7-8-9-11-22-33-44-55-66-77-88-99-
        }

    }


    def reverseEquals(num: Int): Boolean = {
//        println("reverseEquals被调用了!")
        num.toString.equals(num.toString.reverse)
    }

}

扩展-线程安全的集合

	SynchronizedBuffer
	SynchronizedMap
	SynchronizedPriorityQueue
	SynchronizedQueue
	SynchronizedSet
	SynchronizedStack

九、扩展-并行集合

基本介绍

1. Scala为了充分使用多核CPU,提供了并行集合(有别于前面的串行集合),用于多核环境的并行计算。
2.主要用到的算法有: 
	Divide and conquer : 分治算法,Scala通过splitters(分解器),combiners(组合器)等抽象层来实现,主要
原理是将计算工作分解很多任务,分发给一些处理器去完成,并将它们处理结果合并返回;
	Work stealin算法【学数学】,主要用于任务调度负载均衡(load-balancing),通俗点完成自己的所有任务
之后,发现其他人还有活没干完,主动(或被安排)帮他人一起干,这样达到尽早干完的目的。

应用案例

parallel(pærəlel并行)
	1. 打印1~5
		(1 to 5).foreach(println(_))
		println("------------------华丽分隔符----------------------")
		(1 to 5).par.foreach(println(_))
	2. 查看并行集合中元素访问的线程
		val result1 = (0 to 100).map{case _ => Thread.currentThread.getName}.distinct
		val result2 = (0 to 100).par.map{case _ => Thread.currentThread.getName}.distinct
		println(result2)   // 并行
		println("------------------华丽分隔符----------------------")
		println(result1)   // 非并行

十、扩展-操作符(了解即可)

操作符扩展示例代码:

package com.lj.scala.GatherOperate

/**
  * @author Administrator
  * @create 2020-03-16
  */
object OperatorTest01 {

    def main(args: Array[String]): Unit = {

        // 1. 如果想在变量名、类名等定义中使用语法关键字(保留字),可以配合反引号反引号.
        val `val` = 10
        // 2. 中置操作符:A 操作符 B 等同于 A.操作符(B)
        val n1 = 10
        val n2 = 20
        val sum01 = n1 + n2 // 30
        val sum02 = n1.+(n2)  // 30

        val monster = new Monster
        monster + 10
        monster.+(10)  // 中置操作
        println("money:" + monster.money)  // 20

        // 3. 后置操作符:A操作符 等同于 A.操作符,如果操作符定义的时候不带()则调用时不能加括号
        println(monster++)
        println(monster.++)
        println("money:" + monster.money)  // 22

        // 4. 前置操作符,+、-、!、~等操作符A等同于A.unary_操作符
        !monster
        println("money:" + monster.money)  // -22

        // 5. 赋值操作符,A 操作符= B 等同于 A = A 操作符 B  ,比如 A += B 等价 A = A + B
        var a = 10
        var b = 20
        a = a + b   // a = 30
        println(a)
        a += b  // a = 50
        println(a)

    }

}

class Monster {
    var money = 0

    // 对操作符进行重载(中置操作符)
    def +(n: Int): Unit = {
        this.money += n
    }

    // 对操作符进行重载(后置操作符)
    def ++(): Unit ={
        this.money += 1
    }

    // 对操作符进行重载(前置操作符)
    def unary_!(): Unit = {
        this.money = -this.money
    }
}

对以前的知识回顾,加深基础知识!
学习来自:北京尚硅谷韩顺平老师—尚硅谷大数据技术之Scala
每天进步一点点,也许某一天你也会变得那么渺小!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值