Scala | 教程 | 学习手册 --- 常用集合

2021-12-14 18:36:09 浏览数 (1)

collections提供一些数据结构来收集给定类型的一个或多个值。

scala的集合还有单独的可变和不可变的集合的类型层次体系。

所有集合的根是Iterator,它提供了一组公共方法,可以用来迭代处理和管理集合数据。

列表、集和映射

List

List类型是一个不可变的单链表

代码语言:javascript复制
scala> val numbers = List(32, 95, 24, 21, 17)
numbers: List[Int] = List(32, 95, 24, 21, 17)

scala> val colors = List("red", "green", "blue")
colors: List[String] = List(red, green, blue)

scala> println(s"I have ${colors.size} colors: $colors")
I have 3 colors: List(red, green, blue)

所有集合和String instance都有size方法返回集合中的项数。

可以用head方法和tail方法来访问一个列表的首元素和其余元素不用加括号!

使用括号来访问一个索引

代码语言:javascript复制
scala> colors.head
res0: String = red

scala> colors.tail
res1: List[String] = List(green, blue)

scala> colors(1)
res2: String = green

for循环也很适合迭代处理列表

代码语言:javascript复制
scala> for (c <- colors) { println(c) }
red
green
blue

scala> val numbers = List(32, 95, 24, 21, 17)
numbers: List[Int] = List(32, 95, 24, 21, 17)

scala> var total = 0; for (i <- numbers) { total  = i }
total: Int = 189

list中还有foreach(), map(), reduce()高阶函数。分别用来迭代处理列表、转换列表以及将列表规约为一项。这些方法分别需要传入函数字面量。

  1. foreach()取一个函数,对列表中每一项调用这个函数
  2. map()取一个函数,将一个列表元素转换为另一个值或类型
  3. reduce()取一个函数,将两个列表列表元素结合为一个元素
代码语言:javascript复制
scala> val colors = List("red", "green", "blue")
colors: List[String] = List(red, green, blue)

scala> colors.foreach( (c: String) => println(c) ) (1)
red
green
blue

scala> val sizes = colors.map( (c: String) => c.size ) (2)
sizes: List[Int] = List(3, 5, 4)

scala> val numbers = List(32, 95, 24, 21, 17)
numbers: List[Int] = List(32, 95, 24, 21, 17)

scala> val total = numbers.reduce( (a: Int, b: Int) => a   b ) (3)
total: Int = 189

Set

不可变的集合。只包含不重复的唯一元素。

set也支持list同样的操作,map,reduce,foreach,size,head,tail等。

代码语言:javascript复制
scala> val unique = Set(10, 20, 30, 20, 20, 10)
unique: scala.collection.immutable.Set[Int] = Set(10, 20, 30)

scala> val sum = unique.reduce( (a: Int, b: Int) => a   b )
sum: Int = 60

map是一个不可变的键值库,其他语言也叫hashmap,dictionary或关联数组。

scala中,键和值都可以参数化。

创建map时,指定键值为元组(),可以使用关系操作符 -> 来指定键和值元组。

代码语言:javascript复制
scala> val colorMap = Map("red" -> 0xFF0000, "green" -> 0xFF00,
  "blue" -> 0xFF)
colorMap: scala.collection.immutable.Map[String,Int] =
  Map(red -> 16711680, green -> 65280, blue -> 255)

scala> val redRGB = colorMap("red")
redRGB: Int = 16711680

// |符号表示按位或
scala> val cyanRGB = colorMap("green") | colorMap("blue")
cyanRGB: Int = 65535

scala> for (pairs <- colorMap) { println(pairs) }
(red,16711680)
(green,65280)
(blue,255)

List里面有什么

可以在任何集合collections里面存储任何类型的值,不仅仅是字符串和数字,比如集合的集合

代码语言:javascript复制
scala> val oddsAndEvents = List(List(1, 3, 5), List(2, 4, 6))
oddsAndEvents: List[List[Int]] = List(List(1, 3, 5), List(2, 4, 6))

要访问列表中的单个元素,使用一个括号和索引号

代码语言:javascript复制
scala> val primes = List(2, 3, 5, 7, 11, 13)
primes: List[Int] = List(2, 3, 5, 7, 11, 13)

scala> val first = primes(0)
first: Int = 2

使用head和tail来索引第一项和其余项

代码语言:javascript复制
scala> val first = primes.head
first: Int = 2

scala> val remaining = primes.tail
remaining: List[Int] = List(3, 5, 7, 11, 13)

isEmpty可以判断是否列表已经为空

代码语言:javascript复制
scala> while(! i.isEmpty) { print(i.head   ", "); i = i.tail }
2, 3, 5, 7, 11, 13,

或者使用size方法

代码语言:javascript复制
scala> def visit(i: List[Int]) { if (i.size > 0) { print(i.head   ", "); visit(i.tail) } }
visit: (i: List[Int])Unit

scala> visit(primes)
2, 3, 5, 7, 11, 13,

或者使用nil,因为每个列表都有一个nil实例作为终点

代码语言:javascript复制
scala> while(i != Nil) { print(i.head   ", "); i = i.tail }
2, 3, 5, 7, 11, 13,

创建一个空列表时,实际上也是返回一个nil而不是一个新实例。

代码语言:javascript复制
scala> val l: List[Int] = List()
l: List[Int] = List()

scala> l == Nil
res0: Boolean = true

scala> val m: List[String] = List("a")
m: List[String] = List(a)

scala> m.head
res1: String = a

scala> m.tail == Nil
res2: Boolean = true

cons操作符

cons操作符:: 是两个冒号,可以员工cons操作符来绑定元素,构建列表,而不必使用传统额的List()格式

代码语言:javascript复制
scala> val numbers = 1 :: 2 :: 3 :: Nil
numbers: List[Int] = List(1, 2, 3)

或者追加一个值

代码语言:javascript复制
scala> val second = 2 :: first
second: List[Int] = List(2, 1)

scala> second.tail == first
res4: Boolean = true

列表算术运算

常见的算术运算符比如

::

:::

==

distinct

drop

filter

flatten

reverse

slice

sortBy

sorted

splitAt

take

zip

在上表中,有三个高阶函数

filter, partition和sortBy

代码语言:javascript复制
scala> val f = List(23, 8, 14, 21) filter (_ > 18)
f: List[Int] = List(23, 21)

scala> val p = List(1, 2, 3, 4, 5) partition (_ < 3)
p: (List[Int], List[Int]) = (List(1, 2),List(3, 4, 5))

scala> val s = List("apple", "to") sortBy (_.size)
s: List[String] = List(to, apple)

partition和filter函数分别取一个谓词函数(predicate function)得到一个输入值后会相应返回true或false。

sortBy方法指定一个函数时,它会返回一值,用来对列表中的元素排序。

对于性能方面,::, drop, take在列表前面完成,因此不存在性能损失。它们的反向操作是 :, dropRight和takeRight,这些操作符的参数与其反向操作的参数完全相同,但是需要遍历一遍列表。

代码语言:javascript复制
scala> val appended = List(1, 2, 3, 4) :  5
appended: List[Int] = List(1, 2, 3, 4, 5)

scala> val suffix = appended takeRight 3
suffix: List[Int] = List(3, 4, 5)

scala> val middle = suffix dropRight 2
middle: List[Int] = List(3)

map映射列表

map方法取一个函数,将它应用于列表每一个成员,再把结果收集到一个新列表。

  • collect使用一个偏函数,只对一部分元素应用
  • flatMap使用一个给定函数转换各个元素,将结果列表扁平化到这个列表中
  • map使用给定函数转换各个元素
代码语言:javascript复制
scala> List(0, 1, 0) collect {case 1 => "ok"}
res0: List[String] = List(ok)

scala> List("milk,tea") flatMap (_.split(','))
res1: List[String] = List(milk, tea)

scala> List("milk","tea") map (_.toUpperCase)
res2: List[String] = List(MILK, TEA)

reduce规约列表

列表规约指的是把列表收缩为单个值

数学reduce操作:max,min,product,sum

boolean reduce操作:contains,endsWith,exists,forall,startsWith

代码语言:javascript复制
scala> val validations = List(true, true, false, true, true, true)
validations: List[Boolean] = List(true, true, false, true, true, true)

scala> val valid1 = !(validations contains false)
valid1: Boolean = false

scala> val valid2 = validations forall (_ == true)
valid2: Boolean = false

scala> val valid3 = validations.exists(_ == false) == false
valid3: Boolean = false

事实上,我们也可以自己来实现规约操作。通过迭代处理一个累加器变量(accumulator),这个变量包含目前为止的当前结果,基于当前元素更新累加器。可以实现exists,forall,startsWith和其他boolean操作。

比如我们要判断列表中是否包含某个元素。逻辑是这样:如果accumulator是false,那继续判断是否包含,如果包含,更新accumulator,然后下次就不用判断。

代码语言:javascript复制
scala> def boolReduce(l: List[Int], start: Boolean)(f: (Boolean, Int) =>
     |   Boolean): Boolean = {
     |
     |   var a = start
     |   for (i <- l) a = f(a, i)
     |   a
     | }
boolReduce: (l: List[Int], start: Boolean)(f: (Boolean, Int) => Boolean)Boolean

scala> val included = boolReduce(List(46, 19, 92), false) { (a, i) =>
     |   if (a) a else (i == 19)
     | }
included: Boolean = true

可以用类型参数来替换特定的类型

代码语言:javascript复制
scala> def reduceOp[A,B](l: List[A], start: B)(f: (B, A) => B): B = { (1)
     |   var a = start
     |   for (i <- l) a = f(a, i)
     |   a
     | }
reduceOp: [A, B](l: List[A], start: B)(f: (B, A) => B)B

scala> val included = reduceOp(List(46, 19, 92), false) { (a, i) => (2)
     |   if (a) a else (i == 19)
     | }
included: Boolean = true

scala> val answer = reduceOp(List(11.3, 23.5, 7.2), 0.0)(_   _) (3)
answer: Double = 42.0

根据输入函数规约列表的高阶函数也称为fold折叠

fold,reduce和scan实际上没有太大差别。

主要关注点是fold和foldLeft版本之间的差别。fold,reduce和scan都限于返回与列表元素类型相同的一个值。foldLeft可以实现forall布尔操作,但是fold做不到。

代码语言:javascript复制
// false是starting value
// def foldLeft[B](z: B)(op: (B, A) ⇒ B): B
scala> val included = List(46, 19, 92).foldLeft(false) { (a, i) => (1)
     |   if (a) a else (i == 19)
     | }
included: Boolean = true

scala> val answer = List(11.3, 23.5, 7.2).reduceLeft(_   _)        (2)
answer: Double = 42.0

转换集合

mkString, toList, toBuffer, toMap, toSet, toString

Java和Scala的兼容性

代码语言:javascript复制
scala> import collection.JavaConverters._
import collection.JavaConverters._

比如可以使用asJava, asScala方法实现java集合和scala集合的相互转换

导入JavaConverters后,将有更多的Java库和JVM函数可用。

使用集合的模式匹配

代码语言:javascript复制
scala> val statuses = List(500, 404)
statuses: List[Int] = List(500, 404)

// 通配符
scala> val msg = statuses.head match {
     |   case x if x < 500 => "okay"
     |   case _ => "whoah, an error"
     | }
msg: String = whoah, an error

// pattern guard(增加if表达式)
scala> val msg = statuses match {
     |   case x if x contains(500) => "has error"
     |   case _ => "okay"
     | }
msg: String = has error

// 值绑定
scala> val msg = statuses match {
     |   case List(500, x) => s"Error followed by $x"
     |   case List(e, x) => s"$e was followed by $x"
     | }
msg: String = Error followed by 404

// 列表可以分解为表头和表尾,匹配得到表头
scala> val head = List('r','g','b') match {
     |   case x :: xs => x
     |   case Nil => ' '
     | }
head: Char = r

// 综合值绑定和模式匹配
scala> val code = ('h', 204, true) match {
     |   case (_, _, false) => 501
     |   case ('c', _, true) => 302
     |   case ('h', x, true) => x
     |   case (c, x, true) => {
     |     println(s"Did not expect code $c")
     |     x
     |   }
     | }
code: Int = 204

总结

scala中的核心数据结构List, Map和Set都是不可变的。它们不可以调整大小,也不能改变其内容。

0 人点赞