421 lines
15 KiB
Kotlin
421 lines
15 KiB
Kotlin
/*
|
|
* Copyright 2023 dorkbox, llc
|
|
*
|
|
* 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.
|
|
*/
|
|
/*******************************************************************************
|
|
* Copyright 2011 LibGDX.
|
|
* Mario Zechner <badlogicgames></badlogicgames>@gmail.com>
|
|
* Nathan Sweet <nathan.sweet></nathan.sweet>@gmail.com>
|
|
*
|
|
* 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 dorkbox.collections
|
|
|
|
/**
|
|
* An [ObjectMap] that also stores keys in an [ArrayList] using the insertion order. Null keys are not allowed. No
|
|
* allocation is done except when growing the table size.
|
|
*
|
|
*
|
|
* Iteration over the [.entries], [.keys], and [.values] is ordered and faster than an unordered map. Keys
|
|
* can also be accessed and the order changed using [.orderedKeys]. There is some additional overhead for put and remove.
|
|
* When used for faster iteration versus ObjectMap and the order does not actually matter, copying during remove can be greatly
|
|
* reduced by setting [Array.ordered] to false for [OrderedMap.orderedKeys].
|
|
*
|
|
*
|
|
* This class performs fast contains (typically O(1), worst case O(n) but that is rare in practice). Remove is somewhat slower due
|
|
* to [.orderedKeys]. Add may be slightly slower, depending on hash collisions. Hashcodes are rehashed to reduce
|
|
* collisions and the need to resize. Load factors greater than 0.91 greatly increase the chances to resize to the next higher POT
|
|
* size.
|
|
*
|
|
*
|
|
* Unordered sets and maps are not designed to provide especially fast iteration. Iteration is faster with OrderedSet and
|
|
* OrderedMap.
|
|
*
|
|
*
|
|
* This implementation uses linear probing with the backward shift algorithm for removal. Hashcodes are rehashed using Fibonacci
|
|
* hashing, instead of the more common power-of-two mask, to better distribute poor hashCodes (see [Malte Skarupke's blog post](https://probablydance.com/2018/06/16/fibonacci-hashing-the-optimization-that-the-world-forgot-or-a-better-alternative-to-integer-modulo/)). Linear probing continues to work even when all hashCodes collide, just more slowly.
|
|
*
|
|
* @author Nathan Sweet
|
|
* @author Tommy Ettinger
|
|
*/
|
|
class OrderedMap<K, V> : ObjectMap<K, V> where K : Any, K : Comparable<K> {
|
|
companion object {
|
|
const val version = Collections.version
|
|
}
|
|
|
|
private val keys_: MutableList<K>
|
|
|
|
/**
|
|
* Creates a new map with an initial capacity of 51 and a load factor of 0.8.
|
|
*/
|
|
constructor(): this(51, 0.8F)
|
|
|
|
/**
|
|
* Creates a new map with a load factor of 0.8.
|
|
*
|
|
* @param initialCapacity The backing array size is initialCapacity / loadFactor, increased to the next power of two.
|
|
*/
|
|
constructor(initialCapacity: Int) : super(initialCapacity) {
|
|
keys_ = ArrayList(initialCapacity)
|
|
}
|
|
|
|
/**
|
|
* Creates a new map with the specified initial capacity and load factor. This map will hold initialCapacity items before
|
|
* growing the backing table.
|
|
*
|
|
* @param initialCapacity The backing array size is initialCapacity / loadFactor, increased to the next power of two.
|
|
* @param loadFactor The loadfactor used to determine backing array growth
|
|
*/
|
|
constructor(initialCapacity: Int, loadFactor: Float) : super(initialCapacity, loadFactor) {
|
|
keys_ = ArrayList(initialCapacity)
|
|
}
|
|
|
|
/**
|
|
* Creates a new map containing the items in the specified map.
|
|
*/
|
|
constructor(map: OrderedMap<out K, out V>) : super(map) {
|
|
keys_ = ArrayList(map.keys_)
|
|
}
|
|
|
|
/**
|
|
* Sorts this array. The array elements must implement [Comparable]. This method is not thread safe.
|
|
*/
|
|
fun sort() {
|
|
keys_.sort()
|
|
}
|
|
|
|
/**
|
|
* Sorts the array. This method is not thread safe.
|
|
*/
|
|
fun sort(comparator: Comparator<in K>) {
|
|
keys_.sortWith(comparator)
|
|
}
|
|
|
|
override fun put(key: K, value: V?): V? {
|
|
var i = locateKey(key)
|
|
if (i >= 0) { // Existing key was found.
|
|
val oldValue = valueTable[i]
|
|
valueTable[i] = value
|
|
return oldValue
|
|
}
|
|
|
|
i = -(i + 1) // Empty space was found.
|
|
keyTable[i] = key
|
|
valueTable[i] = value
|
|
keys_.add(key)
|
|
if (++mapSize >= threshold) resize(keyTable.size shl 1)
|
|
return null
|
|
}
|
|
|
|
fun putAll(map: OrderedMap<K, V>) {
|
|
ensureCapacity(map.size)
|
|
val keys = map.keys_
|
|
var i = 0
|
|
val n = map.keys_.size
|
|
while (i < n) {
|
|
val key = keys[i]
|
|
put(key, map.get(key)!!) // we know this is value, because we checked it earlier
|
|
i++
|
|
}
|
|
}
|
|
|
|
override fun remove(key: K): V? {
|
|
keys_.remove(key)
|
|
return super.remove(key)
|
|
}
|
|
|
|
fun removeIndex(index: Int): V? {
|
|
val itemAtIndex = keys_.removeAt(index)
|
|
return super.remove(itemAtIndex)
|
|
}
|
|
|
|
/**
|
|
* Changes the key `before` to `after` without changing its position in the order or its value. Returns true if
|
|
* `after` has been added to the OrderedMap and `before` has been removed; returns false if `after` is
|
|
* already present or `before` is not present. If you are iterating over an OrderedMap and have an index, you should
|
|
* prefer [.alterIndex], which doesn't need to search for an index like this does and so can be faster.
|
|
*
|
|
* @param before a key that must be present for this to succeed
|
|
* @param after a key that must not be in this map for this to succeed
|
|
*
|
|
* @return true if `before` was removed and `after` was added, false otherwise
|
|
*/
|
|
fun alter(before: K, after: K): Boolean {
|
|
if (containsKey(after)) return false
|
|
val index = keys_.indexOf(before)
|
|
if (index == -1) return false
|
|
val prev = super.remove(before)
|
|
if (prev != null) {
|
|
super.put(after, prev)
|
|
}
|
|
keys_[index] = after
|
|
return true
|
|
}
|
|
|
|
/**
|
|
* Changes the key at the given `index` in the order to `after`, without changing the ordering of other entries or
|
|
* any values. If `after` is already present, this returns false; it will also return false if `index` is invalid
|
|
* for the size of this map. Otherwise, it returns true. Unlike [.alter], this operates in constant time.
|
|
*
|
|
* @param index the index in the order of the key to change; must be non-negative and less than [.size]
|
|
* @param after the key that will replace the contents at `index`; this key must not be present for this to succeed
|
|
*
|
|
* @return true if `after` successfully replaced the key at `index`, false otherwise
|
|
*/
|
|
fun alterIndex(index: Int, after: K): Boolean {
|
|
if (index < 0 || index >= size || containsKey(after)) return false
|
|
val prev = super.remove(keys_[index])
|
|
if (prev != null) {
|
|
super.put(after, prev)
|
|
}
|
|
keys_[index] = after
|
|
return true
|
|
}
|
|
|
|
override fun clear(maximumCapacity: Int) {
|
|
keys_.clear()
|
|
super.clear(maximumCapacity)
|
|
}
|
|
|
|
override fun clear() {
|
|
keys_.clear()
|
|
super.clear()
|
|
}
|
|
|
|
fun orderedKeys(): List<K> {
|
|
return keys_
|
|
}
|
|
|
|
/**
|
|
* Returns an iterator for the entries in the map. Remove is supported.
|
|
*
|
|
* If [Collections.allocateIterators] is false, the same iterator instance is returned each time this method is called.
|
|
* Use the [OrderedMapEntries] constructor for nested or multithreaded iteration.
|
|
*/
|
|
@Suppress("UNCHECKED_CAST")
|
|
override fun entries(): Entries<K, V?> {
|
|
if (allocateIterators) return OrderedMapEntries(this as OrderedMap<K, V?>)
|
|
if (entries1 == null) {
|
|
entries1 = OrderedMapEntries(this as OrderedMap<K, V?>)
|
|
entries2 = OrderedMapEntries(this as OrderedMap<K, V?>)
|
|
}
|
|
if (!entries1!!.valid) {
|
|
entries1!!.reset()
|
|
entries1!!.valid = true
|
|
entries2!!.valid = false
|
|
return entries1 as Entries<K, V?>
|
|
}
|
|
entries2!!.reset()
|
|
entries2!!.valid = true
|
|
entries1!!.valid = false
|
|
return entries2 as Entries<K, V?>
|
|
}
|
|
|
|
/**
|
|
* Returns an iterator for the values in the map. Remove is supported.
|
|
*
|
|
* If [Collections.allocateIterators] is false, the same iterator instance is returned each time this method is called.
|
|
*
|
|
* Use the [OrderedMapValues] constructor for nested or multithreaded iteration.
|
|
*/
|
|
@Suppress("UNCHECKED_CAST")
|
|
override fun values(): Values<V?> {
|
|
if (allocateIterators) return OrderedMapValues(this as OrderedMap<K, V?>)
|
|
if (values1 == null) {
|
|
values1 = OrderedMapValues(this as OrderedMap<K, V?>)
|
|
values2 = OrderedMapValues(this as OrderedMap<K, V?>)
|
|
}
|
|
if (!values1!!.valid) {
|
|
values1!!.reset()
|
|
values1!!.valid = true
|
|
values2!!.valid = false
|
|
return values1 as Values<V?>
|
|
}
|
|
values2!!.reset()
|
|
values2!!.valid = true
|
|
values1!!.valid = false
|
|
return values2 as Values<V?>
|
|
}
|
|
|
|
/**
|
|
* Returns an iterator for the keys in the map. Remove is supported.
|
|
*
|
|
* If [Collections.allocateIterators] is false, the same iterator instance is returned each time this method is called.
|
|
*
|
|
* Use the [OrderedMapKeys] constructor for nested or multithreaded iteration.
|
|
*/
|
|
override fun keys(): Keys<K> {
|
|
if (allocateIterators) return OrderedMapKeys(this)
|
|
if (keys1 == null) {
|
|
keys1 = OrderedMapKeys(this)
|
|
keys2 = OrderedMapKeys(this)
|
|
}
|
|
if (!keys1!!.valid) {
|
|
keys1!!.reset()
|
|
keys1!!.valid = true
|
|
keys2!!.valid = false
|
|
return keys1 as Keys<K>
|
|
}
|
|
keys2!!.reset()
|
|
keys2!!.valid = true
|
|
keys1!!.valid = false
|
|
return keys2 as Keys<K>
|
|
}
|
|
|
|
@Suppress("KotlinConstantConditions")
|
|
override fun toString(separator: String, braces: Boolean): String {
|
|
if (size == 0) return if (braces) "{}" else ""
|
|
|
|
val buffer = StringBuilder(32)
|
|
if (braces) buffer.append('{')
|
|
val keys = keys_
|
|
|
|
var i = 0
|
|
val n = keys.size
|
|
while (i < n) {
|
|
val key = keys[i]
|
|
if (i > 0) buffer.append(separator)
|
|
buffer.append(if (key === this) "(this)" else key)
|
|
buffer.append('=')
|
|
val value = get(key)
|
|
buffer.append(if (value === this) "(this)" else value)
|
|
i++
|
|
}
|
|
|
|
if (braces) buffer.append('}')
|
|
return buffer.toString()
|
|
}
|
|
|
|
class OrderedMapEntries<K, V>(map: OrderedMap<K, V?>) : Entries<K, V?>(map) where K : Any, K : Comparable<K> {
|
|
private val keys: MutableList<K>
|
|
|
|
init {
|
|
keys = map.keys_
|
|
}
|
|
|
|
override fun reset() {
|
|
currentIndex = -1
|
|
nextIndex = 0
|
|
hasNext = map.size > 0
|
|
}
|
|
|
|
override fun next(): Entry<K, V?> {
|
|
if (!hasNext) throw NoSuchElementException()
|
|
if (!valid) throw RuntimeException("#iterator() cannot be used nested.")
|
|
currentIndex = nextIndex
|
|
|
|
val key = keys[nextIndex]
|
|
if (entry == null) {
|
|
entry = Entry(key, map.get(key), map)
|
|
} else {
|
|
entry!!.key = key
|
|
entry!!.value = map.get(key)
|
|
}
|
|
|
|
nextIndex++
|
|
hasNext = nextIndex < map.size
|
|
return entry!!
|
|
}
|
|
|
|
override fun remove() {
|
|
check(currentIndex >= 0) { "next must be called before remove." }
|
|
map.remove(entry!!.key)
|
|
nextIndex--
|
|
currentIndex = -1
|
|
}
|
|
}
|
|
|
|
class OrderedMapKeys<K>(map: OrderedMap<K, *>) : Keys<K>(map) where K : Any, K : Comparable<K> {
|
|
private val keys: MutableList<K>
|
|
|
|
init {
|
|
keys = map.keys_
|
|
}
|
|
|
|
override fun reset() {
|
|
currentIndex = -1
|
|
nextIndex = 0
|
|
hasNext = map.size > 0
|
|
}
|
|
|
|
override fun next(): K {
|
|
if (!hasNext) throw NoSuchElementException()
|
|
if (!valid) throw RuntimeException("#iterator() cannot be used nested.")
|
|
val key = keys[nextIndex]
|
|
currentIndex = nextIndex
|
|
nextIndex++
|
|
hasNext = nextIndex < map.size
|
|
return key
|
|
}
|
|
|
|
override fun remove() {
|
|
check(currentIndex >= 0) { "next must be called before remove." }
|
|
(map as OrderedMap<*, *>).removeIndex(currentIndex)
|
|
nextIndex = currentIndex
|
|
currentIndex = -1
|
|
}
|
|
|
|
@Suppress("USELESS_CAST", "UNCHECKED_CAST")
|
|
override fun toArray(): Array<K> {
|
|
return Array(keys.size - nextIndex) { next() as Any } as Array<K>
|
|
}
|
|
}
|
|
|
|
class OrderedMapValues<V>(map: OrderedMap<*, V?>) : Values<V?>(map) {
|
|
private val keys: MutableList<*>
|
|
|
|
init {
|
|
keys = map.keys_
|
|
}
|
|
|
|
override fun reset() {
|
|
currentIndex = -1
|
|
nextIndex = 0
|
|
hasNext = map.size > 0
|
|
}
|
|
|
|
override fun next(): V? {
|
|
if (!hasNext) throw NoSuchElementException()
|
|
if (!valid) throw RuntimeException("#iterator() cannot be used nested.")
|
|
val value = map.get(keys.get(nextIndex)!!)
|
|
currentIndex = nextIndex
|
|
nextIndex++
|
|
hasNext = nextIndex < map.size
|
|
return value
|
|
}
|
|
|
|
override fun remove() {
|
|
check(currentIndex >= 0) { "next must be called before remove." }
|
|
(map as OrderedMap<*, *>).removeIndex(currentIndex)
|
|
nextIndex = currentIndex
|
|
currentIndex = -1
|
|
}
|
|
|
|
override fun toArray(): Array<V?> {
|
|
@Suppress("UNCHECKED_CAST")
|
|
return Array(keys.size - nextIndex) { next() as Any } as Array<V?>
|
|
}
|
|
}
|
|
}
|