Scala集合:Seq

Scala 有一个丰富的集合库,包含很多不同类型的集合。此外,所有的集合都暴露出相同的接口。因此,一旦熟悉了一个Scala 集合,就可以很容易地使用其它集合类型。

Scala中的集合体系主要包括:Iterable、Seq(IndexSeq)、Set(SortedSet)、Map(SortedMap)。其中Iterable是所有集合trait的根trait。实际上Seq、Set、和Map都是子trait。

  • Seq:是一个有先后次序的值的序列,比如数组或列表。IndexSeq允许我们通过它们的下标快速的访问任意元素。举例来说,ArrayBuffer是带下标的,但是链表不是。
  • Set:是一组没有先后次序的值。在SortedSet中,元素以某种排过序顺序被访问。
  • Map:是一组(键、值)对。SortedMap按照键的排序访问其中的实体。

Scala中的集合是分成可变和不可变两类集合的。可变集合的内容或引用可以更改,不可变集合不能更改。这两种类型的集合分别对应Scala中的scala.collection.mutable和scala.collection.immutable两个包。

Scala明确区分了不可变和可变集合数据类型。不可变集合驻留在scala.collection.immutable包中,可变集合分别在scala.collection.mutable包中组织。毫无疑问,Scala支持不可变集合,并在不需要任何显式导入语句的情况下将它们添加到作用域中。

Scala编程语言提供了大量开箱即用的数据结构,可以将它们分组为Array、Map、List、Tree、Set和Queue。Scala中常用的集合如下表所示:

集合 描述
List 是一个相同类型元素的线性序列
Set 类型相同但没有重复的元素的集合
Map 键/值对的集合
Tuple 不同类型但大小固定的元素的集合
Option 包含0个或1个元素的容器

下面的代码简要描述了这几种集合形式:

val booksList = List("Spark","Scala","Python", "Spark")
val booksSet = Set("Spark","Scala","Python", "Spark")
val booksMap = Map(101 -> "Scala", 102 -> "Scala")
val booksTuple = new Tuple4(101,"Spark", "xinliwei","机械工业出版社",65.50)	

Seq

序列表示一个以特定顺序排列的元素集合。因为该元素有个定义好的顺序,所以可以按照它们在集合中的位置进行访问。例如,可以请求序列中第n个元素。

Seq下包含了Range、ArrayBuffer、List等子trait。其中Range就代表了一个序列,通常可以使用“1 to10”这种语法来产生一个Range。 ArrayBuffer就类似于Java中的ArrayList。

序列有三个实现类,分别是Array、List和Vector。

1)Array(数组)

在Scala中,数组分为不可变数组和可变数组。不可变数组的实现类为Array,可变数组的实现类为ArrayBuffer。Array是一个索引的元素序列,有一个固定的长度。它是一个可变的数据结构;可以修改数组中的元素,不过,在一个Array被创建以后,不可以再向Array添加元素。Array中的元素有一个基于零的索引。要获得或更新一个元素,在括号内指定其索引。例如:

val arr = Array(10,20,30,40);
arr(0) = 50;
val first = arr(0);

在一个数组上的基本操作包含:

  • 根据索引提取一个元素
  • 使用索引更新一个元素

在下面的示例代码中,演示了数组的创建及操作:

// 1.Array 创建
var arr1 = new Array[Int](10)
var arr2 = Array(1, 5, 3, 7)
    
//  2.ArrayBuffer 创建
var arr3 = new ArrayBuffer[Int]()
var arr4 = ArrayBuffer(10, 50, 30, 70)
    
//  3.共同方法
println("---------Array 和 ArrayBuffer 共同方法----------");
println(arr2.sum) 	// 求和:16
println(arr2.max) 	// 求最大值:7
println(arr2.min) 	// 求最小值:1
println(arr2.mkString("|")) 		// 元素连接:结果是 1|5|3|7
println(arr2.sorted.toBuffer) 	// 1 3 5 7
println(arr2.reverse.toBuffer) 	// 7 3 5 1 
println(arr4.toArray.getClass)	// class [I
println(arr2.toBuffer.getClass) 	// class scala.collection.mutable.ArrayBuffer
println(Array(("cn", "china"), ("fr", "french")).toMap)   // 将数组(元素是元组类型)转换为Map
    
//  4.ArrayBuffer 的独有方法
println("---------ArrayBuffer 独有方法----------");
arr4 += 20			// 增加一个元素
println(arr4) 			// 10 50 30 70 20

arr4 ++= Array(50, 60) 	// 增加一个数组集合
println(arr4)			/ /10 50 30 70 20 50 60

arr4.trimEnd(3) 		// 删除最后 3 个元素 
println(arr4)			// 10 50 30 70

arr4.insert(2, 28, 29) 	// 在索引2处插入两个元素
println(arr4)			// 10 50 28 29 30 70

arr4.remove(2, 3) 		// 在索引2处删除三个元素
println(arr4)			// 10 50 70

arr4.clear()			// 清空数组
println(arr4)
    
//  5.遍历
println("-------5.遍历--------");
var arr5 = Array(1, 5, 3, 7)

// i) 直接取值
for (i <- arr5) {
   println(i) 			// 1 5 3 7
}

// ii)通过下标
for (i <- 0 to arr5.length - 1) {
    println(arr5(i)) 		// 1 5 3 7
}
    
    
// 多维数组
println("--------多维数组------")
var arr6 = Array(Array(1, 3, 5), Array(2, 4, 6))
for (i <- arr6) {   
    for(j <- i) {
       print(j + " ")
    }
    print("\n")
}

下面的代码演示了在数组上的求和与排序:

// 1、求和与排序
println(Array(1,7,2,9).sum)

println("------------")
// 2、求最大值
println(ArrayBuffer("Mary","had","a","little","lamb").max) 

println("------------")
// 3、排序
// 升序
val b = ArrayBuffer(1,7,2, 9)
val bSorted = b.sorted        //1,2,7,9
b.sortWith(_<_).foreach(println)

println("------------")
// 降序
b.sortWith(_>_).foreach(println)

println("------------")
// 4、显示数组内容
println(b.mkString(" And ")) 			// 使用指定分隔符连接数组中的元素为字符串
println(b.mkString("<",",",">")) 			// <1,7,2,9>,指定前缀、分隔符、后缀

2)List(列表)

在Scala中,列表分为不可变的和可变的。不可变列表的实现类为List,可变数组的实现类为ListBuffer。List 是元素的一个线性序列。它是一个递归数据结构,不像数组,数组是一个扁平数据结构。另外,与数组不同,它是一个不可变的数据结构;List 被创建以后,不可以被修改,其大小以及元素不能被改变。List 是Scala 中最常用的数据结构之一。

Scala的List列表类似于数组,但是它们也有所不同:List列表是不可变的,值一旦被定义了就不能改变,其次列表具有递归的结构(也就是链接表结构)而数组不是。

虽然可以通过元素的索引来访问list 中的元素,但是通过索引访问元素不是一个高效的数据结构。访问时间与元素在list 中的位置成正比。

Scala的List是作为Linked List实现的,并提供有head、tail和isEmpty方法。因此,在List上的大多数操作涉及递归算法,将list拆分为head和tail部分。

创建List有两种方式:像Array一样,或者使用 :: 连接运算符。也可以将其他集合转换为List集合。下面的代码演示了创建一个列表的一些方式:

val xs = List(10,20,30,40);
val ys = (1 to 100).toList;
val zs = Array(1,2,3).toList;

// 创建一个空的List
val empty: List[Nothing] = List()

// 也可用Nil创建空的列表
val empty = Nil

// 创建图书列表
val books: List[String] = List("Scala从入门到精通", "Groovy从入门到精通", "Java从入门到精通")

// 使用tail Nil 和 :: 来创建图书列表
val books = "Scala从入门到精通" :: "Groovy从入门到精通" :: "Java从入门到精通" :: Nil

books.head     // 第一个元素
books.tail     // 除了第一个元素

【示例】不可变List列表的使用示例。

object ListDemo {
  def main(args: Array[String]): Unit = {
    println("1: 初始化一个不可变的List")
    // val list1: List[String] = List("苹果","香蕉","葡萄干")
    val list1 = List("苹果","香蕉","葡萄干")   // 完全可以使用类型推断
    println(s"list1中的元素有 = $list1")

    println("\n2: 在特定的索引访问元素的不可变列表")
    println(s"Element at index 0 = ${list1(0)}")
    println(s"Element at index 1 = ${list1(1)}")
    println(s"Element at index 2 = ${list1(2)}")

    println("\n3: list是一个递归数据结构")
    println(s"head of list is = ${list1.head}")
    println(s"tail of list is = ${list1.tail}")

    println("\n4: 使用 :+ 在不可变列表末尾添加元素")
    // val list2: List[String] = list1 :+ "草莓"
    val list2 = list1 :+ "草莓"
    println(s"在末尾追加元素,使用 :+ = $list2")

    println("\n5: 使用 +: 在不可变列表的前面添加元素")
    // val list3: List[String] = "菠萝" +: list1
    val list3 = "菠萝" +: list1
    println(s"在不可变列表前端添加元素,使用 +: = $list3")

    println("\n6: 使用 :: 将两个不可变列表添加在一起")
    // val list4: List[Any] = list1 :: list2
    val list4 = list1 :: list2
    println(s"添加两个列表一起,使用 :: = $list4")

    println("\n7: 使用 ::: 添加两个不可变列表在一起")
    // val list5: List[String] = list1 ::: list2
    val list5 = list1 ::: list2
    println(s"添加两个列表一起,使用 ::: = $list5")

    println("\n8: 初始化一个空的不可变列表")
    // val emptyList: List[String] = List.empty[String]
    val emptyList = List.empty[String]
    // val emptyList = List()
    // val emptyList = Nil
    println(s"Empty list = $emptyList")
  }
}

可以使用iterator方法对集合进行迭代。iterator.hasNext方法用于查找集合是否具有进一步的元素,而iterator.next方法用于访问集合中的元素。下面的代码描述了iterator方法:

val booksList = List("Spark","Scala","Python","Spark")
// val booksList = Array("Spark","Scala","Python","Spark")

def iteratingList(booksList:List[String]){
    val iterator = booksList.iterator
    while(iterator.hasNext){
        println(iterator.next)
    }
}

iteratingList(booksList)

执行以上代码,输出结果如下所示:

Spark
Scala
Python
Spark

在一个list 上的基本操作包括:

  • head方法:提取第一个元素;
  • tail方法:提取除第一个元素之外的所有元素;
  • isEmpty方法:判断一个list 是否为空。如果一个list 是空的,该方法返回true。

下面的代码演示了List上的常用操作:

// 1、在Scala中,列表要么是Nil(及空表),要么是一个head元素加上一个tail,而tail又是一个列表

val list = List(1, 2, 3, 4, 5)
list.head
list.tail
list.isEmpty
list == Nil

// 下面是一个使用递归求list集合中和的例子:
def recursion(list:List[Int]):Int = {
    if(list.isEmpty) {
        return 0  
    }
    list.head + recursion(list.tail)
}

// 增
/*  A.++(B)  --> 在列表A的尾部添加另外一个列表B,组成一个新的列表
 *  A.++:(B) --> 在列表A的首部添加另外一个列表B,组成一个新的列表
 *  A.:::(B) --> 在列表A的首部添加另外一个列表B,组成一个新的列表
 *  ------
 *  A.:+ (element) -->在列表A的尾部添加一个element,组成一个新的集合
 *  A.+: (element) -->在列表A的首部添加一个element,组成一个新的集合
 *  A.:: (element) -->在列表A的首部添加一个element,组成一个新的集合
 */
val left = List(1, 2, 3, 4)
val right = List(5, 6, 7)

left.++(right)
left.++:(right)
left.:::(right)
left.:+(10)
left.+:(10)
left.::(10)

// 删除
// drop(n)         --->删除list的前n个元素(首部开始删除)
// dropRight(n)    --->删除list的后n个元素(尾部开始删除)
// dropWhile(p: A => Boolean)  --->逐个匹配去除符合条件的元素,直到不符合条件,之后的元素不再判断
val list = List(1, 2, 3, 4, 5, 6, 7)
list.drop(2)
list.dropRight(3)
list.dropWhile(_ <= 3)
list.dropWhile(_ > 3)    // 第一个元素就不符合条件,后面的不再判断,所以一个也没有删除

val list = List(1, 2, 13, 14, 15, 6, 7)
list.take(5)
list.takeWhile(_ <= 3)
list.takeWhile(_ > 3)   // 获取的是最满足条件的最长前缀
list.mkString
list.mkString(";")
list.count(_ % 2 == 0)

val fruit = "apples" :: ("oranges" :: ("pears" :: Nil))  // ::操作符从给定的头和尾部创建一个新的列表

val list1 = List(1, 2, 3)
val list2 = List(4, 5, 6)

list1 ++ list2   // ++两个集合之间的操作

list1.sum

下面的代码示例中,演示了列表的创建及操作:

//1. List 创建
// 方式一: 
var list1 = List("aa", "bb", "ccc")
list1(0)
list1(1)
list1(2)
// list1(0) = "aaaaa"    // 不可以,因为List不可以改变

//方式二:右操作符:当方法名以:结尾时,为右操作符,先做右边
// :: 右操作符,拼接元素
var list3 = "aa" :: "bb" :: "cc" :: Nil       	// Nil 是空集合,先做"cc"::Nil,其中::是 Nil 的方法
var list4 = list1 ::: "dd" :: Nil   			// :::拼接集合

// 还可以
val x = List.tabulate(5)(n => n * n)
x.foreach(println)

val y = List.range(1, 10)				// 1 - 9
y.foreach(println)

// 2.方法
// 检索数据 
// head() 第一个元素
list1.head

// tail() 除了第 1 个元素外的全部元素
list1.tail
list1.tail.head
list1.tail.tail

// take(n) 取前 n 个元素 - 子集
list1.take(2)

// init() 除了最后一个的全部元素
list1.init

// 3.ListBuffer 创建
import scala.collection.mutable.ListBuffer
var list5 = ListBuffer("111", "222", "333")

// 增
// +=或 append 追加元素
list5.append("444")
list5 += "555"
// list5 += ("666","777")
list5 :+ "000"
"000" +: list5

// ++= 追加数组/列表
list5 ++= List("666", "777") 

// 丢弃前 3 个元素 
list5.drop(3) 

// 判断是否为空
list5.isEmpty

// 翻转
list5.reverse

// splitAt(m): 把列表前m个做成一组,后面做为另一组。返回的是一个Tuple2(ListBuffer, ListBuffer)
list5.splitAt(3)

// flatten: 扁平化
var list6 = List(List('a', 'b'), List('c'), List('d'))
list6.flatten

// 转换成数组
list5.toArray

// zip:两个 List 内部的元素合并 - 拉链方法
var list7 = List(1, 2, 3).zip(List(4, 5, 6)) 

// grouped(n):每 n 个元素分成 1 组,返回是迭代器 Iterator,可以再用 toList 转成 list 类型
List(1, 3, 5, 7, 9).grouped(2).foreach(println)
List(1, 3, 5, 7, 9).grouped(2).toList

println("---------------")
// 遍历方式一:不推荐
for (e <- List("88", "99")) {
   println(e)
}

println("---------------")
// 遍历方式二:使用foreach,推荐,这才是采用函数式编程
List("88", "99").foreach(e => println(e))   	// foreach是高阶函数
println("---------------")
List("88", "99").foreach(println(_))   		// 简化写法 _占位符
println("---------------")
List("88", "99").foreach(println) 		// 最简方法

《Flink原理深入与编程实战》