dotty.tools.dotc.util.HashSet.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scala3-compiler_3 Show documentation
Show all versions of scala3-compiler_3 Show documentation
scala3-compiler-bootstrapped
package dotty.tools.dotc.util
import dotty.tools.uncheckedNN
object HashSet:
/** The number of elements up to which dense packing is used.
* If the number of elements reaches `DenseLimit` a hash table is used instead
*/
inline val DenseLimit = 8
def from[T](xs: IterableOnce[T]): HashSet[T] =
val set = new HashSet[T]()
set ++= xs
set
/** A hash set that allows some privileged protected access to its internals
* @param initialCapacity Indicates the initial number of slots in the hash table.
* The actual number of slots is always a power of 2, so the
* initial size of the table will be the smallest power of two
* that is equal or greater than the given `initialCapacity`.
* Minimum value is 4.
* @param capacityMultiple The minimum multiple of capacity relative to used elements.
* The hash table will be re-sized once the number of elements
* multiplied by capacityMultiple exceeds the current size of the hash table.
* However, a table of size up to DenseLimit will be re-sized only
* once the number of elements reaches the table's size.
*/
class HashSet[T](initialCapacity: Int = 8, capacityMultiple: Int = 2) extends MutableSet[T] {
import HashSet.DenseLimit
private var used: Int = _
private var limit: Int = _
private var table: Array[AnyRef | Null] = _
clear()
private def allocate(capacity: Int) =
table = new Array[AnyRef | Null](capacity)
limit = if capacity <= DenseLimit then capacity - 1 else capacity / capacityMultiple
private def roundToPower(n: Int) =
if n < 4 then 4
else if Integer.bitCount(n) == 1 then n
else 1 << (32 - Integer.numberOfLeadingZeros(n))
def clear(resetToInitial: Boolean): Unit =
used = 0
if resetToInitial then allocate(roundToPower(initialCapacity))
else java.util.Arrays.fill(table, null)
/** The number of elements in the set */
def size: Int = used
protected def isDense = limit < DenseLimit
/** Hashcode, by default a processed `x.hashCode`, can be overridden */
protected def hash(key: T): Int =
val h = key.hashCode
// Part of the MurmurHash3 32 bit finalizer
val i = (h ^ (h >>> 16)) * 0x85EBCA6B
val j = (i ^ (i >>> 13)) & 0x7FFFFFFF
if j==0 then 0x41081989 else j
/** Hashcode, by default `equals`, can be overridden */
protected def isEqual(x: T, y: T): Boolean = x.equals(y)
/** Turn hashcode `x` into a table index */
protected def index(x: Int): Int = x & (table.length - 1)
protected def currentTable: Array[AnyRef | Null] = table
protected def firstIndex(x: T) = if isDense then 0 else index(hash(x))
protected def nextIndex(idx: Int) =
Stats.record(statsItem("miss"))
index(idx + 1)
protected def entryAt(idx: Int): T | Null = table(idx).asInstanceOf[T | Null]
protected def setEntry(idx: Int, x: T) = table(idx) = x.asInstanceOf[AnyRef | Null]
def lookup(x: T): T | Null =
Stats.record(statsItem("lookup"))
var idx = firstIndex(x)
var e: T | Null = entryAt(idx)
while e != null do
if isEqual(e.uncheckedNN, x) then return e
idx = nextIndex(idx)
e = entryAt(idx)
null
/** Add entry at `x` at index `idx` */
protected def addEntryAt(idx: Int, x: T): T =
Stats.record(statsItem("addEntryAt"))
setEntry(idx, x)
used += 1
if used > limit then growTable()
x
def put(x: T): T =
Stats.record(statsItem("put"))
var idx = firstIndex(x)
var e: T | Null = entryAt(idx)
while e != null do
// TODO: remove uncheckedNN when explicit-nulls is enabled for regule compiling
if isEqual(e.uncheckedNN, x) then return e.uncheckedNN
idx = nextIndex(idx)
e = entryAt(idx)
addEntryAt(idx, x)
def +=(x: T): Unit = put(x)
def remove(x: T): Boolean =
Stats.record(statsItem("remove"))
var idx = firstIndex(x)
var e: T | Null = entryAt(idx)
while e != null do
if isEqual(e.uncheckedNN, x) then
var hole = idx
while
idx = nextIndex(idx)
e = entryAt(idx)
e != null
do
val eidx = index(hash(e.uncheckedNN))
if isDense
|| index(eidx - (hole + 1)) > index(idx - (hole + 1))
// entry `e` at `idx` can move unless `index(hash(e))` is in
// the (ring-)interval [hole + 1 .. idx]
then
setEntry(hole, e.uncheckedNN)
hole = idx
table(hole) = null
used -= 1
return true
idx = nextIndex(idx)
e = entryAt(idx)
false
def -=(x: T): Unit =
remove(x)
private def addOld(x: T) =
Stats.record(statsItem("re-enter"))
var idx = firstIndex(x)
var e = entryAt(idx)
while e != null do
idx = nextIndex(idx)
e = entryAt(idx)
setEntry(idx, x)
def copyFrom(oldTable: Array[AnyRef | Null]): Unit =
if isDense then
Array.copy(oldTable, 0, table, 0, oldTable.length)
else
var idx = 0
while idx < oldTable.length do
val e: T | Null = oldTable(idx).asInstanceOf[T | Null]
if e != null then addOld(e.uncheckedNN)
idx += 1
protected def growTable(): Unit =
val oldTable = table
val newLength =
if oldTable.length == DenseLimit then DenseLimit * 2 * roundToPower(capacityMultiple)
else table.length * 2
allocate(newLength)
copyFrom(oldTable)
abstract class EntryIterator extends Iterator[T]:
def entry(idx: Int): T | Null
private var idx = 0
def hasNext =
while idx < table.length && table(idx) == null do idx += 1
idx < table.length
def next() =
require(hasNext)
try entry(idx).uncheckedNN finally idx += 1
def iterator: Iterator[T] = new EntryIterator():
def entry(idx: Int) = entryAt(idx)
override def toString: String =
iterator.mkString("HashSet(", ", ", ")")
protected def statsItem(op: String) =
val prefix = if isDense then "HashSet(dense)." else "HashSet."
val suffix = getClass.getSimpleName
s"$prefix$op $suffix"
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy