Sets
英文原文:http://docs.scala-lang.org/overviews/collections/sets.html
所有 Set 类集合都属于 Iterable 类的集合,只是集合中没有重复的元素。后面的两个表格中总结了 Set 上的方法,先是一般 Set 类上的方法,之后是可变 Set 中的方法。他们可以划分为以下几类:
- Tests 类操作,contains、apply 以及 subsetOf。contains 方法判断集合中是否包含给定元素。对于一个 Set 类实例来说,apply 方法和 contains 方法是一样的,因此 set(elem) 等同于 set contains elem。这说明 Set 类集合也可以用作一个测试函数,判断是否包含给定元素。
scala> val fruit = Set("apple", "orange", "peacch", "banana")
fruit: scala.collection.immutable.Set[java.lang.String] = Set(apple, orange, peach, banana)
scala> fruit("peach")
res0: Boolean = true
scala> fruit("potat")
- Additions 类操作,+ 和 ++ 方法, 返回一个新生成的集合,其元素包括原集合元素以及给定新增的一个或多个元素。
- Removals 类操作,- 和 -- 方法,返回一个新生成的集合,其元素包括原集合元素并去掉给定的一个或多个元素
- Set operations 类操作,并集操作、交集操作、差集操作。这些操作中的每个操作都有两种调用形式:字母形式和操作符形式。字母形式的版本是 intersect、union 和 diff,而符号形式的版本是 &、| 以及 &~。事实上,从 Traversable 继承下来的 ++ 方法可以看成是 union 或 | 的另一个别名,只有一点不同就是 ++ 方法的参数是 Traversable 而 union 和 | 的参数是 Set 类实例。
方法 | 作用 |
Tests: | |
xs contains x | 判断 x 是否是集合 xs 的元素 |
xs(x) | 同 xs contains x |
xs subsetOf ys | 判断 xs 是否是 ys 的子集 |
Additions: | |
xs + x | 返回一个新的集合,其元素包括原集合元素以及 x |
xs + (x, y, z) | 返回一个新的集合,其元素包括原集合元素以及 x,y,z |
xs ++ ys | 返回一个新的集合,其元素包括 xs 以及 ys 中所有去重后的元素 |
Tests: | |
xs - x | 返回一个新的集合,其元素包括 xs 中除 x 外的所有元素 |
xs - (x, y, z) | 返回一个新的集合,其元素包括 xs 中除 x,y,z 外的所有元素 |
xs -- ys | 返回一个新的集合,其元素包括 xs 中除 ys 中元素外的所有元素 |
xs.empty | 返回与 xs 同类型的一个空的集合 |
Binary Operations: | |
xs & ys | 集合 xs 与 ys 的交集 |
xs intersect ys | 同 xs & ys |
xs | ys | 集合 xs 与 ys 的并集 |
xs union ys | 同 xs | ys |
xs &~ ys | 集合 xs 与 ys 的差集 |
xs diff ys | 同 xs &~ ys |
除了以上这些方法,可变集合还提供了新的方法对集合中元素进行添加、删除和更新,归纳如下。
mutable.Set 中的方法
方法 | 作用 |
Additions: | |
xs += x | 以副作用的方式将 x 添加到 xs,并返回 xs 本身 |
xs += (x, y, z) | 以副作用的方式将 x、y、z 添加到 xs,并返回 xs 本身 |
xs ++= ys | 以副作用的方式将 ys 中的元素逐个添加到 xs,并返回 xs 本身 |
xs add x | 以副作用的方式将 x 添加到 xs。 如果 xs 原来就包含 x,则返回 false; 否则返回 true |
Removals: | |
xs -= x | 以副作用的方式除去 xs 中的 x 元素,并返回 xs 本身 |
xs -= (x, y, z) | 以副作用的方式除去 xs 中的 x、y、z 元素,并返回 xs 本身 |
xs --= ys | 以副作用的方式除去 xs 中的 ys 所包含的元素,并返回 xs 本身 |
xs remove x | 副作用的方式除去 xs 中的 x 元素。 如果 xs 原来包含 x,则返回 true; 否则返回 false |
xs retain p | 保留 xs 中满足谓词条件 p 的所有元素,其他元素均删除掉 |
xs.clear | 删除 xs 中所有的元素 |
Update: | |
xs(x) = b | (也可以直接写成 xs.update(x, b))。 如果 b 的值为 true,添加 x 到 xs; 否则删除 xs 中的 x 元素 |
Cloning: | |
xs.clone | 返回一个包含 xs 中所有元素的新的集合 |
就像不可变 Set 一样,可变 Set 也提供了用于添加元素的 + 和 ++ 方法和用于删除元素的 - 和 -- 方法。但是,由于这些操作需要重新拷贝一份 Set 数据,所以可变集合很少使用它们。可变 Set 类集合提供了更加高效的 += 和 -= 方法作为想用这些方法时的另一种选择。s += elem 操作以副作用的方式添加 elem 到 s 中,并且返回 s 本身。同样, s -= elem 以副作用的方式删除掉元素 elem,并返回 s 本身。除此之外,+= 和 -= 也有块操作的版本 ++= 和 --=,用于在原集合基础上添加或者删除一个 Traversable 类集合或者一个迭代器中的所有元素。
+= 和 -= 方法的取名意味着非常相似的代码既可以在可变的集合上工作也可以在不可变的集合上工作。首先考虑下面这段使用不可变集合的 REPL 对话。
scala> var s = Set(1, 2, 3)
s: scala.collection.immutable.Set[Int] = Set(1, 2, 3)
scala> s += 4
scala> s -= 2
scala> s
res2: scala.collection.immutable.Set[Int] = Set(1, 2, 3)
scala> val s = collection.mutable.Set(1, 2, 3)
s: scala.collection.mutable.Set[Int] = Set(1, 2, 3)
scala> s += 4
res3: s.type = Set(1, 4, 2, 3)
scala> s -= 2
res4: s.type = Set(1, 4, 3)
这里运行的最终效果与上一个对不可变集合的 REPL 对话非常相似:我们以一个 Set(1, 2, 3) 的定义开始,最后都得到了一个 Set(1, 3, 4)。虽然运行语句看起来很类似,但是他们做的事情并不相同。这里的 s +=4 语句实际上是调用了可变集合 s 上的 += 方法,修改了集合本身。同样,s -= 2 语句实际上是调用了 s 上的 -= 方法。
这两组 REPL 对话的对比说明了一个重要的原则。通常,你可以用一个用 var 声明的不可变集合替换一个 val 声明的可变集合,反过来也一样。这至少在这种情况下是有效的:即没有对当前集合的其他别名引用,并且从这个引用可以得知集合本身是否变化或者是否生成了一个新的集合。(意思就是只要程序不关心是新生成的集合还是原来集合,只关心最终结果的情况下这个原则总是有效的)
可变集合还提供了 += 和 -= 方法的变体 add 和 remove 方法。他们和原来方法的不同在于他们最终返回一个指示原集合是否发生变化的布尔值。
当前可变集合的默认实现使用了哈希表来存储集合的元素。不可变集合的默认实现使用了一种自适应元素数量的表示方式。一个空的集合由一个单例对象表示。元素数量不超过 4 个的集合使用一个将所有元素都作为其属性的单例对象。超过 4 个元素时,不可变集合使用 HashTrie 实现。
这种实现方式使得对于小集合(元素不超过 4 个),不可变集合通常比可变集合更加紧凑、高效。因此,如果你知道集合的元素数量很小,就应该考虑使用不可变集合。
特质 Set 有两个子特质 SortedSet 以及 BitSet。
Sorted Sets
SortedSet 按照给定的次序(集合创建的时候可以指定)给出元素(使用 iterator 和 foreach 方法时)。SortedSet 的默认实现是一个有序二叉树,树中所有节点的左子树的元素都比右子树中的元素小。这样的话,一个简单的中序遍历就能按照升序输出所有元素。scala 中的 immutable.TreeSet 使用了红黑树实现来维持元素有序同时保持树的平衡--从树根到叶子节点的所有路径长度做多相差 1。
创建一个空的 TreeSet,你可以首先声明一个期望的次序:
scala> val myOrdering = Ordering.fromLessThan[String](_>_)
myOrdering: scala.math.Ordering[String] = ...
然后,使用这个次序创建一个空的 TreeSet:
scala> TreeSet.empty(myOrdering)
res1: scala.collection.immutable.TreeSet[String] = TreeSet()
或者你可以不指定次序参数而只指定元素类型和一个空的集合。这种情况下,给定类型上的默认次序将会被使用。
scala> TreeSet.empty[String]
res2: scala.collection.immutable.TreeSet[String] = TreeSet()
如果你想通过一个已有的 TreeSet 来创建一个新的 TreeSet (例如通过连接或者是过滤),新的集合会保持原来集合的次序。例如,
scala> res2 + ("one", "two", "three", "four")
res3: scala.collection.immutable.TreeSet[String] = TreeSet(four, one, three, two)
SortedSet 还支持元素的范围选择。例如,range 方法返回从给定的开始元素到给定的结束元素的所有元素(不包含给定结束元素)。另外,from 方法返回大于等于给定开始元素的所有元素。这两个方法的结果同样是一个 SortedSet。例如:
scala> res3 range ("one", "two")
res4: scala.collection.immutable.TreeSet[String] = TreeSet(one, three)
scala> res3 from ("three")
res5: scala.collection.immutable.TreeSet[String] = TreeSet(three, two)
BitSets
BitSet 是一个非负整数的集合,而这些非负整数用比特位来表示。BitSet 内部实现使用一个 Long 型的数组。数组中的第一个 Long 型元素表示了数值 0 到 63,第二个 Long 型元素表示了数值 64 到 127,以此类推(不可变的 BitSet 在数值范围在 0 到 127 内时对实现进行了优化,直接将比特位对应到一到两个 Long 型字段)。对于每一个 Long 型元素,如果该位对应的非负整数在集合中,则该位置为 1;否则该位置为 0。一个 BitSet 集合的大小取决于集合中的最大元素值。如果 N 是当前集合中的最大整数值,则该集合的大小为 N/64 个整型字(觉得应是 N/64 + 1),或者是 N/8 个字节(觉得应是 8*(N/64 + 1)),此外还包括少量额外字节用来存储集合的状态信息。
因此,如果集合中含有大量的小数值元素,那么 BitSet 比其他集合更加紧凑。BitSet 的另一个好处是成员身份测试如 contains 方法,以及元素添加和删除方法 += 和 -= 效率都非常的高。