[Scala基础系列 07]Scala集合

Scala有一个非常通用,丰富,强大,可组合的集合库;集合是高阶的(high level)并暴露了一大套操作方法。很多集合的处理和转换可以被表达的简洁又可读,但不审慎地用它们的功能也会导致相反的结果。每个Scala程序员应该阅读 集合设计文档;通过它可以很好地洞察集合库,并了解设计动机。

1.数组(Array&ArrayBuffer)

1.1.Array

数组(Array)其实并不在scala.collection包里面,它属于scala包,直接对应于Java的数组,比如,Scala中的Array[Int], 在底层,与Java的int[]一样。

不过,Scala还是给Array提供了丰富得多的特性,比如支持泛型,比如支持类似于其他集合类型的遍历,转换等操作。 Arry对象还提供了大量的apply方法,这使得我们可以很方便的创建Array对象。下面,我们来看一些实际的例子。

//generic
def printArray[T](arr: Array[T]) {
  println(arr.mkString("Array(", ",", ")"))
}

val nums = new Array[Int](3)
for(i <- 0 until nums.length) nums(i) = i
printArray(nums)  //Array(0,1,2)

val nums1 = Array(1,2,3)
printArray(nums1) //Array(1,2,3)

val nums2 = nums1.map( _ * 2)
printArray(nums2) //Array(2,4,6)

val nums3 = nums1.filter(_ % 2 == 0)
printArray(nums3) //Array(2)

val nums4 = for(n <- nums1 if n % 2 != 0) yield n * 2
printArray(nums4) //Array(2,6)

println(nums4.sum) //8

val strs = Array("Beijing", "New York", "London")
val strs1 = strs.reverse
printArray( strs1)  //Array(London,New York,Beijing)

一个真实的应用程序如下:

package com.tv189.foundation

/**
 * Created by Administrator on 2015/7/30.
 */
object ArrayTest extends App{
  def printArray[T](arr:Array[T]): Unit ={
    println(arr.mkString("Array(",",",")"));
  }

  val nums=new Array[Int](3);
  for(i<-0 until nums.length){
    nums(i)=i;
  }
  printArray(nums);
  //Array(0,1,2)

  val nums2=nums.map(_*2);
  printArray(nums2);
  //Array(0,2,4)

  val nums3=nums.filter(_%2==0);
  printArray(nums3);
  //Array(0,2)
}

1.2.ArrayBuffer

Array在大多数语言中,都是不可变的,在Scala里也不例外。不过,Scala提供了一个可变的Array,这就是ArrayBuffer。 ArrayBuffer属于scala.collection.mutable包,使用时需要先引入。

通过ArrayBuffer,你可以很方便的添加或删除元素。很多情况下,我们在需要构建一个数组,预先不知道数组长度,但是构建完毕后,使用时不需要改变Array。 这时候,我们一般使用ArrayBuffer来构建数组,构建完毕后,调用它的toArray方法,得到不可变的Array供后续使用,以提高性能。

import scala.collection.mutable.ArrayBuffer

val buffer = ArrayBuffer(1)
println(buffer)

buffer += 2
println(buffer)

buffer += (3,4,5)
println(buffer)

buffer.prepend(0)
println(buffer)

buffer.trimEnd(2)
println(buffer)

buffer ++= Array(7,8)
println(buffer)

buffer.insert(4,4,5,6)
println(buffer)

val a = buffer.toArray
println(a.getClass.getSimpleName)

NOTE
向ArrayBuffer的尾部添加或删除元素的效率很高,但是在别的位置插入元素的效率就比较低下了,因为这涉及到将该位置之后的元素后移。
因此,在使用ArrayBuffer时,请尽量在尾部进行操作。

2.List&ListBuffer

2.1.List

List可能是最常用的容器类了。而Scala为List提供了比其他语言丰富得多的操作,本节只介绍基本操作,一些复杂操作我们将在后续章节中慢慢接触到。

2.1.1.Nil

我们在Scala的类层级曾经提到过Nil对象,这是一个特殊的List对象,其类型是List[Nothing]。 你已经知道Nothing是所有类型的子类型,因此List[Nothing]是所有List类型的子类型,这意味着,Nil可用于任何需要List类型实例的场合。

Nil的另一个用处就是,在构建List时,用这个空元素作为起点。比如,语句 1 :: 2 :: Nil 能创建一个List,但是如果末尾没有Nil,只有1 :: 2则由于Int类没有::这个操作,在语法上就会出错了。

2.1.2.构建List

Scala中构建List的方式可谓灵活多变。最常用的是apply方法和使用操作符(::)这两种,还有比较少见的fill方法,也可用于创建List。

  • 使用apply方法

前面我们介绍过apply方法,List对象提供了一整套这样的方法供构建List用,比如List(1,2,3)

  • 使用::操作符

::操作符是List特有的方法,属于右结合操作符,适合于List这样在头部操作效率较高的数据结构。比如,1 :: 2 :: 3 :: Nil, 它的计算步骤是(1 :: (2 :: (3 :: Nil))),首先将3添加到空列表中,然后是2,最后才是1。

  • 使用range和fill方法

除了apply方法外,List对象还提供了几个实用方法来创建List,range和fill就是其中的两个。

range方法用于构建一个从开始到结束的一系列数字,包含下边界,但是不包含上边界。另外,range是泛型的,接受类型参数,但是对类型有约束,必须是整数类型range[T: Integral]

fill方法则用于用一个初始元素创建指定长度的List,该List的每一个元素值都一样。

下面是创建List的示例。

val nums1 = List(1,2,3)
val strs1 = List("Beijing", "Shanghai", "Guangzhou")
println(nums1)
println(strs1)

val nums2 = 1 :: 2 :: 3 :: Nil
val strs2 = "Beijing" :: "Shanghai" :: "Guangzhou" :: Nil
println(nums2)
println(strs2)

val nums3 = List.range[Int](1,6,2)  //from 1 to 6, with step 2
val chars = List.range[Char](‘a‘,‘c‘)
println(nums3)
println(chars)

val fill = List.fill(3)("original")
println(fill)

2.1.3.常用的操作

List继承了通用的集合特质,比如SeqLike,IterableLike等等,常用的在集合上能进行的操作,比如contains, filter, sortWith等等, 此外List本身还提供了许多方法,因此,可以进行的操作非常多,以下选取常用的几个来介绍他们的用法。

  • isEmpty: Boolean:判断List是否有元素
  • length: Int:获取List的长度
  • head: A:取List的第一个元素
  • tail: List[A]:获得List除了第一个元素之外,剩下的list
  • last: A:获取List的最后一个元素
  • init: List[A]:获取除了最后一个元素之外的list
  • apply(n): A:获取第n个元素
  • drop(n): List[A]:去掉左边n个元素,返回剩下的list,对应的,有dropRight(n)方法
  • take(n): List[A]:获取左边n个元素组成的list,也有对应的takeRight(n)方法
  • dropWhile(p: A => Boolean): List[A]:去掉左边满足条件的元素
  • takeWhile(p: A => Boolean): List[A]:获取左边满足条件的元素
  • contains(elem: Any): Boolean:重载SeqLike的方法,判断List是否包含某元素
  • exists(p: A => Boolean): Boolean:重载IterableLike的方法,判断List中是否包含满足给定条件的元素
  • forall(p: A => Boolean): Boolean:重载IterableLike的方法,判断是否List中所有的元素都满足给定条件
  • filter(p: A => Boolean): List[A]:是TraversableOnce特质提供的方法,挑选复合条件的元素组成新的List
  • mkString:是TraversableOnce特质提供的方法,有多个重载,可方便的将一个序列转换为String
  • splitAt(n: Int): (List[A], List[A]):将List从给定位置分成两个部分,返回一对List。
  • :::[B >: A](prefix: List[B]): List[B]:将给定List中的所有元素添加到本List之前。这个方法是右结合运算。
println(Nil.isEmpty) //true

val nums = List(1, 2, 3, 4, 5)
println(nums.length) //5
println(nums.head) //1
println(nums.tail) //List(2, 3, 4, 5)
println(nums.last) //5
println(nums.init) //List(1, 2, 3, 4)
println(nums.reverse) //List(5, 4, 3, 2, 1)
println(nums(3)) //4
println(nums.drop(2)) //List(3, 4, 5)
println(nums.take(2)) //List(1, 2)
println(nums.takeWhile(_ % 2 != 0 )) //List(1)
println(nums.dropWhile(_  % 2 != 0)) ////List(2, 3, 4, 5)
println(nums.exists(_ == 3)) //true
println(nums.forall(_ % 2 ==0)) //false
println(nums.filter(_ % 2 ==0)) //List(2, 4)
println(nums.mkString("Numbers[", ",", "]")) //Numbers[1,2,3,4,5]
println(nums.splitAt(3)) //(List(1, 2, 3),List(4, 5))
println(nums.sortWith((x,y) => x > y))

val nums2 = List(6,7)
println(nums ::: nums2) //List(1, 2, 3, 4, 5, 6, 7)

请注意
在List的头部进行操作,比尾部效率要高,这是因为List是由单链表实现,访问第一个元素只需要O(1)的时间,而最后一个元素则需要O(n)。
因此使用List时,请尽量设计为越常访问的数据在越靠前,构建List时,尽量从头部添加元素。

2.2.ListBuffer

List是不可变的,在头部操作效率比较高。如果你需要频繁的更改元素,而且在尾部的操作比较多,可以使用对应的可变集合,ListBuffer。 在ListBuffer上prepend(+=)和append(+=:)操作都是常数时间O(1)。与ArrayBuffer相似,在构建完毕之后,可以使用toList得到不可变的List。

import scala.collection.mutable.ListBuffer

val buffer = new ListBuffer[Int]
buffer += 2
buffer ++= List(3,4)
buffer.+=:(1) // equals to ‘1 +=: buffer‘
val list = buffer.toList
println(list)

请注意
+=:是右结合操作符(所有以:结尾的操作符都是右结合的),因此,不能写作buffer +=: 1,如果想写成操作符形式,需要将buffer放在右边,
也就是1 +=: buffer

3.Set

[Scala Level: A1]

Set最大的特点就是不能有重复的元素,所有的元素都是唯一的。判断元素是否唯一,是通过==方法,由于这个方法定义在根类Any上,所以能对所有的对象调用该方法。 不过,设计类时,如果有特有的相等逻辑,需要重载==以确保你的类有正确的等价关系。

Set分为可变Set和不可变的两个版本,处于不同的package内,这一点与Array和List不同。

3.1.不可变的Set

不可变Set位于scala.collection.immutable包里,由于Predef里包含了不可变Set的定义,默认情况下,你使用的Set是不可变Set。 Set常常用于要求元素唯一的场景,比如key-value中的keySet。

与List一样,Scala也给Set提供了很多的操作,常用的在集合上的操作都可以用于Set。

3.2.可变Set

要使用可变Set,需要显示引用它,如下面例子所示。Scala为可变Set提供了一系列操作,可以方便的在Set中添加或删除元素。

val set = Set(1,2,3)
println(set.getClass.getName) //scala.collection.immutable.Set

println(set.exists(_ % 2 == 0)) //true
println(set.drop(1)) //Set(2,3)

import scala.collection.mutable.Set //import can be anywhere

val mutableSet = Set(1,2,3)
println(mutableSet.getClass.getName) //scala.collection.mutable.HashSet

mutableSet.add(4)
mutableSet.remove(1)
mutableSet += 5
mutableSet -= 2

println(mutableSet) //Set(5, 3, 4)

val another = mutableSet.toSet
println(another.getClass.getName) //scala.collection.immutable.Set

请注意
虽然可变Set和不可变Set都有添加或删除元素的操作,但是有一个非常大的差别。对不可变Set进行操作,会产生一个新的set,原来的set并没有改变,这与List一样。
而对可变Set进行操作,改变的是该Set本身,与ListBuffer类似。

3.3.SortedSet(TreeSet)

默认情况下,我们用的Set都是以HashSet实现,也就是底层数据结构是哈希表。我们知道,哈希表的特点是能快速定位元素,因此HashSet的添加,删除等操作都是常数时间。 但是哈希表没有确定的排序,因此,在需要排序的情况下,可以使用SortedSet。

SortedSet实际上是一个特质(trait),它有一个实现类TreeSet,由红黑树实现。你可以选择用SortedSet或者直接使用TreeSet, 另外,他们也有可变可不可变版本,可根据需要使用。

import scala.collection.immutable.SortedSet

val set = SortedSet(2, 6, 7, 3, 5, 4, 1, 8)
println(set.getClass.getName)  //scala.collection.immutable.TreeSet
println(set) //TreeSet(1, 2, 3, 4, 5, 6, 7, 8)

import scala.collection.mutable.TreeSet

val mSet = TreeSet(3,9)
mSet += 5
mSet += 1
println(mSet)  //TreeSet(1, 3, 5, 9)

4.Map

Map是一系列键值对(key-value pair)的集合,它将键(key)和值(value)对应起来,可以通过键快速定位值。与其他集合类一样,Scala提供了很多方便实用的方法来操作Map。 Map与Set类似,按可变性,可分为可变Map和不可变Map,按照内部实现,可分为HashMap和TreeMap。

4.1.构建Map

构建不可变的Map可使用Map对象的apply方法,该方法接受一系列键值对作为参数。比如:

val scores = Map(("Jackie", 90), ("Abby", 93), ("Tim", 87))
val caps = Map("China" -> "Beijing", "France" -> "Paris", "Norway" -> "Oslo")

以上构建的Map是默认的不可变Map。也可以创建可变的Map,然后往里添加数据,比如:

val teachers = scala.collection.mutable.Map("Chunni" -> "History")
teachers += "Jim" -> "Math"
teachers("Zhou") = "English"
println(teachers)

同其他可变集合一样,可变Map也可以通过toMap方法返回一个不可变Map。

请注意
update方法,与apply方法一样,比较特殊,有简写方式,比如,obj.update(k, v),可以写为obj(k) = v。
因此,以上teachers("Zhou") = "English"等价于teachers.update("Zhou", "English")

4.2.访问Map元素

访问Map元素最直接的莫过于apply方法,map(k)即可获得以k为键的值,如

scores("Abby")

使用apply方法时,如果指定的键不存在,会抛出异常,为方便起见,可使用get方法get(key: A): Option[B], 这个方法返回一个Option,你可以在使用时检查该Option是否为空。

val scores = Map(("Jackie", 90), ("Abby", 93), ("Tim", 87))
val caps = Map("China" -> "Beijing", "France" -> "Paris", "Norway" -> "Oslo")
//scores("no") //will throw an exception, key not found
val score = scores.get("no")  //None
if(score.isEmpty) {
  //handle the empty case
}

println(scores.getOrElse("no", 90))

如果你只在为空的情况下设置一个默认值,而不做其他处理,也可以使用getOrElse方法,该方法的第二个参数是默认值。如println(scores.getOrElse("no", 90)).

请注意
Scala中,‘[]‘一般是用于在泛型中指定类型。而‘()‘用于方法调用。访问Map的元素,与Array一样,是用‘()‘调用apply方法。
这与C#或Java经常用‘[]‘访问Array或Map的数据不同。

4.3.Map的遍历

要遍历一个Map,可以通过它的keySet,也可以直接遍历它的键值对。如下:

val capitals = Map("China" -> "Beijing", "France" -> "Paris", "Norway" -> "Oslo")

println("---use key iterator---")
val itr = capitals.keysIterator
while (itr.hasNext)
  println(itr.next())

println("---use for on key set---")
for(k <- capitals.keySet)
  println(capitals(k))

println("---use for on key value pair---")
for((k, v) <- capitals)
  println("The capital of %s is %s".format(k, v))

4.4.SoretedMap(TreeMap)

与Set一样,默认情况下,使用的是HashMap。如果需要排序的话,可以使用SortedMap,或直接用TreeMap类。 为什么与Set一样?因为Map的实现方式就在于其Key Set的实现,HashMap就是指其Key Set是HashSet实现,而TreeMap的Key Set为TreeSet实现。

import scala.collection.immutable.TreeMap
var capitals = TreeMap("China" -> "Beijing", "Norway" -> "Oslo", "France" -> "Paris")
capitals += "Austria" -> "Vienna"
println(capitals)  //sorted

import scala.collection.mutable.LinkedHashMap
val mutable = LinkedHashMap("China" -> "Beijing", "Norway" -> "Oslo", "France" -> "Paris")
mutable += "Austria" -> "Vienna"
println(mutable) //insertion order

请注意 Scala没有提供可变的TreeMap,如果需要的话,可以使用Java的TreeMap。不过,如果只需按插入顺序排序的话,可使用LinkedHashMap。

5.Tuple

[Scala Level: A1]

Tuple是一个方便的集合,可以容纳不同类型的元素,因此它最常用的场景就是用于返回多个对象,而这对象之间值逻辑关系并不紧密,不够格为他们设计一个类。

5.1.创建Tuple

当你把一系列对象用小括号‘()‘括起来时,那就是一个Tuple了。当然,由于Tuple其实是scala里面定义好的22个类,Tuple1...Tuple22, 你也可以直接用这些类。

val a = (1, "a")
val b = Tuple2(1,"a")

以上两个语句是等价的,都是创建了一个Tuple2[Int, String]的对象。从这里我们也能看到,Tuple是静态类型的,其类型由编译器通过类型推导(type inference)得知。

5.2.使用Tuple

使用Tuple里的元素,只需用_i按位置获取,比如a._1。请看下面的实例。

val score = ("Jimmy", 90, 92 )
def total(a: Int, b: Int) = a + b
println("%s got %d in total".format(score._1, total(score._2, score._3)))

请注意
Tuple的位置从1开始,而不是像数组或其他集合那样,从0开始。

Tuple最常用的就是可一次返回多个对象,而且,还可以用一个语句将这多个对象赋值给其他变量,如下例所示。

def findHighest(scores: Map[String,Int]) = {
  var max = 0
  var name = ""
  for((k, v) <- scores) {
    if(v > max) {
      max = v
      name = k
    }
  }
  (name, max)
}

val scores = Map("Jimmy" -> 90, "Abby" -> 92, "Ella" -> 88)
val (champion, score) = findHighest(scores)
println("The champion: %s, score: %d".format(champion, score))

以上findHighest需要返回两个值,为这两个值新建一个类在此处仿佛不很必要,因此,用Tuple将他们打包返回给调用者。 另外,在使用的时候,可用Tuple一次给多个变量赋值,如上面的val (champion, score) = findHighest(scores)

请注意
如果返回的多个对象之间有紧密的逻辑关系,或者经常使用,可能定义一个类会是更好的选择。总之,让程序简洁又容易理解是最终的目标。

参考文献:

《Effective Scala》

http://alvinalexander.com/scala/simple-scala-akka-actor-examples-hello-world-actors

时间: 2024-10-08 09:09:33

[Scala基础系列 07]Scala集合的相关文章

[Scala基础系列 04]Scala基本类型

1.Scala的数值类型 Scala的数值类型与Java类似,他们的范围与Java也是一致的.与Java不同的是,他们都是对象,是相应的数值类的实例.Scala通过富包装(Rich Wrapper)类给这些数值类型提供了强大的支持. 1.1.数值类型 Scala的数值类型和取值范围,见下表. Boolean: true 或者 false Byte: 8位, 有符号(2-7 ~ 27 - 1) Short: 16位, 有符号 (2-15 ~ 215 - 1) Int: 32位, 有符号 (2-31

[Scala基础系列 05]Scala控制结构

1.If语句 Scala的If语句可以完成其他语言中If语句,于此同时,if/else通常还有值,可以用来赋值,或者代替三元条件运算符(?:).不过它可以比条件运算符更强大,因为你可以在if-else里面写很复杂的程序块. 1.1.普通的If语句 package com.tv189.foundation /** * Created by molyeo on 2015/7/30. */ object IfCondition { def main(args: Array[String]) { val

[Scala基础系列 08]Scala继承、抽象类、接口trait以及AOP实现

1.继承 和java一样,scala采用extends关键字继承基类.代码实例如下: /** * Created by molyeo on 2015/8/11. */ class Person(val name: String, var age: Int) { println("The primary constructor of Person") val school = "BJU" def sleep = "8 hours" override

[Scala基础系列 10]Scala泛型、类型边界

1.泛型和类型边界 1.1.上限(upper bounds) 我们先看一个简单的实例,用于判断两个变量中较大的值,其中两个变量的类型均为Int型 package com.tv189.advanced /** * Created by molyeo on 2015/8/12. */ class PairInt(val first: Int, val second: Int) { def bigger = { if (first.compareTo(second) > 0) first else s

[Scala基础系列 03]Scala操作符

1.Scala操作符简介 首先,请记住,Scala没有操作符!也没有通常意义上的表达式.你所见到的类似操作符和表达式的语句,其实是方法(函数),它们只是方法的一种比较直观的写法,可以叫做操作符记法. 1.1.二元操作符(中缀表达式) 二元操作符是最常见的操作符,比如,一个简单的表达式1 + 2.其实,“+”是定义在Int类的一个方法,你完全可以用普通方法调用的写法1.+(2).相应的,其他的方法,比如"Hello".drop(2),也可以用操作符记法,"Hello"

[Scala基础系列 02]Scala函数

本文主要内容如下: 变量和不变量 函数和过程 函数的参数 分号 1.变量和不变量 1.1.变量 Scala的变量分两种,var和val.var,即variable,类似于我们在Java等其他语言中接触到的变量,而val,是value,类似于我们在其他语言中用到的不可重新赋值的常量,或者final变量. 为什么会有这种区别,这是由于很多情况下,其实你不需要一个可变的var,尤其在函数式编程中,更为明显.不变性能给程序带来很多便利,因此Scala非常强调不可变(immutable)的概念.关于不可变

[Scala基础系列 06]Scala类和对象

1.类和构造函数 Scala中的类,基本概念与其他面向对象语言是一致的,不过在语法上有些不一样的地方.与Java等语言相比,Scala的类语法更简洁,使用起来也更方便. 1.1.类的基本语法 我们先来看一个简单的类定义和使用的代码. class ScoreCalculator { private var total, count = 0 def report(score: Int) { total += score count += 1 } def score = if (count == 0)

第1节 Scala基础语法:scala中的方法源码分析

val list=List(1,2,3,4) list.reduce((x:Int,y:Int)=>x+y)--->list.reduceLeft((x:Int,y:Int)=>x+y) var first = true var acc:Int = 0 op=(x:Int,y:Int)=>x+y for循环第一次循环:acc=1 first = false第二次循环:acc=op(1,2)=1+2=3第三次循环:acc=op(3,3)=3+3=6第四次循环:acc=op(6,4)=

scala 基础十二 scala apply的使用,工厂方法和单例模式的实现

1. apply 可以用来实现类似于静态的初始化类的实例,请看下面实例 package smart.iot class applyclass { } class A { def apply()=println("hello class A"); } object B { def apply()=println("hello object B"); } object applyRun { def main(args: Array[String]): Unit = {