-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathIntIntHashMap.kt
More file actions
135 lines (120 loc) · 5.02 KB
/
IntIntHashMap.kt
File metadata and controls
135 lines (120 loc) · 5.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package day4
import kotlinx.atomicfu.*
/**
* Int-to-Int hash map with open addressing and linear probes.
*/
class IntIntHashMap {
private var core = Core(INITIAL_CAPACITY)
/**
* Returns value for the corresponding key or zero if this key is not present.
*
* @param key a positive key.
* @return value for the corresponding or zero if this key is not present.
* @throws IllegalArgumentException if key is not positive.
*/
operator fun get(key: Int): Int {
require(key > 0) { "Key must be positive: $key" }
return toValue(core.getInternal(key))
}
/**
* Changes value for the corresponding key and returns old value or zero if key was not present.
*
* @param key a positive key.
* @param value a positive value.
* @return old value or zero if this key was not present.
* @throws IllegalArgumentException if key or value are not positive, or value is equal to
* [Integer.MAX_VALUE] which is reserved.
*/
fun put(key: Int, value: Int): Int {
require(key > 0) { "Key must be positive: $key" }
require(isValue(value)) { "Invalid value: $value" }
return toValue(putAndRehashWhileNeeded(key, value))
}
/**
* Removes value for the corresponding key and returns old value or zero if key was not present.
*
* @param key a positive key.
* @return old value or zero if this key was not present.
* @throws IllegalArgumentException if key is not positive.
*/
fun remove(key: Int): Int {
require(key > 0) { "Key must be positive: $key" }
return toValue(putAndRehashWhileNeeded(key, DEL_VALUE))
}
private fun putAndRehashWhileNeeded(key: Int, value: Int): Int {
while (true) {
val oldValue = core.putInternal(key, value)
if (oldValue != NEEDS_REHASH) return oldValue
core = core.rehash()
}
}
private class Core(capacity: Int) {
// Pairs of <key, value> here, the actual
// size of the map is twice as big.
val map = AtomicIntArray(2 * capacity)
val shift: Int
init {
val mask = capacity - 1
assert(mask > 0 && mask and capacity == 0) { "Capacity must be power of 2: $capacity" }
shift = 32 - Integer.bitCount(mask)
}
fun getInternal(key: Int): Int {
var index = index(key)
var probes = 0
while (map[index].value != key) { // optimize for successful lookup
if (map[index].value == NULL_KEY) return NULL_VALUE // not found -- no value
if (++probes >= MAX_PROBES) return NULL_VALUE
if (index == 0) index = map.size
index -= 2
}
// found key -- return value
return map[index + 1].value
}
fun putInternal(key: Int, value: Int): Int {
var index = index(key)
var probes = 0
while (map[index].value != key) { // optimize for successful lookup
if (map[index].value == NULL_KEY) {
// not found -- claim this slot
if (value == DEL_VALUE) return NULL_VALUE // remove of missing item, no need to claim slot
map[index].value = key
break
}
if (++probes >= MAX_PROBES) return NEEDS_REHASH
if (index == 0) index = map.size
index -= 2
}
// found key -- update value
val oldValue = map[index + 1].value
map[index + 1].value = value
return oldValue
}
fun rehash(): Core {
val newCore = Core(map.size) // map.length is twice the current capacity
var index = 0
while (index < map.size) {
if (isValue(map[index + 1].value)) {
val result = newCore.putInternal(map[index].value, map[index + 1].value)
assert(result == 0) { "Unexpected result during rehash: $result" }
}
index += 2
}
return newCore
}
/**
* Returns an initial index in map to look for a given key.
*/
fun index(key: Int): Int = (key * MAGIC ushr shift) * 2
}
}
private const val MAGIC = -0x61c88647 // golden ratio
private const val INITIAL_CAPACITY = 2 // !!! DO NOT CHANGE INITIAL CAPACITY !!!
private const val MAX_PROBES = 8 // max number of probes to find an item
private const val NULL_KEY = 0 // missing key (initial value)
private const val NULL_VALUE = 0 // missing value (initial value)
private const val DEL_VALUE = Int.MAX_VALUE // mark for removed value
private const val NEEDS_REHASH = -1 // returned by `putInternal` to indicate that rehash is needed
// Checks is the value is in the range of allowed values
private fun isValue(value: Int): Boolean = value in (1 until DEL_VALUE)
// Converts internal value to the public results of the methods
private fun toValue(value: Int): Int = if (isValue(value)) value else 0