main.shark.internal.hppc.LongScatterSet.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.LongScatterSet copy pasted, inlined and converted to Kotlin.
*
* See https://github.com/carrotsearch/hppc .
*/
internal class LongScatterSet(expectedElements: Int = 4) {
/** The hash array holding keys. */
private var keys: LongArray = longArrayOf()
/**
* The number of stored keys (assigned key slots), excluding the special
* "empty" key, if any.
*
* @see .size
* @see .hasEmptyKey
*/
private var assigned = 0
/**
* Mask for slot scans in [.keys].
*/
private var mask = 0
/**
* Expand (rehash) [.keys] when [.assigned] hits this value.
*/
private var resizeAt = 0
/**
* Special treatment for the "empty slot" key marker.
*/
private var hasEmptyKey = false
/**
* The load factor for [.keys].
*/
private val loadFactor = 0.75
fun clear() {
keys.fill(0)
assigned = 0
hasEmptyKey = false
}
init {
ensureCapacity(expectedElements)
}
fun elementSequence(): 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
}
slot++
}
}
if (slot == max && hasEmptyKey) {
slot++
return@generateSequence 0L
}
return@generateSequence null
}
}
private fun hashKey(key: Long): Int {
return HPPC.mixPhi(key)
}
operator fun plusAssign(key: Long) {
add(key)
}
fun add(key: Long): Boolean {
if (key == 0L) {
val added = !hasEmptyKey
hasEmptyKey = true
return added
} 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 false
}
slot = slot + 1 and mask
existing = keys[slot]
}
if (assigned == resizeAt) {
allocateThenInsertThenRehash(slot, key)
} else {
keys[slot] = key
}
assigned++
return true
}
}
operator fun contains(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 remove(key: Long): Boolean {
return if (key == 0L) {
val hadEmptyKey = hasEmptyKey
hasEmptyKey = false
hadEmptyKey
} else {
val keys = this.keys
val mask = this.mask
var slot = hashKey(key) and mask
var existing: Long = keys[slot]
while (existing != 0L) {
if (existing == key) {
shiftConflictingKeys(slot)
return true
}
slot = slot + 1 and mask
existing = keys[slot]
}
false
}
}
/**
* Shift all the slot-conflicting keys allocated to (and including) `slot`.
*/
private fun shiftConflictingKeys(inputGapSlot: Int) {
var gapSlot = inputGapSlot
val keys = keys
val mask = 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
gapSlot = slot
distance = 0
}
}
// Mark the last found gap slot without a conflict as empty.
keys[gapSlot] = 0L
assigned--
}
fun release() {
assigned = 0
hasEmptyKey = false
allocateBuffers(HPPC.minBufferSize(4, loadFactor))
}
fun ensureCapacity(expectedElements: Int) {
if (expectedElements > resizeAt) {
val prevKeys = this.keys
allocateBuffers(HPPC.minBufferSize(expectedElements, loadFactor))
if (size() != 0) {
rehash(prevKeys)
}
}
}
fun size(): Int {
return assigned + if (hasEmptyKey) 1 else 0
}
private fun rehash(fromKeys: LongArray) {
// Rehash all stored keys into the new buffers.
val keys = this.keys
val mask = this.mask
var existing: Long
var i = fromKeys.size - 1
while (--i >= 0) {
existing = fromKeys[i]
if (existing != 0L) {
var slot = hashKey(existing) and mask
while (keys[slot] != 0L) {
slot = slot + 1 and mask
}
keys[slot] = existing
}
}
}
/**
* 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
try {
val emptyElementSlot = 1
this.keys = LongArray(arraySize + emptyElementSlot)
} catch (e: OutOfMemoryError) {
this.keys = prevKeys
throw RuntimeException(
String.format(
Locale.ROOT,
"Not enough memory to allocate buffers for rehashing: %d -> %d",
size(),
arraySize
), e
)
}
this.resizeAt = HPPC.expandAtCount(arraySize, loadFactor)
this.mask = arraySize - 1
}
private fun allocateThenInsertThenRehash(
slot: Int,
pendingKey: Long
) {
// Try to allocate new buffers first. If we OOM, we leave in a consistent state.
val prevKeys = this.keys
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
// Rehash old keys, including the pending key.
rehash(prevKeys)
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy