|
| 1 | +package dotty.tools.dotc.util |
| 2 | + |
| 3 | +import dotty.tools.uncheckedNN |
| 4 | + |
| 5 | +object GenericHashSet: |
| 6 | + |
| 7 | + /** The number of elements up to which dense packing is used. |
| 8 | + * If the number of elements reaches `DenseLimit` a hash table is used instead |
| 9 | + */ |
| 10 | + inline val DenseLimit = 8 |
| 11 | + |
| 12 | +/** A hash set that allows some privileged protected access to its internals |
| 13 | + * @param initialCapacity Indicates the initial number of slots in the hash table. |
| 14 | + * The actual number of slots is always a power of 2, so the |
| 15 | + * initial size of the table will be the smallest power of two |
| 16 | + * that is equal or greater than the given `initialCapacity`. |
| 17 | + * Minimum value is 4. |
| 18 | +* @param capacityMultiple The minimum multiple of capacity relative to used elements. |
| 19 | + * The hash table will be re-sized once the number of elements |
| 20 | + * multiplied by capacityMultiple exceeds the current size of the hash table. |
| 21 | + * However, a table of size up to DenseLimit will be re-sized only |
| 22 | + * once the number of elements reaches the table's size. |
| 23 | + */ |
| 24 | +abstract class GenericHashSet[T](initialCapacity: Int = 8, capacityMultiple: Int = 2) extends MutableSet[T] { |
| 25 | + import GenericHashSet.DenseLimit |
| 26 | + |
| 27 | + protected var used: Int = _ |
| 28 | + protected var limit: Int = _ |
| 29 | + protected var table: Array[AnyRef | Null] = _ |
| 30 | + |
| 31 | + clear() |
| 32 | + |
| 33 | + private def allocate(capacity: Int) = |
| 34 | + table = new Array[AnyRef | Null](capacity) |
| 35 | + limit = if capacity <= DenseLimit then capacity - 1 else capacity / capacityMultiple |
| 36 | + |
| 37 | + private def roundToPower(n: Int) = |
| 38 | + if n < 4 then 4 |
| 39 | + else if Integer.bitCount(n) == 1 then n |
| 40 | + else 1 << (32 - Integer.numberOfLeadingZeros(n)) |
| 41 | + |
| 42 | + def clear(resetToInitial: Boolean): Unit = |
| 43 | + used = 0 |
| 44 | + if resetToInitial then allocate(roundToPower(initialCapacity)) |
| 45 | + else java.util.Arrays.fill(table, null) |
| 46 | + |
| 47 | + /** The number of elements in the set */ |
| 48 | + def size: Int = used |
| 49 | + |
| 50 | + protected def isDense = limit < DenseLimit |
| 51 | + |
| 52 | + /** Hashcode, by default a processed `x.hashCode`, can be overridden */ |
| 53 | + protected def hash(key: T): Int |
| 54 | + |
| 55 | + /** Hashcode, by default `equals`, can be overridden */ |
| 56 | + protected def isEqual(x: T, y: T): Boolean |
| 57 | + |
| 58 | + /** Turn hashcode `x` into a table index */ |
| 59 | + private def index(x: Int): Int = x & (table.length - 1) |
| 60 | + |
| 61 | + protected def currentTable: Array[AnyRef | Null] = table |
| 62 | + |
| 63 | + private def firstIndex(x: T) = if isDense then 0 else index(hash(x)) |
| 64 | + private def nextIndex(idx: Int) = |
| 65 | + Stats.record(statsItem("miss")) |
| 66 | + index(idx + 1) |
| 67 | + |
| 68 | + private def entryAt(idx: Int): T | Null = table(idx).asInstanceOf[T | Null] |
| 69 | + private def setEntry(idx: Int, x: T) = table(idx) = x.asInstanceOf[AnyRef | Null] |
| 70 | + |
| 71 | + def lookup(x: T): T | Null = |
| 72 | + Stats.record(statsItem("lookup")) |
| 73 | + var idx = firstIndex(x) |
| 74 | + var e: T | Null = entryAt(idx) |
| 75 | + while e != null do |
| 76 | + if isEqual(e.uncheckedNN, x) then return e |
| 77 | + idx = nextIndex(idx) |
| 78 | + e = entryAt(idx) |
| 79 | + null |
| 80 | + |
| 81 | + /** Add entry at `x` at index `idx` */ |
| 82 | + private def addEntryAt(idx: Int, x: T): T = |
| 83 | + Stats.record(statsItem("addEntryAt")) |
| 84 | + setEntry(idx, x) |
| 85 | + used += 1 |
| 86 | + if used > limit then growTable() |
| 87 | + x |
| 88 | + |
| 89 | + /** attempts to put `x` in the Set, if it was not entered before, return true, else return false. */ |
| 90 | + override def add(x: T): Boolean = |
| 91 | + Stats.record(statsItem("enter")) |
| 92 | + var idx = firstIndex(x) |
| 93 | + var e: T | Null = entryAt(idx) |
| 94 | + while e != null do |
| 95 | + if isEqual(e.uncheckedNN, x) then return false // already entered |
| 96 | + idx = nextIndex(idx) |
| 97 | + e = entryAt(idx) |
| 98 | + addEntryAt(idx, x) |
| 99 | + true // first entry |
| 100 | + |
| 101 | + def put(x: T): T = |
| 102 | + Stats.record(statsItem("put")) |
| 103 | + var idx = firstIndex(x) |
| 104 | + var e: T | Null = entryAt(idx) |
| 105 | + while e != null do |
| 106 | + // TODO: remove uncheckedNN when explicit-nulls is enabled for regule compiling |
| 107 | + if isEqual(e.uncheckedNN, x) then return e.uncheckedNN |
| 108 | + idx = nextIndex(idx) |
| 109 | + e = entryAt(idx) |
| 110 | + addEntryAt(idx, x) |
| 111 | + |
| 112 | + def +=(x: T): Unit = put(x) |
| 113 | + |
| 114 | + def remove(x: T): Boolean = |
| 115 | + Stats.record(statsItem("remove")) |
| 116 | + var idx = firstIndex(x) |
| 117 | + var e: T | Null = entryAt(idx) |
| 118 | + while e != null do |
| 119 | + if isEqual(e.uncheckedNN, x) then |
| 120 | + var hole = idx |
| 121 | + while |
| 122 | + idx = nextIndex(idx) |
| 123 | + e = entryAt(idx) |
| 124 | + e != null |
| 125 | + do |
| 126 | + val eidx = index(hash(e.uncheckedNN)) |
| 127 | + if isDense |
| 128 | + || index(eidx - (hole + 1)) > index(idx - (hole + 1)) |
| 129 | + // entry `e` at `idx` can move unless `index(hash(e))` is in |
| 130 | + // the (ring-)interval [hole + 1 .. idx] |
| 131 | + then |
| 132 | + setEntry(hole, e.uncheckedNN) |
| 133 | + hole = idx |
| 134 | + table(hole) = null |
| 135 | + used -= 1 |
| 136 | + return true |
| 137 | + idx = nextIndex(idx) |
| 138 | + e = entryAt(idx) |
| 139 | + false |
| 140 | + |
| 141 | + def -=(x: T): Unit = |
| 142 | + remove(x) |
| 143 | + |
| 144 | + private def addOld(x: T) = |
| 145 | + Stats.record(statsItem("re-enter")) |
| 146 | + var idx = firstIndex(x) |
| 147 | + var e = entryAt(idx) |
| 148 | + while e != null do |
| 149 | + idx = nextIndex(idx) |
| 150 | + e = entryAt(idx) |
| 151 | + setEntry(idx, x) |
| 152 | + |
| 153 | + def copyFrom(oldTable: Array[AnyRef | Null]): Unit = |
| 154 | + if isDense then |
| 155 | + Array.copy(oldTable, 0, table, 0, oldTable.length) |
| 156 | + else |
| 157 | + var idx = 0 |
| 158 | + while idx < oldTable.length do |
| 159 | + val e: T | Null = oldTable(idx).asInstanceOf[T | Null] |
| 160 | + if e != null then addOld(e.uncheckedNN) |
| 161 | + idx += 1 |
| 162 | + |
| 163 | + protected def growTable(): Unit = |
| 164 | + val oldTable = table |
| 165 | + val newLength = |
| 166 | + if oldTable.length == DenseLimit then DenseLimit * 2 * roundToPower(capacityMultiple) |
| 167 | + else table.length * 2 |
| 168 | + allocate(newLength) |
| 169 | + copyFrom(oldTable) |
| 170 | + |
| 171 | + abstract class EntryIterator extends Iterator[T]: |
| 172 | + def entry(idx: Int): T | Null |
| 173 | + private var idx = 0 |
| 174 | + def hasNext = |
| 175 | + while idx < table.length && table(idx) == null do idx += 1 |
| 176 | + idx < table.length |
| 177 | + def next() = |
| 178 | + require(hasNext) |
| 179 | + try entry(idx).uncheckedNN finally idx += 1 |
| 180 | + |
| 181 | + def iterator: Iterator[T] = new EntryIterator(): |
| 182 | + def entry(idx: Int) = entryAt(idx) |
| 183 | + |
| 184 | + override def toString: String = |
| 185 | + iterator.mkString("HashSet(", ", ", ")") |
| 186 | + |
| 187 | + protected def statsItem(op: String) = |
| 188 | + val prefix = if isDense then "HashSet(dense)." else "HashSet." |
| 189 | + val suffix = getClass.getSimpleName |
| 190 | + s"$prefix$op $suffix" |
| 191 | +} |
0 commit comments