本节主要学习列表List集合的概念和相关操作。 List是Scala中最重要和最常使用的集合。
一、列表简介
- 列表是Seq的子类。Seq是一个有先次序的值的序列,具有一定长度的可迭代访问的对象。
- 列表分成 不可变列表List 和 可变列表ListBuffer
- List中可以存放任何数据类型, 但是和数组一样,同一个列表的元素必须是相同类型的。
- 继承关系
- 不可变列表List
List -> LinearSeq -> Seq -> Iterable -> Traversable - 可变列表ListBuffer
ListBuffer -> Buffer -> Seq -> Iterable -> Traversable
- 不可变列表List
- 比对Java
Scala中的List 和Java List 不一样,在Java中List是一个接口,真正存放数据是ArrayList/LinkedList,而Scala的List是一个object,可以直接存放数据。Scala在LinearSeq下面有LinkedList 、DoubleLinkedList 和 MutableList 这三个都不推荐使用,已经过时。
二、不可变列表List
默认情况下,Scala提供的List是不可变的,位于scala.collection.immutable.List。在Scala中列表要么是Nil(空表),要么就是head元素加上一个tail,而tail又是一个列表。
1.构建列表
所有的构建本质上都是来自两个基础的构建单元: 空列表"Nil" 和 中缀操作符"::" 。 中缀操作符"::"表示在列表的前面追加元素
- 常用的创建方式:
底层调用的Object List伴生对象的apply方法。 apply方法的底层也是由 “Nil” + “::” 定义的包装方法。val nums: List[Int] = List(1,2,3,4) // nums类型可以省略,会自动推断出来 val fruit: List[String] = List("apples","oranges","pears") val empty: List[Nothing] = List()
- "Nil" + “::” 创建方式
由于 “::” 右结合的。 A :: B :: C 底层会自动翻译成 A::(B::C) 所以可以去到圆括号定义,即:val nums = 1::(2::(3::(4::Nil)))
val nums = 1 :: 2 :: 3 :: 4 :: Nil
2.基本操作
对于列表的基本操作有以下三个方法:
- head: 返回列表的第一个元素
- tail: 返回列表中除了第一个元素之外的所有所元素(注意:不是返回最后一个元素。)
- isEmpty: 返回列表是否为空列表
nums.head nums.tail nums.isEmpty
3.初阶方法
如果一个方法不接收任何函数做为入参,就称作初阶(first-order)方法
-
①拼接列表":::"
"::"用于拼接元素。":::"用于拼接列表。":::"也是右拼接的,在右边列表的前面拼接列表。
xs ::: ys ::: zs 底层对应 xs ::: (ys ::: zs) 。List(1,2) ::: List(3,4,5) List(3,4,5) ::: List(1,2) //一定要注意 ::: 的结合顺序 右结合。
-
②获取长度:length
length: 计算链表的长度List(1,2,3).length
相比数组,列表在length操作上更耗资源。需要遍历整个列表,直到列表尾部。所以当需要判断列表是否为空的操作建议使用 xs.isEmpty 替换 xs.length == 0 -
③访问列表末端:init 和 last
last: 返回非空列表的最后一个元素。 对应 head
init: 返回非空列表除了最后一个元素意外的元素。 对应 tail
init和last也需要遍历整个列表,时间复杂为O(n) n为列表元素个数。 -
④反转列表:reverse
如果需要频繁的访问列表的尾部,可以考虑先将列表反转。reverse会创建一个新的列表,不会对原列表做修改。
-
⑤前缀和后缀:take、drop、splitAt
take: 返回列表的前n个元素
drop: 返回列表除了前n个元素以外的元素
splitAt:指定列表下标位置切分,返回切分后的两个列表
-
⑥元素选择:apply 、indices、iterator
apply方法:apply方法支持从任意位置选取元素,不对相对数组,列表很少使用
indices方法:返回包含列表所有有效的下标列表
iterator:通过迭代器访问列表元素 -
⑦扁平化列表:flatten
flatten方法:接收一个列表的列表,并将它扁平化,返回单个列表。
-
⑧列表拉链:zip 和 unzip
zip:接收两个列表,返回一个由对偶组成的列表。如果列表的长度不同,将丢失没有配对的元素。
zipWithIndex:将列表元素和对应下标组合成对偶列表
unzip:将对偶列表 转换回由列表组成的元组
-
⑨显示列表:toString 和 mkString
toString:返回列表的标准字符串表现形式
mkString:返回列表指定形式的字符串表现形式。mkString(pre,sep,post):pre:最前面的字符,sep:元素之间的分隔字符串, post:最后面的字符串
-
⑩列表转换: iterator、toArray、copyToArray
toArray: 将列表转换成数组
copyToArray: 将列表中的元素依次复制到目标数组的指定位置。copyToArray(arr,start): arr目标数组,start复制的起始索引位置
4.高阶方法
许多对列表的操作都有相似的结构,有一些模式反复的出现。例如对列表的元素做转换等等。在Java中通常需要通过固定写法的for循环或者while循环来实现。而Scala允许我们使用高阶方法来更加精简、直接地表达。
-
①列表映射: map 、 flatMap 、 foreach
xs map f : 将类型为List[T]的列表xs 和 类型为 T=>U的函数f 作为操作元。返 函数f应用到 xs每个元素后的列表。
这种写法避免了繁杂的for循环或者while循环。
xs flatMap f : 与map类似,flatma会有个扁平化的操作。下面清楚的显示了两者的区别:map返回的是列表的列表,flatMap返回的是所有元素拼接起来的单个列表。
xs foreach f: foreach 要求右操作元f 是一个过程(结果类型是Unit的函数),它是简单将过程f应用到每个元素。整个操作本身的结果类型也是Unit,并没有列表的类型结果被组装出来。例如列表求和:
-
②列表过滤:filter、partition、find 、takeWhile、dropWhile、span
xs filter p: 两个操作元分别是:类型为List[T]的xs列表 和 类型为 T=> Boolean的条件函数p。返回xs列表中 所有p(x)为true的元素x列表。
xs patition p :和filter类似,不过返回两个列表,其中一个是p(x)为true的元素列表,另一个是p(x)为false的元素列表。
即 xs patition p = (xs filter p , xs filter !p)
xs find p: 返回满足条件的第一个元素,而不是所有元素。返回值是一个可选值Option类型。
xs takeWhile p :返回列表中满足p的最长连续元素的列表(从第一个元素开始判断,不满足就是空)
xs dropWhile p: 返回列表中不满足p的最长连续元素的列表
xs span p: 将takeWhile和dropWhile操作合二为一。 等价于 (xs takeWhile p, xs dropWhile p)
-
③ 列表前置条件检查: forall 、exists
xs forall p : 如果xs列表的所有元素都满足p 就返回true。
xs exists p: 如果xs列表存在至少一个元素满足p 就返回true
-
④ 列表折叠: 左折叠 /: 和右折叠 :\
- 左折叠 " (z /: xs)(op)" 涉及三个对象: 起始值z、列表xs、二元操作函数op 。折叠的结果是以z为前缀,对列表的元素依次连续应用op。
示例:拼接字符串
这样拼接出来的字符串前面会有一个空格,因为初始值给的是" ",可以这样解决
- 右折叠:"(xs :\ z)(op)" 三个操作元和左折叠类似。折叠的结果是以z为后缀,对列表的元素倒序依次连续应用op。
示例:拼接字符串
- 使用左右折叠的方法名:foldLeft 和 foldRight
折叠操作符可能记不清楚,可以使用对应的方法名
- 左折叠 " (z /: xs)(op)" 涉及三个对象: 起始值z、列表xs、二元操作函数op 。折叠的结果是以z为前缀,对列表的元素依次连续应用op。
-
⑤列表排序:sortWith
xs sortWith before: 对列表xs中的元素进行排序。 before 是比较两个元素的函数。表达式 x before y 对于在预期排序中 x 应该出来在y之前的情况应该返回true。
5.List 对象的方法
到目前为止,介绍的所有操作都是 List 类的方法,因此我们其实是在每个具体的列表对象上调用方法。还有一些方法是定义在全局可访问对象scala.list 上的,这是 List 类的伴生对象 。某些操作是用于创建列表的工厂方法,另一些是对特定形状的列表进行操。这两类方法在本节都会介绍。
- ①从元素创建列表: List.apply
List(l, 2, 3)这样的字面量只不过是简单地将对象LIst 应用到元素1,2,3而已。也就是说,它跟 List.apply(1,2,3)是等效的:
- ②创建数值区间:List.range
List.range: 它创建一个包含一个区间的数值的列表。
List.rang( from,until): 创建一个包含了从 from 开始递增到 **until-1的列表。**不含有until List.rang(from,until,step): 创建一个包含了从 from 开始,递增间隔为 step ,到 until-1的列表
- ③创建相同元素的列表:List.fill
fill : 创建包含零个或多个同一个元素拷贝的列表。它接收两个参数:要创建的列表长度和需要重复的元素。两个参数各自以不同的参数列表给出。
如果我们给fill的参数多于1个,那么它就会创建多维的列表。
- ④拼接多个列表:List.concat
concat: 将多个列表拼接在一起。要拼接的列表通过 concat 的直接入参给出。
三、可变列表
ListBuffer是可变的list集合,可以添加,删除元素,ListBuffer属于序列。
List 类提供对列表头部的快速访问,对尾部访问则没那么高效。当需要往列表尾部追加元素来构建列表时,通常要考虑反过来往头部追加元素,追加完成以后,再调用 reverse 来获得想要的顺序。
另一种避免 reverse 操作的可选方案是使用 ListBuffer。ListBuffer 提供了常量时间的往后追加和往前追加的操作。可以调用 ListBuffer的toList 来获取最终的List。
1.常用方法
- ①创建ListBuffer:使用可变列表需要先导入包 scala.collection.mutable.ListBuffer
- ②追加元素:+= 向后追加 +=:向前追加
- ③ 转为不可变列表: toList