All Downloads are FREE. Search and download functionalities are using the official Maven repository.

main.shark.internal.hppc.LongScatterSet.kt Maven / Gradle / Ivy

There is a newer version: 3.0-alpha-8
Show newest version
/*
 *  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