|
| 1 | +/* |
| 2 | + * Scala (https://www.scala-lang.org) |
| 3 | + * |
| 4 | + * Copyright EPFL and Lightbend, Inc. |
| 5 | + * |
| 6 | + * Licensed under Apache License 2.0 |
| 7 | + * (http://www.apache.org/licenses/LICENSE-2.0). |
| 8 | + * |
| 9 | + * See the NOTICE file distributed with this work for |
| 10 | + * additional information regarding copyright ownership. |
| 11 | + */ |
| 12 | + |
| 13 | +package scala |
| 14 | +package collection |
| 15 | + |
| 16 | +import scala.util.hashing.MurmurHash3 |
| 17 | +import java.lang.String |
| 18 | + |
| 19 | +import scala.annotation.nowarn |
| 20 | + |
| 21 | +/** Base trait for set collections. |
| 22 | + */ |
| 23 | +trait Set[A] |
| 24 | + extends Iterable[A] |
| 25 | + with SetOps[A, Set, Set[A]] |
| 26 | + with Equals |
| 27 | + with IterableFactoryDefaults[A, Set] { |
| 28 | + |
| 29 | + def canEqual(that: Any) = true |
| 30 | + |
| 31 | + /** |
| 32 | + * Equality of sets is implemented using the lookup method [[contains]]. This method returns `true` if |
| 33 | + * - the argument `that` is a `Set`, |
| 34 | + * - the two sets have the same [[size]], and |
| 35 | + * - for every `element` this set, `other.contains(element) == true`. |
| 36 | + * |
| 37 | + * The implementation of `equals` checks the [[canEqual]] method, so subclasses of `Set` can narrow down the equality |
| 38 | + * to specific set types. The `Set` implementations in the standard library can all be compared, their `canEqual` |
| 39 | + * methods return `true`. |
| 40 | + * |
| 41 | + * Note: The `equals` method only respects the equality laws (symmetry, transitivity) if the two sets use the same |
| 42 | + * element equivalence function in their lookup operation. For example, the element equivalence operation in a |
| 43 | + * [[scala.collection.immutable.TreeSet]] is defined by its ordering. Comparing a `TreeSet` with a `HashSet` leads |
| 44 | + * to unexpected results if `ordering.equiv(e1, e2)` (used for lookup in `TreeSet`) is different from `e1 == e2` |
| 45 | + * (used for lookup in `HashSet`). |
| 46 | + * |
| 47 | + * {{{ |
| 48 | + * scala> import scala.collection.immutable._ |
| 49 | + * scala> val ord: Ordering[String] = _ compareToIgnoreCase _ |
| 50 | + * |
| 51 | + * scala> TreeSet("A")(ord) == HashSet("a") |
| 52 | + * val res0: Boolean = false |
| 53 | + * |
| 54 | + * scala> HashSet("a") == TreeSet("A")(ord) |
| 55 | + * val res1: Boolean = true |
| 56 | + * }}} |
| 57 | + * |
| 58 | + * |
| 59 | + * @param that The set to which this set is compared |
| 60 | + * @return `true` if the two sets are equal according to the description |
| 61 | + */ |
| 62 | + override def equals(that: Any): Boolean = |
| 63 | + (this eq that.asInstanceOf[AnyRef]) || (that match { |
| 64 | + case set: Set[A @unchecked] if set.canEqual(this) => |
| 65 | + (this.size == set.size) && { |
| 66 | + try this.subsetOf(set) |
| 67 | + catch { case _: ClassCastException => false } // PR #9565 / scala/bug#12228 |
| 68 | + } |
| 69 | + case _ => |
| 70 | + false |
| 71 | + }) |
| 72 | + |
| 73 | + override def hashCode(): Int = MurmurHash3.setHash(this) |
| 74 | + |
| 75 | + override def iterableFactory: IterableFactory[Set] = Set |
| 76 | + |
| 77 | + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") |
| 78 | + override protected[this] def stringPrefix: String = "Set" |
| 79 | + |
| 80 | + override def toString(): String = super[Iterable].toString() // Because `Function1` overrides `toString` too |
| 81 | +} |
| 82 | + |
| 83 | +/** Base trait for set operations |
| 84 | + * |
| 85 | + * @define coll set |
| 86 | + * @define Coll `Set` |
| 87 | + */ |
| 88 | +trait SetOps[A, +CC[_], +C <: SetOps[A, CC, C]] |
| 89 | + extends IterableOps[A, CC, C] |
| 90 | + with (A => Boolean) { |
| 91 | + |
| 92 | + def contains(elem: A): Boolean |
| 93 | + |
| 94 | + /** Tests if some element is contained in this set. |
| 95 | + * |
| 96 | + * This method is equivalent to `contains`. It allows sets to be interpreted as predicates. |
| 97 | + * @param elem the element to test for membership. |
| 98 | + * @return `true` if `elem` is contained in this set, `false` otherwise. |
| 99 | + */ |
| 100 | + @`inline` final def apply(elem: A): Boolean = this.contains(elem) |
| 101 | + |
| 102 | + /** Tests whether this set is a subset of another set. |
| 103 | + * |
| 104 | + * @param that the set to test. |
| 105 | + * @return `true` if this set is a subset of `that`, i.e. if |
| 106 | + * every element of this set is also an element of `that`. |
| 107 | + */ |
| 108 | + def subsetOf(that: Set[A]): Boolean = this.forall(that) |
| 109 | + |
| 110 | + /** An iterator over all subsets of this set of the given size. |
| 111 | + * If the requested size is impossible, an empty iterator is returned. |
| 112 | + * |
| 113 | + * @param len the size of the subsets. |
| 114 | + * @return the iterator. |
| 115 | + */ |
| 116 | + def subsets(len: Int): Iterator[C] = { |
| 117 | + if (len < 0 || len > size) Iterator.empty |
| 118 | + else new SubsetsItr(this.to(IndexedSeq), len) |
| 119 | + } |
| 120 | + |
| 121 | + /** An iterator over all subsets of this set. |
| 122 | + * |
| 123 | + * @return the iterator. |
| 124 | + */ |
| 125 | + def subsets(): Iterator[C] = new AbstractIterator[C] { |
| 126 | + private[this] val elms = SetOps.this.to(IndexedSeq) |
| 127 | + private[this] var len = 0 |
| 128 | + private[this] var itr: Iterator[C] = Iterator.empty |
| 129 | + |
| 130 | + def hasNext = len <= elms.size || itr.hasNext |
| 131 | + def next() = { |
| 132 | + if (!itr.hasNext) { |
| 133 | + if (len > elms.size) Iterator.empty.next() |
| 134 | + else { |
| 135 | + itr = new SubsetsItr(elms, len) |
| 136 | + len += 1 |
| 137 | + } |
| 138 | + } |
| 139 | + |
| 140 | + itr.next() |
| 141 | + } |
| 142 | + } |
| 143 | + |
| 144 | + /** An Iterator including all subsets containing exactly len elements. |
| 145 | + * If the elements in 'This' type is ordered, then the subsets will also be in the same order. |
| 146 | + * ListSet(1,2,3).subsets => {{1},{2},{3},{1,2},{1,3},{2,3},{1,2,3}} |
| 147 | + * |
| 148 | + * $willForceEvaluation |
| 149 | + * |
| 150 | + */ |
| 151 | + private class SubsetsItr(elms: IndexedSeq[A], len: Int) extends AbstractIterator[C] { |
| 152 | + private[this] val idxs = Array.range(0, len+1) |
| 153 | + private[this] var _hasNext = true |
| 154 | + idxs(len) = elms.size |
| 155 | + |
| 156 | + def hasNext = _hasNext |
| 157 | + @throws[NoSuchElementException] |
| 158 | + def next(): C = { |
| 159 | + if (!hasNext) Iterator.empty.next() |
| 160 | + |
| 161 | + val buf = newSpecificBuilder |
| 162 | + idxs.slice(0, len) foreach (idx => buf += elms(idx)) |
| 163 | + val result = buf.result() |
| 164 | + |
| 165 | + var i = len - 1 |
| 166 | + while (i >= 0 && idxs(i) == idxs(i+1)-1) i -= 1 |
| 167 | + |
| 168 | + if (i < 0) _hasNext = false |
| 169 | + else { |
| 170 | + idxs(i) += 1 |
| 171 | + for (j <- (i+1) until len) |
| 172 | + idxs(j) = idxs(j-1) + 1 |
| 173 | + } |
| 174 | + |
| 175 | + result |
| 176 | + } |
| 177 | + } |
| 178 | + |
| 179 | + /** Computes the intersection between this set and another set. |
| 180 | + * |
| 181 | + * @param that the set to intersect with. |
| 182 | + * @return a new set consisting of all elements that are both in this |
| 183 | + * set and in the given set `that`. |
| 184 | + */ |
| 185 | + def intersect(that: Set[A]): C = this.filter(that) |
| 186 | + |
| 187 | + /** Alias for `intersect` */ |
| 188 | + @`inline` final def & (that: Set[A]): C = intersect(that) |
| 189 | + |
| 190 | + /** Computes the difference of this set and another set. |
| 191 | + * |
| 192 | + * @param that the set of elements to exclude. |
| 193 | + * @return a set containing those elements of this |
| 194 | + * set that are not also contained in the given set `that`. |
| 195 | + */ |
| 196 | + def diff(that: Set[A]): C |
| 197 | + |
| 198 | + /** Alias for `diff` */ |
| 199 | + @`inline` final def &~ (that: Set[A]): C = this diff that |
| 200 | + |
| 201 | + @deprecated("Consider requiring an immutable Set", "2.13.0") |
| 202 | + def -- (that: IterableOnce[A]): C = { |
| 203 | + val toRemove = that.iterator.to(immutable.Set) |
| 204 | + fromSpecific(view.filterNot(toRemove)) |
| 205 | + } |
| 206 | + |
| 207 | + @deprecated("Consider requiring an immutable Set or fall back to Set.diff", "2.13.0") |
| 208 | + def - (elem: A): C = diff(Set(elem)) |
| 209 | + |
| 210 | + @deprecated("Use &- with an explicit collection argument instead of - with varargs", "2.13.0") |
| 211 | + def - (elem1: A, elem2: A, elems: A*): C = diff(elems.toSet + elem1 + elem2) |
| 212 | + |
| 213 | + /** Creates a new $coll by adding all elements contained in another collection to this $coll, omitting duplicates. |
| 214 | + * |
| 215 | + * This method takes a collection of elements and adds all elements, omitting duplicates, into $coll. |
| 216 | + * |
| 217 | + * Example: |
| 218 | + * {{{ |
| 219 | + * scala> val a = Set(1, 2) concat Set(2, 3) |
| 220 | + * a: scala.collection.immutable.Set[Int] = Set(1, 2, 3) |
| 221 | + * }}} |
| 222 | + * |
| 223 | + * @param that the collection containing the elements to add. |
| 224 | + * @return a new $coll with the given elements added, omitting duplicates. |
| 225 | + */ |
| 226 | + def concat(that: collection.IterableOnce[A]): C = this match { |
| 227 | + case optimizedSet @ (_ : scala.collection.immutable.Set.Set1[A] | _: scala.collection.immutable.Set.Set2[A] | _: scala.collection.immutable.Set.Set3[A] | _: scala.collection.immutable.Set.Set4[A]) => |
| 228 | + // StrictOptimizedSetOps optimization of concat (these Sets cannot extend StrictOptimizedSetOps because of binary-incompatible return type; cf. PR #10036) |
| 229 | + var result = optimizedSet.asInstanceOf[scala.collection.immutable.SetOps[A, scala.collection.immutable.Set, scala.collection.immutable.Set[A]]] |
| 230 | + val it = that.iterator |
| 231 | + while (it.hasNext) result = result + it.next() |
| 232 | + result.asInstanceOf[C] |
| 233 | + case _ => fromSpecific(that match { |
| 234 | + case that: collection.Iterable[A] => new View.Concat(this, that) |
| 235 | + case _ => iterator.concat(that.iterator) |
| 236 | + }) |
| 237 | + } |
| 238 | + |
| 239 | + @deprecated("Consider requiring an immutable Set or fall back to Set.union", "2.13.0") |
| 240 | + def + (elem: A): C = fromSpecific(new View.Appended(this, elem)) |
| 241 | + |
| 242 | + @deprecated("Use ++ with an explicit collection argument instead of + with varargs", "2.13.0") |
| 243 | + def + (elem1: A, elem2: A, elems: A*): C = fromSpecific(new View.Concat(new View.Appended(new View.Appended(this, elem1), elem2), elems)) |
| 244 | + |
| 245 | + /** Alias for `concat` */ |
| 246 | + @`inline` final def ++ (that: collection.IterableOnce[A]): C = concat(that) |
| 247 | + |
| 248 | + /** Computes the union between of set and another set. |
| 249 | + * |
| 250 | + * @param that the set to form the union with. |
| 251 | + * @return a new set consisting of all elements that are in this |
| 252 | + * set or in the given set `that`. |
| 253 | + */ |
| 254 | + @`inline` final def union(that: Set[A]): C = concat(that) |
| 255 | + |
| 256 | + /** Alias for `union` */ |
| 257 | + @`inline` final def | (that: Set[A]): C = concat(that) |
| 258 | +} |
| 259 | + |
| 260 | +/** |
| 261 | + * $factoryInfo |
| 262 | + * @define coll set |
| 263 | + * @define Coll `Set` |
| 264 | + */ |
| 265 | +@SerialVersionUID(3L) |
| 266 | +object Set extends IterableFactory.Delegate[Set](immutable.Set) |
| 267 | + |
| 268 | +/** Explicit instantiation of the `Set` trait to reduce class file size in subclasses. */ |
| 269 | +abstract class AbstractSet[A] extends AbstractIterable[A] with Set[A] |
0 commit comments