|
| 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 | +package immutable |
| 16 | + |
| 17 | +import BitSetOps.{LogWL, updateArray} |
| 18 | +import mutable.Builder |
| 19 | +import scala.annotation.{implicitNotFound, nowarn} |
| 20 | + |
| 21 | +/** A class for immutable bitsets. |
| 22 | + * $bitsetinfo |
| 23 | + * @see [[https://docs.scala-lang.org/overviews/collections/concrete-immutable-collection-classes.html#immutable-bitsets "Scala's Collection Library overview"]] |
| 24 | + * section on `Immutable BitSets` for more information. |
| 25 | + * |
| 26 | + * @define Coll `immutable.BitSet` |
| 27 | + * @define coll immutable bitset |
| 28 | + */ |
| 29 | +sealed abstract class BitSet |
| 30 | + extends AbstractSet[Int] |
| 31 | + with SortedSet[Int] |
| 32 | + with SortedSetOps[Int, SortedSet, BitSet] |
| 33 | + with StrictOptimizedSortedSetOps[Int, SortedSet, BitSet] |
| 34 | + with collection.BitSet |
| 35 | + with collection.BitSetOps[BitSet] |
| 36 | + with Serializable { |
| 37 | + |
| 38 | + override def unsorted: Set[Int] = this |
| 39 | + |
| 40 | + override protected def fromSpecific(coll: IterableOnce[Int]): BitSet = bitSetFactory.fromSpecific(coll) |
| 41 | + override protected def newSpecificBuilder: Builder[Int, BitSet] = bitSetFactory.newBuilder |
| 42 | + override def empty: BitSet = bitSetFactory.empty |
| 43 | + |
| 44 | + def bitSetFactory = BitSet |
| 45 | + |
| 46 | + protected[collection] def fromBitMaskNoCopy(elems: Array[Long]): BitSet = BitSet.fromBitMaskNoCopy(elems) |
| 47 | + |
| 48 | + def incl(elem: Int): BitSet = { |
| 49 | + require(elem >= 0, "bitset element must be >= 0") |
| 50 | + if (contains(elem)) this |
| 51 | + else { |
| 52 | + val idx = elem >> LogWL |
| 53 | + updateWord(idx, word(idx) | (1L << elem)) |
| 54 | + } |
| 55 | + } |
| 56 | + |
| 57 | + def excl(elem: Int): BitSet = { |
| 58 | + require(elem >= 0, "bitset element must be >= 0") |
| 59 | + if (contains(elem)) { |
| 60 | + val idx = elem >> LogWL |
| 61 | + updateWord(idx, word(idx) & ~(1L << elem)) |
| 62 | + } else this |
| 63 | + } |
| 64 | + |
| 65 | + /** Update word at index `idx`; enlarge set if `idx` outside range of set. |
| 66 | + */ |
| 67 | + protected def updateWord(idx: Int, w: Long): BitSet |
| 68 | + |
| 69 | + override def map(f: Int => Int): BitSet = strictOptimizedMap(newSpecificBuilder, f) |
| 70 | + override def map[B](f: Int => B)(implicit @implicitNotFound(collection.BitSet.ordMsg) ev: Ordering[B]): SortedSet[B] = |
| 71 | + super[StrictOptimizedSortedSetOps].map(f) |
| 72 | + |
| 73 | + override def flatMap(f: Int => IterableOnce[Int]): BitSet = strictOptimizedFlatMap(newSpecificBuilder, f) |
| 74 | + override def flatMap[B](f: Int => IterableOnce[B])(implicit @implicitNotFound(collection.BitSet.ordMsg) ev: Ordering[B]): SortedSet[B] = |
| 75 | + super[StrictOptimizedSortedSetOps].flatMap(f) |
| 76 | + |
| 77 | + override def collect(pf: PartialFunction[Int, Int]): BitSet = strictOptimizedCollect(newSpecificBuilder, pf) |
| 78 | + override def collect[B](pf: scala.PartialFunction[Int, B])(implicit @implicitNotFound(collection.BitSet.ordMsg) ev: Ordering[B]): SortedSet[B] = |
| 79 | + super[StrictOptimizedSortedSetOps].collect(pf) |
| 80 | + |
| 81 | + // necessary for disambiguation |
| 82 | + override def zip[B](that: scala.IterableOnce[B])(implicit @implicitNotFound(collection.BitSet.zipOrdMsg) ev: Ordering[(Int, B)]): SortedSet[(Int, B)] = |
| 83 | + super.zip(that) |
| 84 | + |
| 85 | + protected[this] def writeReplace(): AnyRef = new BitSet.SerializationProxy(this) |
| 86 | +} |
| 87 | + |
| 88 | +/** |
| 89 | + * $factoryInfo |
| 90 | + * @define Coll `immutable.BitSet` |
| 91 | + * @define coll immutable bitset |
| 92 | + */ |
| 93 | +@nowarn("cat=deprecation&msg=Implementation classes of BitSet should not be accessed directly") |
| 94 | +@SerialVersionUID(3L) |
| 95 | +object BitSet extends SpecificIterableFactory[Int, BitSet] { |
| 96 | + |
| 97 | + def fromSpecific(it: scala.collection.IterableOnce[Int]): BitSet = |
| 98 | + it match { |
| 99 | + case bs: BitSet => bs |
| 100 | + case _ => (newBuilder ++= it).result() |
| 101 | + } |
| 102 | + |
| 103 | + final val empty: BitSet = new BitSet1(0L) |
| 104 | + |
| 105 | + def newBuilder: Builder[Int, BitSet] = |
| 106 | + mutable.BitSet.newBuilder.mapResult(bs => fromBitMaskNoCopy(bs.elems)) |
| 107 | + |
| 108 | + private def createSmall(a: Long, b: Long): BitSet = if (b == 0L) new BitSet1(a) else new BitSet2(a, b) |
| 109 | + |
| 110 | + /** A bitset containing all the bits in an array */ |
| 111 | + def fromBitMask(elems: Array[Long]): BitSet = { |
| 112 | + val len = elems.length |
| 113 | + if (len == 0) empty |
| 114 | + else if (len == 1) new BitSet1(elems(0)) |
| 115 | + else if (len == 2) createSmall(elems(0), elems(1)) |
| 116 | + else { |
| 117 | + val a = java.util.Arrays.copyOf(elems, len) |
| 118 | + new BitSetN(a) |
| 119 | + } |
| 120 | + } |
| 121 | + |
| 122 | + /** A bitset containing all the bits in an array, wrapping the existing |
| 123 | + * array without copying. |
| 124 | + */ |
| 125 | + def fromBitMaskNoCopy(elems: Array[Long]): BitSet = { |
| 126 | + val len = elems.length |
| 127 | + if (len == 0) empty |
| 128 | + else if (len == 1) new BitSet1(elems(0)) |
| 129 | + else if (len == 2) createSmall(elems(0), elems(1)) |
| 130 | + else new BitSetN(elems) |
| 131 | + } |
| 132 | + |
| 133 | + @deprecated("Implementation classes of BitSet should not be accessed directly", "2.13.0") |
| 134 | + class BitSet1(val elems: Long) extends BitSet { |
| 135 | + protected[collection] def nwords = 1 |
| 136 | + protected[collection] def word(idx: Int) = if (idx == 0) elems else 0L |
| 137 | + protected[collection] def updateWord(idx: Int, w: Long): BitSet = |
| 138 | + if (idx == 0) new BitSet1(w) |
| 139 | + else if (idx == 1) createSmall(elems, w) |
| 140 | + else this.fromBitMaskNoCopy(updateArray(Array(elems), idx, w)) |
| 141 | + |
| 142 | + |
| 143 | + override def diff(other: collection.Set[Int]): BitSet = other match { |
| 144 | + case bs: collection.BitSet => bs.nwords match { |
| 145 | + case 0 => this |
| 146 | + case _ => |
| 147 | + val newElems = elems & ~bs.word(0) |
| 148 | + if (newElems == 0L) this.empty else new BitSet1(newElems) |
| 149 | + } |
| 150 | + case _ => super.diff(other) |
| 151 | + } |
| 152 | + |
| 153 | + override def filterImpl(pred: Int => Boolean, isFlipped: Boolean): BitSet = { |
| 154 | + val _elems = BitSetOps.computeWordForFilter(pred, isFlipped, elems, 0) |
| 155 | + if (_elems == 0L) this.empty else new BitSet1(_elems) |
| 156 | + } |
| 157 | + } |
| 158 | + |
| 159 | + @deprecated("Implementation classes of BitSet should not be accessed directly", "2.13.0") |
| 160 | + class BitSet2(val elems0: Long, val elems1: Long) extends BitSet { |
| 161 | + protected[collection] def nwords = 2 |
| 162 | + protected[collection] def word(idx: Int) = if (idx == 0) elems0 else if (idx == 1) elems1 else 0L |
| 163 | + protected[collection] def updateWord(idx: Int, w: Long): BitSet = |
| 164 | + if (idx == 0) new BitSet2(w, elems1) |
| 165 | + else if (idx == 1) createSmall(elems0, w) |
| 166 | + else this.fromBitMaskNoCopy(updateArray(Array(elems0, elems1), idx, w)) |
| 167 | + |
| 168 | + |
| 169 | + override def diff(other: collection.Set[Int]): BitSet = other match { |
| 170 | + case bs: collection.BitSet => bs.nwords match { |
| 171 | + case 0 => this |
| 172 | + case 1 => |
| 173 | + new BitSet2(elems0 & ~bs.word(0), elems1) |
| 174 | + case _ => |
| 175 | + val _elems0 = elems0 & ~bs.word(0) |
| 176 | + val _elems1 = elems1 & ~bs.word(1) |
| 177 | + |
| 178 | + if (_elems1 == 0L) { |
| 179 | + if (_elems0 == 0L) { |
| 180 | + this.empty |
| 181 | + } else { |
| 182 | + new BitSet1(_elems0) |
| 183 | + } |
| 184 | + } else { |
| 185 | + new BitSet2(_elems0, _elems1) |
| 186 | + } |
| 187 | + } |
| 188 | + case _ => super.diff(other) |
| 189 | + } |
| 190 | + |
| 191 | + override def filterImpl(pred: Int => Boolean, isFlipped: Boolean): BitSet = { |
| 192 | + val _elems0 = BitSetOps.computeWordForFilter(pred, isFlipped, elems0, 0) |
| 193 | + val _elems1 = BitSetOps.computeWordForFilter(pred, isFlipped, elems1, 1) |
| 194 | + |
| 195 | + if (_elems1 == 0L) { |
| 196 | + if (_elems0 == 0L) { |
| 197 | + this.empty |
| 198 | + } |
| 199 | + else new BitSet1(_elems0) |
| 200 | + } |
| 201 | + else new BitSet2(_elems0, _elems1) |
| 202 | + } |
| 203 | + } |
| 204 | + |
| 205 | + @deprecated("Implementation classes of BitSet should not be accessed directly", "2.13.0") |
| 206 | + class BitSetN(val elems: Array[Long]) extends BitSet { |
| 207 | + protected[collection] def nwords = elems.length |
| 208 | + |
| 209 | + protected[collection] def word(idx: Int) = if (idx < nwords) elems(idx) else 0L |
| 210 | + |
| 211 | + protected[collection] def updateWord(idx: Int, w: Long): BitSet = this.fromBitMaskNoCopy(updateArray(elems, idx, w)) |
| 212 | + |
| 213 | + override def diff(that: collection.Set[Int]): BitSet = that match { |
| 214 | + case bs: collection.BitSet => |
| 215 | + /* |
| 216 | + * Algorithm: |
| 217 | + * |
| 218 | + * We iterate, word-by-word, backwards from the shortest of the two bitsets (this, or bs) i.e. the one with |
| 219 | + * the fewer words. Two extra concerns for optimization are described below. |
| 220 | + * |
| 221 | + * Array Shrinking: |
| 222 | + * If `this` is not longer than `bs`, then since we must iterate through the full array of words, |
| 223 | + * we can track the new highest index word which is non-zero, at little additional cost. At the end, the new |
| 224 | + * Array[Long] allocated for the returned BitSet will only be of size `maxNonZeroIndex + 1` |
| 225 | + * |
| 226 | + * Tracking Changes: |
| 227 | + * If the two sets are disjoint, then we can return `this`. Therefor, until at least one change is detected, |
| 228 | + * we check each word for if it has changed from its corresponding word in `this`. Once a single change is |
| 229 | + * detected, we stop checking because the cost of the new Array must be paid anyways. |
| 230 | + */ |
| 231 | + |
| 232 | + val bsnwords = bs.nwords |
| 233 | + val thisnwords = nwords |
| 234 | + if (bsnwords >= thisnwords) { |
| 235 | + // here, we may have opportunity to shrink the size of the array |
| 236 | + // so, track the highest index which is non-zero. That ( + 1 ) will be our new array length |
| 237 | + var i = thisnwords - 1 |
| 238 | + var currentWord = 0L |
| 239 | + // if there are never any changes, we can return `this` at the end |
| 240 | + var anyChanges = false |
| 241 | + while (i >= 0 && currentWord == 0L) { |
| 242 | + val oldWord = word(i) |
| 243 | + currentWord = oldWord & ~bs.word(i) |
| 244 | + anyChanges ||= currentWord != oldWord |
| 245 | + i -= 1 |
| 246 | + } |
| 247 | + if (i < 0) { |
| 248 | + // all indices >= 0 have had result 0, so the bitset is empty |
| 249 | + this.empty |
| 250 | + } else { |
| 251 | + val minimumNonZeroIndex: Int = i + 1 |
| 252 | + while (!anyChanges && i >= 0) { |
| 253 | + val oldWord = word(i) |
| 254 | + currentWord = oldWord & ~bs.word(i) |
| 255 | + anyChanges ||= currentWord != oldWord |
| 256 | + i -= 1 |
| 257 | + } |
| 258 | + if (anyChanges) { |
| 259 | + if (minimumNonZeroIndex == -1) { |
| 260 | + this.empty |
| 261 | + } else if (minimumNonZeroIndex == 0) { |
| 262 | + new BitSet1(currentWord) |
| 263 | + } else if (minimumNonZeroIndex == 1) { |
| 264 | + new BitSet2(word(0) & ~bs.word(0), currentWord) |
| 265 | + } else { |
| 266 | + val newArray = elems.take(minimumNonZeroIndex + 1) |
| 267 | + newArray(i + 1) = currentWord |
| 268 | + while (i >= 0) { |
| 269 | + newArray(i) = word(i) & ~bs.word(i) |
| 270 | + i -= 1 |
| 271 | + } |
| 272 | + this.fromBitMaskNoCopy(newArray) |
| 273 | + } |
| 274 | + } else { |
| 275 | + this |
| 276 | + } |
| 277 | + } |
| 278 | + } else { |
| 279 | + var i = bsnwords - 1 |
| 280 | + var anyChanges = false |
| 281 | + var currentWord = 0L |
| 282 | + while (i >= 0 && !anyChanges) { |
| 283 | + val oldWord = word(i) |
| 284 | + currentWord = oldWord & ~bs.word(i) |
| 285 | + anyChanges ||= currentWord != oldWord |
| 286 | + i -= 1 |
| 287 | + } |
| 288 | + if (anyChanges) { |
| 289 | + val newElems = elems.clone() |
| 290 | + newElems(i + 1) = currentWord |
| 291 | + while (i >= 0) { |
| 292 | + newElems(i) = word(i) & ~bs.word(i) |
| 293 | + i -= 1 |
| 294 | + } |
| 295 | + this.fromBitMaskNoCopy(newElems) |
| 296 | + } else { |
| 297 | + this |
| 298 | + } |
| 299 | + } |
| 300 | + case _ => super.diff(that) |
| 301 | + } |
| 302 | + |
| 303 | + |
| 304 | + override def filterImpl(pred: Int => Boolean, isFlipped: Boolean): BitSet = { |
| 305 | + // here, we may have opportunity to shrink the size of the array |
| 306 | + // so, track the highest index which is non-zero. That ( + 1 ) will be our new array length |
| 307 | + var i = nwords - 1 |
| 308 | + var currentWord = 0L |
| 309 | + // if there are never any changes, we can return `this` at the end |
| 310 | + var anyChanges = false |
| 311 | + while (i >= 0 && currentWord == 0L) { |
| 312 | + val oldWord = word(i) |
| 313 | + currentWord = BitSetOps.computeWordForFilter(pred, isFlipped, oldWord, i) |
| 314 | + anyChanges ||= currentWord != oldWord |
| 315 | + i -= 1 |
| 316 | + } |
| 317 | + if (i < 0) { |
| 318 | + // all indices >= 0 have had result 0, so the bitset is empty |
| 319 | + if (currentWord == 0) this.empty else this.fromBitMaskNoCopy(Array(currentWord)) |
| 320 | + } else { |
| 321 | + val minimumNonZeroIndex: Int = i + 1 |
| 322 | + while (!anyChanges && i >= 0) { |
| 323 | + val oldWord = word(i) |
| 324 | + currentWord = BitSetOps.computeWordForFilter(pred, isFlipped, oldWord, i) |
| 325 | + anyChanges ||= currentWord != oldWord |
| 326 | + i -= 1 |
| 327 | + } |
| 328 | + if (anyChanges) { |
| 329 | + if (minimumNonZeroIndex == -1) { |
| 330 | + this.empty |
| 331 | + } else if (minimumNonZeroIndex == 0) { |
| 332 | + new BitSet1(currentWord) |
| 333 | + } else if (minimumNonZeroIndex == 1) { |
| 334 | + new BitSet2(BitSetOps.computeWordForFilter(pred, isFlipped, word(0), 0), currentWord) |
| 335 | + } else { |
| 336 | + val newArray = elems.take(minimumNonZeroIndex + 1) |
| 337 | + newArray(i + 1) = currentWord |
| 338 | + while (i >= 0) { |
| 339 | + newArray(i) = BitSetOps.computeWordForFilter(pred, isFlipped, word(i), i) |
| 340 | + i -= 1 |
| 341 | + } |
| 342 | + this.fromBitMaskNoCopy(newArray) |
| 343 | + } |
| 344 | + } else { |
| 345 | + this |
| 346 | + } |
| 347 | + } |
| 348 | + } |
| 349 | + |
| 350 | + override def toBitMask: Array[Long] = elems.clone() |
| 351 | + } |
| 352 | + |
| 353 | + @SerialVersionUID(3L) |
| 354 | + private final class SerializationProxy(coll: BitSet) extends scala.collection.BitSet.SerializationProxy(coll) { |
| 355 | + protected[this] def readResolve(): Any = BitSet.fromBitMaskNoCopy(elems) |
| 356 | + } |
| 357 | +} |
0 commit comments