|
| 1 | +package dotty.tools.dotc.util |
| 2 | + |
| 3 | +object HashTable: |
| 4 | + /** The number of elements up to which dense packing is used. |
| 5 | + * If the number of elements reaches `DenseLimit` a hash table is used instead |
| 6 | + */ |
| 7 | + inline val DenseLimit = 8 |
| 8 | + |
| 9 | +/** A hash table using open hashing with linear scan which is also very space efficient |
| 10 | + * at small sizes. |
| 11 | + * @param initialCapacity Indicates the initial number of slots in the hash table. |
| 12 | + * The actual number of slots is always a power of 2, so the |
| 13 | + * initial size of the table will be the smallest power of two |
| 14 | + * that is equal or greater than the given `initialCapacity`. |
| 15 | + * Minimum value is 4. |
| 16 | + * @param capacityMultiple The minimum multiple of capacity relative to used elements. |
| 17 | + * The hash table will be re-sized once the number of elements |
| 18 | + * multiplied by capacityMultiple exceeds the current size of the hash table. |
| 19 | + * However, a table of size up to DenseLimit will be re-sized only |
| 20 | + * once the number of elements reaches the table's size. |
| 21 | + */ |
| 22 | +class HashTable[Key >: Null <: AnyRef, Value >: Null <: AnyRef] |
| 23 | + (initialCapacity: Int = 8, capacityMultiple: Int = 3): |
| 24 | + import HashTable.DenseLimit |
| 25 | + |
| 26 | + private var used: Int = _ |
| 27 | + private var limit: Int = _ |
| 28 | + private var table: Array[AnyRef] = _ |
| 29 | + clear() |
| 30 | + |
| 31 | + private def allocate(capacity: Int) = |
| 32 | + table = new Array[AnyRef](capacity * 2) |
| 33 | + limit = if capacity <= DenseLimit then capacity - 1 else capacity / capacityMultiple |
| 34 | + |
| 35 | + private def roundToPower(n: Int) = |
| 36 | + if Integer.bitCount(n) == 1 then n |
| 37 | + else 1 << (32 - Integer.numberOfLeadingZeros(n)) |
| 38 | + |
| 39 | + /** Remove all elements from this table and set back to initial configuration */ |
| 40 | + def clear(): Unit = |
| 41 | + used = 0 |
| 42 | + allocate(roundToPower(initialCapacity max 4)) |
| 43 | + |
| 44 | + /** The number of elements in the set */ |
| 45 | + def size: Int = used |
| 46 | + |
| 47 | + private def isDense = limit < DenseLimit |
| 48 | + |
| 49 | + /** Hashcode, by default `System.identityHashCode`, but can be overriden */ |
| 50 | + protected def hash(x: Key): Int = System.identityHashCode(x) |
| 51 | + |
| 52 | + /** Equality, by default `eq`, but can be overridden */ |
| 53 | + protected def isEqual(x: Key, y: Key): Boolean = x eq y |
| 54 | + |
| 55 | + /** Turn hashcode `x` into a table index */ |
| 56 | + private def index(x: Int): Int = x & (table.length - 2) |
| 57 | + |
| 58 | + private def firstIndex(key: Key) = if isDense then 0 else index(hash(key)) |
| 59 | + private def nextIndex(idx: Int) = index(idx + 2) |
| 60 | + |
| 61 | + private def keyAt(idx: Int): Key = table(idx).asInstanceOf[Key] |
| 62 | + private def valueAt(idx: Int): Value = table(idx + 1).asInstanceOf[Value] |
| 63 | + |
| 64 | + /** Find entry such that `isEqual(x, entry)`. If it exists, return it. |
| 65 | + * If not, enter `x` in set and return `x`. |
| 66 | + */ |
| 67 | + def lookup(key: Key): Value = |
| 68 | + var idx = firstIndex(key) |
| 69 | + var k = keyAt(idx) |
| 70 | + while k != null do |
| 71 | + if isEqual(k, key) then return valueAt(idx) |
| 72 | + idx = nextIndex(idx) |
| 73 | + k = keyAt(idx) |
| 74 | + null |
| 75 | + |
| 76 | + def enter(key: Key, value: Value): Unit = |
| 77 | + var idx = firstIndex(key) |
| 78 | + var k = keyAt(idx) |
| 79 | + while k != null do |
| 80 | + if isEqual(k, key) then |
| 81 | + table(idx + 1) = value |
| 82 | + return |
| 83 | + idx = nextIndex(idx) |
| 84 | + k = keyAt(idx) |
| 85 | + table(idx) = key |
| 86 | + table(idx + 1) = value |
| 87 | + used += 1 |
| 88 | + if used > limit then growTable() |
| 89 | + |
| 90 | + def invalidate(key: Key): Unit = |
| 91 | + var idx = firstIndex(key) |
| 92 | + var k = keyAt(idx) |
| 93 | + while k != null do |
| 94 | + if isEqual(k, key) then |
| 95 | + var hole = idx |
| 96 | + if !isDense then |
| 97 | + while |
| 98 | + idx = nextIndex(idx) |
| 99 | + k = keyAt(idx) |
| 100 | + k != null && index(hash(k)) != idx |
| 101 | + do |
| 102 | + table(hole) = k |
| 103 | + table(hole + 1) = valueAt(idx) |
| 104 | + hole = idx |
| 105 | + table(hole) = null |
| 106 | + used -= 1 |
| 107 | + return |
| 108 | + idx = nextIndex(idx) |
| 109 | + k = keyAt(idx) |
| 110 | + |
| 111 | + private def addOld(key: Key, value: AnyRef): Unit = |
| 112 | + var idx = firstIndex(key) |
| 113 | + var k = keyAt(idx) |
| 114 | + while k != null do |
| 115 | + idx = nextIndex(idx) |
| 116 | + k = keyAt(idx) |
| 117 | + table(idx) = key |
| 118 | + table(idx + 1) = value |
| 119 | + |
| 120 | + private def growTable(): Unit = |
| 121 | + val oldTable = table |
| 122 | + val newLength = |
| 123 | + if oldTable.length == DenseLimit then DenseLimit * 2 * roundToPower(capacityMultiple) |
| 124 | + else table.length |
| 125 | + allocate(newLength) |
| 126 | + if isDense then |
| 127 | + Array.copy(oldTable, 0, table, 0, oldTable.length) |
| 128 | + else |
| 129 | + var idx = 0 |
| 130 | + while idx < oldTable.length do |
| 131 | + val key = oldTable(idx).asInstanceOf[Key] |
| 132 | + if key != null then addOld(key, oldTable(idx + 1)) |
| 133 | + idx += 2 |
| 134 | + |
| 135 | + def iterator: Iterator[(Key, Value)] = |
| 136 | + for idx <- (0 until table.length by 2).iterator |
| 137 | + if keyAt(idx) != null |
| 138 | + yield (keyAt(idx), valueAt(idx)) |
| 139 | + |
| 140 | + override def toString: String = |
| 141 | + iterator.map((k, v) => s"$k -> $v").mkString("LinearTable(", ", ", ")") |
| 142 | +end HashTable |
0 commit comments