zoukankan      html  css  js  c++  java
  • Programming In Scala笔记-第十六章、Scala中的List

      本章主要分析Scala中List的用法,List上可进行的操作,以及需要注意的地方。

    一、List字面量

      首先看几个List的示例。

    val fruit = List("apples", "oranges", "pears")
    val nums = List(1, 2, 3, 4)
    val diag3 = 
      List(
        List(1, 0, 0),
        List(0, 1, 0),
        List(0, 0, 1)
      )
    val empty = List()

      代码运行结果略。
      
      与arrays不同的是,List是imutable的,即List中的元素不能被修改。其次List具有递归结构,比如linked list,而arrays是连续的。
      有关List中的元素不能被修改,可以看下面代码。

    fruit = "banana"

      运行结果:
      这里写图片描述

    二、List类型

      List变量中的元素都有相同的类型,比如List[T]表示该List对象中所有元素的类型都是T,可以在定义变量时加一个显示的类型声明,如下所示

    val fruit: List[String] = List("apples", "oranges", "pears")
    val nums: List[Int] = List(1, 2, 3, 4)
    val diag3: List[List[Int]] =
      List(
        List(1, 0, 0),
        List(0, 1, 0),
        List(0, 0, 1)
      )
    val empty: List[Nothing] = List()

      运行结果略。
      
      List类型是协变的。即,如果ST的子类,那么List[S]List[T]的子类。

    三、构造List对象

      List对象由两个关键字生成,一个是Nil另一个是::。其中Nil代表空的list,::操作符表示将该元素追加到list的前面。所以,前面的代码还可以写成

    val fruit = "apples" :: ("oranges" :: ("pears" :: Nil))
    val nums = 1 :: (2 :: (3 :: (4 :: Nil)))
    val diag3 = (1 :: (0 :: (0 :: Nil))) ::
                (1 :: (0 :: (0 :: Nil))) ::
                (1 :: (0 :: (0 :: Nil))) :: Nil
    val empty = Nil       

      ::操作符从后往前匹配,即A :: B :: C等价于A :: (B :: C),所以,对上面代码中的变量fruit可以写成

    val fruit = "apples" :: "oranges" :: "pears" :: Nil

    四、List上的基本操作

      List上的所有操作,底层都可以表示成以下三个:
    head:获取list中的第一个元素
    tail:获取list中除了第一个元素之外的其他元素组成的新list
    isEmpty:如果当前list为空,返回true

      需要注意的是headtail方法只能作用在非空list上,否则会报错,如下所示

    Nil.head

      结果如下,
      这里写图片描述

      下面使用者三个方法,实现一个List[Int]类型变量的插入排序代码(升序)。

    def isort(xs: List[Int]): List[Int] = 
      if (xs.isEmpty) Nil
      else insert(xs.head, isort(xs.tail))
    
    def insert(x: Int, xs: List[Int]): List[Int] = 
      if (xs.isEmpty || x <= xs.head) x :: xs
      else xs.head :: insert(x, xs.tail)

      在方法isort中,如果xs为空,直接返回Nil,否则将xs的第一个元素xs.head调用insert方法插入到xs.tail中。
      在方法insert中,如果待插入的xs为空,或者待插入变量xxs的第一个元素还小,则直接插在xs前面。否则将xs的第一个元素放在最前面,继续排列xxs除第一个元素之外的其他元素。

    五、List模式

      List也可以作用在模式匹配的情况下。比如下面代码

    val List(a, b, c) = fruit

      运行结果如下,直接将fruit中的内容依次赋给变量a, b, c
      这里写图片描述

      使用上面的代码,表示明确知道fruit变量中的元素个数为3。否则会出现元素个数不匹配的报错,如下

    val List(x, y) = fruit

      运行结果如下,
      这里写图片描述

      这时如果仍然想要使用模式匹配,可以使用::操作符,如下所示

    val a :: b :: c = fruit
    val a :: b :: c :: d = fruit
    val a :: b :: c :: d :: e = fruit

      运行结果如下,会将fruit变量中的第一个元素赋给变量a,第二个元素赋给b,然后将剩下的以List形式赋给变量c
      但是可赋值的变量个数不能超过List中的元素个数加一,在这种情况下最后一个变量d的内容为一个空的List
      如果变量个数超过List中的元素个数加一,就会报错。
      这里写图片描述

      使用这个特性改造一下插入排序代码。

    def isort(xs: List[Int]): List[Int] = xs match {
      case List() => List()
      case x :: xs1 => insert(x, isort(xs1))
    }
    
    def insert(x: Int, xs: List[Int]): List[Int] = xs match {
      case List() => List(x)
      case y :: ys => if (x <= y) x :: xs
                      else y :: insert(x, ys)
    }

      isort方法中,如果传入的xs是一个空List直接返回一个空List,否则,将第一个元素赋值给变量x,调用insert方法插入到剩余元素组成的xs1中。
      insert方法中,如果待插入的xs为空,直接返回包含一个元素的List。否则,比较待插入元素xxs的第一个元素y的大小,将值小的放在前面,组成一个新的List

    六、List上的一阶操作(First-order)

    1、:::操作符,合并两个List对象

      :::操作符作用于两个List对象上,xs ::: ys表达式的结果是得到一个新的List对象,该对象包含xsys中的全部元素,并且xs在前。

    List(1, 2) ::: List(3, 4, 5)
    List() ::: List(1, 2, 3)
    List(1, 2, 3) :: List(4)

      运行结果如下,
      这里写图片描述

      :::操作符和::类似,也是从右向左执行的。所以xs ::: ys ::: zs等价于xs ::: (ys ::: zs)

    2、使用分治原则自定义:::功能

      前面已经看到了Scala中实现了连接两个List对象的操作:::,如果想要手动实现该功能,比如定义一个append方法,作用与:::相同,应该怎么做?
      首先看一下append方法的定义,

    def append[T](xs: List[T], ys: List[T]): List[T]

      接下来使用分治策略来实现该方法的方法体。前面可以看到,使用List模式匹配能够将一个List对象划分成单个元素或者子List的情况。

    def append[T](xs: List[T], ys: List[T]): List[T] = 
      xs match {
        case List() =>
        case x :: xs1 =>
      }

      对于第一个分支,如果xs为空,那么直接返回ys即可,即

    case List() => ys

      对于第二个分支,则继续将其切割成小的元素,

    def append[T](xs: List[T], ys: List[T]): List[T] =
      xs match {
        case List() => ys
        case x :: xs1 => x :: append(xs1, ys)
      }

    3、length方法,获取list中元素个数

      length方法,获取当前list的长度。所谓长度,是指当前list中有多少个元素。对于List来说,计算其长度,是一个比较耗费时间的操作,时间复杂度与List中的元素个数成正比,因为需要遍历其中每一个元素才能得到List的长度。
      所以,xs.isEmptyxs.length == 0的效率高,虽然两者的作用是相同的。
      下面看一个示例,

    List(1, 2, 3).length

      结果如下,
      这里写图片描述

    4、initlast方法,访问List的尾部

      对应前面的head方法,List上有一个last方法,用于访问该list中最后一个元素。如下所示,

    List('a', 'b', 'c', 'd', 'e').last

      对应于tail方法,init方法获取除去最后一个元素之外其他元素组成的新的List对象,如下所示,

    List('a', 'b', 'c', 'd', 'e').init

      运行结果如下,
      这里写图片描述

      需要注意的是,这两个方法不能作用于空的List上,否则会报错。并且,initlast方法会比tailhead方法是效率低,因为操作List后面的元素,需要遍历整个List对象才能实现。因此,在生成List对象时,最好将经常访问的元素置于List前面。

    5、reverse方法,翻转List中的元素顺序

      该方法会将List中的元素顺序翻转得到一个与原List顺序相反的新List对象,而不改变之前那个List对象的内容,如下所示,

    val abcde = List('a', 'b', 'c', 'd', 'e')
    abcde.reverse

      运行结果如下,
      这里写图片描述

      将reverse方法和前面的init, last, head, fail方法结合,有以下一些规律:
    - xs.reverse.reverse结果为xs
    - xs.reverse.init结果为xs.tail.reverse
    - xs.reverse.tail结果为xs.init.reverse
    - xs.reverse.head结果为xs.last
    - xs.reverse.last结果为xs.head

    6、droptakesplitAt方法,前缀和后缀

      take方法,xs take n返回xs对象的前n个元素,形成一个新的List对象。
      drop方法,xs drop n返回xs对象除了前n个元素之外的其他元素组成的新List对象。

    abcde take 2
    abcde drop 2

      结果如下,
      这里写图片描述

      而splitAt方法,xs splitAt n表示从n个元素处,将xs分割成两个List对象。等价于xs take n, xs drop n

    abcde splitAt 2
    val (x, y) = abcde splitAt 2

      运行结果如下,
      这里写图片描述

    7、apply方法和下标,获取指定元素

      List也可以使用下标访问指定元素abcde(2)List对象的apply`方法,等价于上面所示的访问指定位置的元素,所以下面代码和上面的是等价的

    abcde apply 2

      运行结果如下,
      这里写图片描述

      indicies方法,获取当前List对象中所有元素的下标,

    abcde.indices

      运行结果如下,
      这里写图片描述

    8、flatten方法,展平List[List]对象中的所有元素

      这个方法可以将List[List]结构的对象中所有元素展开,返回一个包含所有子List元素的新的List对象,如下,

    List(List(1, 2), List(3), List(), List(4, 5)).flatten

      运行结果如下,
      这里写图片描述

      注意,该方法只能应用于List[List]的对象上,直接由List对象调用,会报错

    List(1, 2, 3).flatten

      结果如下,
      这里写图片描述

    9、zipunzip方法,组合两个list对象为pairs形式

      直接看示例吧。

    abcde.indices zip abcde

      结果如下,将前一个List对象的每一个元素与后一个List对象的每一个元素组合成pairs
      这里写图片描述

      如果前后两个List对象长度不一致,将会返回较短长度的那个结果,

    abcde zip List(1, 2, 3)
    List(1, 2, 3) zip abcde

      结果如下,
      这里写图片描述

      在这里还有一个特殊的方法zipWithIndex,作用是将该List对象中的每一个元素与其下标进行zip操作。

    abcde.zipWithIndex

      运行结果如下,
      这里写图片描述

      unzip方法的作用与zip方法相反,将一个pairs形式的List拆分成两个List对象,

    val zipped = abcde zip List(1, 2, 3)
    zipped.unzip

      运行结果如下,
      这里写图片描述

    10、toStringmkString方法,将list中元素拼接成字符串

      toString方法略。
      mkString方法可以传入一个连接符,用该连接符将List对象中的各个元素拼接起来最终形成一个字符串。也可以指定其前缀和后缀。

    abcde mkString "-"
    abcde mkString ("[", "-", "]")

      运行结果如下,
      这里写图片描述

    11、iteratortoArraycopyToArray方法,转换List对象类型

    (1)ListArray互转
      List对象转为Array

    val arr = abcde.toArray

      ArrayList

    arr.toList

      运行结果如下,
      这里写图片描述

    (2)copyToArray方法
      该方法可以将List对象中的元素复制到指定Array对象的指定位置,比如下面代码中,将List(1, 2, 3)中的三个元素插入到arr2对象的第3~5位置上。然后arr2这个Array中的元素个数和内容发生变化。

    val arr2 = new Array[Int](10)
    List(1, 2, 3) copyToArray (arr2, 3)

      运行结果如下,
      这里写图片描述

    (3)inerator方法
      获得当前List对象的迭代器,可以用此迭代器访问下一个元素。

    val it = abcde.iterator
    it.next
    it.next

      运行结果如下,
      这里写图片描述

    七、List上的高阶操作(Higher-order)

    1、map, flatMap, foreach方法,处理List中的每个元素

    (1)map方法
      map方法接收一个函数参数,将该函数作用在List的每一个元素上,得到的结果组合成一个新的List对象。

    List(1, 2, 3) map (_ + 1)
    val words = List("the", "quick", "brown", "fox")
    words map (_.length)
    words map (_.toList.reverse.mkString)

      运行结果如下,
      这里写图片描述

    (2)flatMap方法
      和map方法有点类似,区别在于flatMap方法接收到的函数对单一元素作用后得到多个结果,会将这些结果返回到一个List对象中。和前面的flatten方法有点类似。

    words map (_.toList)
    words flatMap (_.toList)

      运行结果如下,传入的方法是将当前元素拆分成List对象,所以一个元素经过map后会返回一个List对象,最终形成List[List]的结构。但是flatMap方法会将map得到的每一个List打散,取出其中每一个元素,组合成一个新的List对象并返回。
      这里写图片描述

    (3)foreach方法
      该方法和map方法类似,也是接收一个函数参数,这个函数参数会作用在List中的每一个元素上。但是这个函数的返回值为Unit,所以其副作用仅仅是比如打印其中的每一个元素,或者改变某个变量的值,比如下面例子中的求和。

    List(1, 2, 3, 4, 5) foreach (println)
    var sum = 0
    List(1, 2, 3, 4, 5) foreach (sum += _)

      运行结果如下,
      这里写图片描述

    2、filter, partition, find, takeWhile, dropWhile, span方法,过滤List中的元素

    (1)filter方法
      传入一个函数参数,最终得到一个新的List对象,包含原List中使该函数参数值为true的元素。

    List(1, 2, 3, 4, 5) filter (_ % 2 == 0)     // 返回所有偶数
    words filter (_.length == 3)     // 返回所有字符长度为3的元素

      运行结果如下,
      这里写图片描述

    (2)partition方法
      该方法是filter方法的加强版。filter只会返回时函数参数值为true的一个新List。而partition方法,会同时返回两个List,第一个为函数参数为true的所有元素,第二个为函数参数为false的所有元素。

    List(1, 2, 3, 4, 5) partition (_ % 2 == 0)

      运行结果如下,
      这里写图片描述

    (3)find方法
      和filter方法类似,但是find方法只返回List中第一个满足该函数表达式的元素,而不是返回一个List对象。返回值类型为Some(x)或者None

    List(1, 2, 3, 4, 5) find (_ % 2 == 0)
    List(1, 2, 3, 4, 5) find (_ <= 0)

      运行结果如下,
      这里写图片描述

    (4)takeWhile方法
      xs takeWhile p获取xs中最前面的所有满足p表达式的元素。

    List(1, 2, 3, -4, 5) takeWhile (_ > 0)

      运行结果如下,直到遇到-4时不满足该表达式,尽可能长的从第一个元素取满足该表达式的元素。
      这里写图片描述

    (5)dropWhile方法
      和takeWhile方法正好相反,尽可能的从第一个元素删除所有满足表达式的元素。

    List(1, 2, 3, -4, 5) dropWhile (_ > 0)

      运行结果如下,
      这里写图片描述

    (6)span方法
      是takeWhiledropWhile两个方法的结合形式。有点类似于splitAt方法综合了takedrop方法一样。
      xs span p等价于xs takeWhile p, xs dropWhile p

    List(1, 2, 3, -4, 5) span (_ > 0)

       运行结果如下,
       这里写图片描述

    3、forall, exists方法,判断满足条件元素是否存在

      xs forall p,其中xs是一个List类型对象,p是一个判断表达式,只有当xs中每一个元素均使p的结果为true时,整个结果才返回true。而xs exists p表示xs中只要存在任意一个元素使p的结果为true,整个结果返回true

    val diag3 = (1 :: 0 :: 0 :: Nil) ::
                (0 :: 1 :: 0 :: Nil) ::
                (0 :: 0 :: 1 :: Nil) :: Nil
    def hasZeroRow(m: List[List[Int]]) = 
        m exists (row => row forall (_ == 0))
    
    hasZeroRow(diag3)

      上面代码遍历diag3变量,如果diag3中某个子List的值全部为0,则结果为true。运行结果如下
      这里写图片描述

    4、/:, :方法,折叠List

      所谓的折叠List即对List中的元素进行汇总。比如说如果有一个函数sum(List(a, b, c))接收一个List参数,然后将该List中各元素的和进行累加,最终得到一个和,这个过程就可以称为对List的折叠。
      Scala中提供了两个方法实现折叠功能
    (1)/:方法,左折叠
      对于左折叠,完整的调用形式是(z /: xs) (op),包含三个部分,其中z是一个初始值,xs是一个List对象,op是一个二元操作表达式。最终结果是将表达式opz开始,依次从左到右的遍历xs中的每个元素。

    (z /: List(a, b, c)) (op)     等价于     op(op(op(z, a), b), c)

      用图表示的话,如下所示,
      这里写图片描述

      下面举个例子,

    val words = List("the", "quick", "brown", "fox")
    ("" /: words) (_ + " " + _)

      运行结果如下,最终返回一个字符串
      这里写图片描述

    (2):方法,右折叠
      右折叠与左折叠功能类似,只不过是从右往左遍历List中的元素。

    (List(a, b, c) : z) (op)     等价于     op(a, op(b, op(c, z)))

      用图表示,
      这里写图片描述

      最后,用左折叠定义一个求数组和的方法,

    def sum(xs: List[Int]): Int = 
         (0 /: xs) (_ + _)
    sum(List(1, 2, 3))

      运行结果如下,
      这里写图片描述

    5、sortWith方法,排序List中的元素

      xs sortWith before表达式中,xs是一个List对象,before是一个可用比较两个元素值的函数表达式。这个表达式的含义是,对xs中的元素进行排序,根据before方法来确定xs中哪个元素在前,哪个元素在后。

    List(1, -3, 4, 2, 6) sortWith (_ < _)
    words sortWith (_.length > _.length)

      运行结果如下,
      这里写图片描述

    八、List对象上的方法

    1、List.apply方法,使用传入的元素生成List对象

      根据给定元素生成一个新的List对象。

    List.apply(1, 2, 3)

      结果,
      这里写图片描述

    2、List.range方法,生成包含范围内值的List

      给定一个范围,根据起始值或者阶跃单位量生成一个List对象,

    List.range(1, 5)     // 从1到5
    List.range(1, 9, 2)     // 从1到9,每隔2生成一个元素
    List.range(9, 1, -3)     // 从9到1,每个-3生成一个元素

      运行结果如下,
      这里写图片描述

    3、List.fill方法,根据给定元素和个数生成List

      示例,

    List.fill(5)('a')     // 生成一个List对象,其中的元素为5个a字符
    List.fill(3)("hello")     // 生成一个List对象,其中元素为3个hello字符串
    List.fill(2, 3)('b')     // 也可以生成多维List,这里展示一个二维

      运行结果如下,
      这里写图片描述

    4、List.tabulate方法,根据给定维度动态生成List元素值

      该方法与List.fill有点类似,也是传入两个参数列表,第一个仍然是维度及长度,第二个参数列表中传入一个函数表达式而不是List.fill中那样传入一个固定值。这样,List.tabulate方法中的元素值就是动态变化的,由第二个参数列表中的表达式计算得到,改表达式的输入参数为当前元素的下标值。

    val xs = List.tabulate(5)(n => "xs" + n)     // 在每个下标前拼接一个xs
    val squares = List.tabulate(5)(n => n * n)     // 每个元素取其下标的平方值
    val multiplication = List.tabulate(5, 5)(_ * _)     // 每个元素取其纵横下标的乘积

      运行结果如下,
      这里写图片描述

    5、List.concat方法,拼接多个List

      将多个List对象中的元素拼接到一起,可以拼接元素类型不同的List对象,形成一个新的List对象。类似于前面的flatten方法。

    List.concat(List('a', 'b'), List('c'))
    List.concat(List('a', 'b'), List(1))
    List.concat()

      运行结果如下,
      这里写图片描述

    九、zipped方法,处理多个List对象

      通过前面介绍的zip方法,可以将两个List对象组合到一起,形成元素为pairs对的新List对象。Scala中还提供了一个zipped方法,在该方法之后就可以同时处理两个List对象中的元素了。

    (List(10, 20), List(3, 4, 5)).zipped.map(_ * _)

      运行结果如下,map方法中的两个_分别表示第一个和第二个List对象中对应位置上的元素。
      这里写图片描述

      zipped方法后,还可以使用forall或者exists方法,如下所示

    (List("abc", "de"), List(3, 2)).zipped.forall(_.length == _)
    (List("abc", "de"), List(3, 2)).zipped.exists(_.length != _)

      运行结果如下,
      这里写图片描述

      这里需要注意的是zipped方法,只取较短那个List的长度,超过该长度的长List中的元素会被丢弃。

  • 相关阅读:
    【题解】 保镖 半平面交
    【题解】 CF1492E Almost Fault-Tolerant Database 暴力+复杂度分析
    【题解】 闷声刷大题 带悔贪心+wqs二分
    【题解】 「WC2021」表达式求值 按位+表达式树+树形dp LOJ3463
    EasyNVR及EasyRTC平台使用Go语言项目管理GoVendor和gomod的使用总结
    一天一个开发技巧:如何基于WebRTC建立P2P通信?
    HTML5如何实现直播推流?值得学习一下!
    java后端学习-第一部分java基础:Scanner的基本使用
    java后端学习-第一部分java基础:三元运算符、运算符优先级、标识符、关键字和保留字
    java后端学习-第一部分java基础:赋值运算符
  • 原文地址:https://www.cnblogs.com/wuyida/p/6300212.html
Copyright © 2011-2022 走看看