Scala 集合(一)

Scala 序列、映射和集合

1 序列

许多数据结构是序列型的,也就是说,元素可以按特定的顺序访问,如:元素的插入顺
序或其他特定顺序。collection.Seq是一个trait,是所有可变或不可变序列类型的抽象,其子trait collection.mutable.
Seq及collection.immutable.Seq分别对应可变和不可变序列。

列表也是一种序列,是函数式编程中最常用的数据结构,从第一代函数式语言Lisp 就开始用列表了。

通常,向列表里追加元素时,该元素会被追加到列表的开头,成为新列表的“头部”。除了头部,剩下的部分就是原列表的元素,这些元素并没有被修改,它们变成了新列表的“尾部”。图中有两个列表,List(1,2,3,4,5) 和List(2,3,4,5),后者是前者的尾部。

我们从旧列表中创建新列表的操作是O(1)。新列表的尾部与旧列表相同,只是在头部多了一个新元素。在函数式数据结构的思想中,我们将复制、共享数据结构的开销降到最低,这是第一个例子。为了支持可变性,我们必须要有降低复制开销的能力。其他代码访问原队列时对新队列是无感知的。列表是不可变的,因此在另一个线程中,创建新队列的代码不会给访问旧列表的代码带来不可预料的修改操作。从队列的创建过程,我们可以清楚地知道,计算队列长度的操作是O(N),其他需要从头遍历队列的操作也是如此。


object Test extends App {
 val l1 = List("aaaa","bbbb","cccc")
 val l2 = "dddd"::"eeee"::"ffff"::l1
  val l3 = "dddd"::"eeee"::"ffff"::Nil
  println(l1)
  println(l2)
  println(l3)
}

你可以用List.apply 方法创建队列,然后用:: 方法(称为cons,意为构造)向队列头部追加数据,从而创建新的列表。在这里我们使用了简单写法,省略了点号与小括号。我们提到过,以冒号(:)结尾的方法向右结合,因此x::list 其实是list.::(x)。

++ 方法可以将两个列表连接起来:

object Test extends App {
 val l1 = List("aaaa","bbbb","cccc")
 val l2 = "dddd"::"eeee"::"ffff"::l1
  val l3 = "dddd"::"eeee"::"ffff"::Nil
 val l4 = l1++l3
 println(l4)
}

输出:
List(aaaa, bbbb, cccc, dddd, eeee, ffff)

在需要的时候构造列表的确是常用的做法,但不推荐方法将列表作为参数或作为返回值。而应该用Seq,这样的话,Seq 的任何子类型就都可以用了,包括List 和Vector。

Seq 的构造方法是+: 而不是::。以下是前文使用过的一个例子,其中用到了Seq。注意,当你对伴随对象使用Seq.apply 方法时,将创建出一个List,这是因为Seq 只是一个特征,而不是具体的类。

在这里,我们用Seq.empty 为seq3 和seq4 创建了空的队列作为队尾。在Scala 中,大部分集合类型的伴随对象都使用empty 方法来创建该类型的空实例,类似列表的Nil 实例。

序列类型还定义了:+ 和+: 方法。它们有什么区别,怎么记住哪个是哪个呢?只要记住:总是靠近集合类型就可以了,

比如:list :+ x,x +: list。所以,:+ 方法用于在尾部追加元素,+: 方法用于在头部追加元素:

Scala 定义的List 是不可变的,不过,它还定义了其他可变的列表类型,如ListBuffer和MutableList。只有当必须
修改元素时才可以使用可变类型。

虽然List 对于序列类型是个不错的选择,你也可以考虑用immutable.Vector 代替List,因为immutable.Vector的所有操作都是O(1) (常数时间),而List 对于那些需要访问头部以外元素的操作,都需要O(n) 操作。

我们能够以常数时间复杂度获取任意元素:

在结束对序列的讨论之前,你还需要知道一个实现上的细节问题。为了鼓励程序员使用不可变的集合类型,Predef 及Predef 中使用的其他类型在暴露部分不可变集合类型时,不需要显式导入或使用全路径导入。如:List 和Map。

2 映射表

映射表用来存储键值对,但不应将其与很多数据结构的map 方法混淆。映射表与map 方法有一定程度的类似,前者每个键都对应一个值,后者每个输入元素都产生一个输出元素。

object Test extends App {
  val stateCapitals = Map("Alabama" -> "Montgomery", "Alaska" -> "Juneau", "Wyoming" -> "Cheyenne")
  val lengths = stateCapitals map {
    kv => (kv._1, kv._2.length)
  }
  val caps = stateCapitals map {
    case (k, v) => (k, v.toUpperCase)
  }

  val stateCapitals2 = stateCapitals + ( "Virginia" -> "Richmond")
  val stateCapitals3 = stateCapitals2 + ("New York" -> "Albany", "Illinois" -> "Springfield")
}

key -> value 的语法形式实际上是用库中的隐式转换实现的,实际调用了Map.apply 方法。Map.apply 方法的参数为一个两元素的元组(键值对)。

map 的参数是一个函数,以上示例展示了定义这个函数的两种方法。每个键值对(元组)都将被传入到该函数中。我们可以将其参数定义为一个两元素的元组,或使用类似case 语法从元组中提取键和值。

用+ 向Map 中添加一个或多个键值对,特别提醒键值对的括号不可以去

与List 不同,Map 有可变和不可变两种实现: 分别是scala.collection.immutable.Map[A,B] 与scala.collection.mutable.Map[A,B]。可变的实现需要显式导入,不可变的实现则已经用Predef 暴露出来了。两种实现都定义了+ 和- 操作用于增加和移除元素;以及++ 和-- 操作来增加和移除Iterator 中定义的元素(Iterator 也可以换为其他集合、列表等)。

3 集合

集合是无序集合类型的一个例子,所以集合不是序列。集合同样要求元素具有唯一性:

object Test extends App {
  val states = Set("Alabama", "Alaska", "Wyoming")
  val lengths = states map (st => st.length)
  val states2 = states + "Virginia"
  val states3 = states2 + ("New York", "Illinois")
}

类似Map,特征scala.collection.Set只定义不可变操作的方法。对于具体的不可变集合和可变集合,分别定义派生的特征scala.collection.immutable.Set和scala.collection.mutable.Set可变的版本需要显式导入,而不可变的版本在Predef 中已经导入了。两者都为增加和移除元素定义了+ 和- 操作;为Iterator(也可以为其他集合、列表等)中的增加和移除元素定义了++ 和-- 操作。

猜你喜欢

转载自blog.csdn.net/u014646662/article/details/84255831