main.shark.internal.hppc.LongLongScatterMap.kt Maven / Gradle / Ivy
/*
* Copyright 2010-2013, Carrot Search s.c., Boznicza 11/56, Poznan, Poland
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package shark.internal.hppc
import java.util.Locale
/**
* Code from com.carrotsearch.hppc.LongLongScatterMap copy pasted, inlined and converted to Kotlin.
*
* See https://github.com/carrotsearch/hppc .
*/
internal class LongLongScatterMap constructor(expectedElements: Int = 4) {
fun interface ForEachCallback {
fun onEntry(key: Long, value: Long)
}
/**
* The array holding keys.
*/
private var keys: LongArray = longArrayOf()
/**
* The array holding values.
*/
private var values: LongArray = longArrayOf()
/**
* The number of stored keys (assigned key slots), excluding the special
* "empty" key, if any (use [.size] instead).
*
* @see .size
*/
private var assigned: Int = 0
/**
* Mask for slot scans in [.keys].
*/
private var mask: Int = 0
/**
* Expand (rehash) [.keys] when [.assigned] hits this value.
*/
private var resizeAt: Int = 0
/**
* Special treatment for the "empty slot" key marker.
*/
private var hasEmptyKey: Boolean = false
/**
* The load factor for [.keys].
*/
private var loadFactor: Double = 0.75
val isEmpty: Boolean
get() = size == 0
init {
ensureCapacity(expectedElements)
}
operator fun set(
key: Long,
value: Long
): Long {
val mask = this.mask
if (key == 0L) {
hasEmptyKey = true
val previousValue = values[mask + 1]
values[mask + 1] = value
return previousValue
} else {
val keys = this.keys
var slot = hashKey(key) and mask
var existing = keys[slot]
while (existing != 0L) {
if (existing == key) {
val previousValue = values[slot]
values[slot] = value
return previousValue
}
slot = slot + 1 and mask
existing = keys[slot]
}
if (assigned == resizeAt) {
allocateThenInsertThenRehash(slot, key, value)
} else {
keys[slot] = key
values[slot] = value
}
assigned++
return 0L
}
}
fun remove(key: Long): Long {
val mask = this.mask
if (key == 0L) {
hasEmptyKey = false
val previousValue = values[mask + 1]
values[mask + 1] = 0L
return previousValue
} else {
val keys = this.keys
var slot = hashKey(key) and mask
var existing = keys[slot]
while (existing != 0L) {
if (existing == key) {
val previousValue = values[slot]
shiftConflictingKeys(slot)
return previousValue
}
slot = slot + 1 and mask
existing = keys[slot]
}
return 0L
}
}
/**
* Being given a key looks it up in the map and returns the slot where element sits, so it later
* can be retrieved with [getSlotValue]; return '-1' if element not found.
* Why so complicated and not just make [get] return null if value not found? The reason is performance:
* this approach prevents unnecessary boxing of the primitive long that would happen with nullable Long?
*/
fun getSlot(key: Long): Int {
if (key == 0L) {
return if (hasEmptyKey) mask + 1 else -1
} else {
val keys = this.keys
val mask = this.mask
var slot = hashKey(key) and mask
var existing = keys[slot]
while (existing != 0L) {
if (existing == key) {
return slot
}
slot = slot + 1 and mask
existing = keys[slot]
}
return -1
}
}
/**
* Being given a slot of element retrieves it from the collection
*/
fun getSlotValue(slot: Int): Long = values[slot]
/**
* Returns an element matching a provided [key]; throws [IllegalArgumentException] if element not found
*/
operator fun get(key: Long): Long {
val slot = getSlot(key)
require(slot != -1) { "Unknown key $key" }
return getSlotValue(slot)
}
fun forEach(forEachCallback: ForEachCallback) {
val max = mask + 1
var slot = -1
exitWhile@ while (true) {
if (slot < max) {
var existing: Long
slot++
while (slot < max) {
existing = keys[slot]
if (existing != 0L) {
forEachCallback.onEntry(existing, values[slot])
continue@exitWhile
}
slot++
}
}
if (slot == max && hasEmptyKey) {
slot++
forEachCallback.onEntry(0L, values[max])
continue@exitWhile
}
break@exitWhile
}
}
fun entrySequence(): Sequence {
val max = mask + 1
var slot = -1
return generateSequence {
if (slot < max) {
var existing: Long
slot++
while (slot < max) {
existing = keys[slot]
if (existing != 0L) {
return@generateSequence existing to values[slot]
}
slot++
}
}
if (slot == max && hasEmptyKey) {
slot++
return@generateSequence 0L to values[max]
}
return@generateSequence null
}
}
fun containsKey(key: Long): Boolean {
if (key == 0L) {
return hasEmptyKey
} else {
val keys = this.keys
val mask = this.mask
var slot = hashKey(key) and mask
var existing = keys[slot]
while (existing != 0L) {
if (existing == key) {
return true
}
slot = slot + 1 and mask
existing = keys[slot]
}
return false
}
}
fun release() {
assigned = 0
hasEmptyKey = false
allocateBuffers(HPPC.minBufferSize(4, loadFactor))
}
val size: Int
get() {
return assigned + if (hasEmptyKey) 1 else 0
}
fun ensureCapacity(expectedElements: Int) {
if (expectedElements > resizeAt) {
val prevKeys = this.keys
val prevValues = this.values
allocateBuffers(HPPC.minBufferSize(expectedElements, loadFactor))
if (!isEmpty) {
rehash(prevKeys, prevValues)
}
}
}
private fun hashKey(key: Long): Int {
return HPPC.mixPhi(key)
}
/**
* Rehash from old buffers to new buffers.
*/
private fun rehash(
fromKeys: LongArray,
fromValues: LongArray
) {
// Rehash all stored key/value pairs into the new buffers.
val keys = this.keys
val values = this.values
val mask = this.mask
var existing: Long
// Copy the zero element's slot, then rehash everything else.
var from = fromKeys.size - 1
keys[keys.size - 1] = fromKeys[from]
values[values.size - 1] = fromValues[from]
while (--from >= 0) {
existing = fromKeys[from]
if (existing != 0L) {
var slot = hashKey(existing) and mask
while (keys[slot] != 0L) {
slot = slot + 1 and mask
}
keys[slot] = existing
values[slot] = fromValues[from]
}
}
}
/**
* Allocate new internal buffers. This method attempts to allocate
* and assign internal buffers atomically (either allocations succeed or not).
*/
private fun allocateBuffers(arraySize: Int) {
// Ensure no change is done if we hit an OOM.
val prevKeys = this.keys
val prevValues = this.values
try {
val emptyElementSlot = 1
this.keys = LongArray(arraySize + emptyElementSlot)
this.values = LongArray(arraySize + emptyElementSlot)
} catch (e: OutOfMemoryError) {
this.keys = prevKeys
this.values = prevValues
throw RuntimeException(
String.format(
Locale.ROOT,
"Not enough memory to allocate buffers for rehashing: %d -> %d",
this.mask + 1,
arraySize
), e
)
}
this.resizeAt = HPPC.expandAtCount(arraySize, loadFactor)
this.mask = arraySize - 1
}
/**
* This method is invoked when there is a new key/ value pair to be inserted into
* the buffers but there is not enough empty slots to do so.
*
* New buffers are allocated. If this succeeds, we know we can proceed
* with rehashing so we assign the pending element to the previous buffer
* (possibly violating the invariant of having at least one empty slot)
* and rehash all keys, substituting new buffers at the end.
*/
private fun allocateThenInsertThenRehash(
slot: Int,
pendingKey: Long,
pendingValue: Long
) {
// Try to allocate new buffers first. If we OOM, we leave in a consistent state.
val prevKeys = this.keys
val prevValues = this.values
allocateBuffers(HPPC.nextBufferSize(mask + 1, size, loadFactor))
// We have succeeded at allocating new data so insert the pending key/value at
// the free slot in the old arrays before rehashing.
prevKeys[slot] = pendingKey
prevValues[slot] = pendingValue
// Rehash old keys, including the pending key.
rehash(prevKeys, prevValues)
}
/**
* Shift all the slot-conflicting keys and values allocated to
* (and including) `slot`.
*/
private fun shiftConflictingKeys(gapSlotArg: Int) {
var gapSlot = gapSlotArg
val keys = this.keys
val values = this.values
val mask = this.mask
// Perform shifts of conflicting keys to fill in the gap.
var distance = 0
while (true) {
val slot = gapSlot + ++distance and mask
val existing = keys[slot]
if (existing == 0L) {
break
}
val idealSlot = hashKey(existing)
val shift = slot - idealSlot and mask
if (shift >= distance) {
// Entry at this position was originally at or before the gap slot.
// Move the conflict-shifted entry to the gap's position and repeat the procedure
// for any entries to the right of the current position, treating it
// as the new gap.
keys[gapSlot] = existing
values[gapSlot] = values[slot]
gapSlot = slot
distance = 0
}
}
// Mark the last found gap slot without a conflict as empty.
keys[gapSlot] = 0L
values[gapSlot] = 0L
assigned--
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy