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

dorkbox.collections.OrderedSet.kt Maven / Gradle / Ivy

/*
 * 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 @gmail.com>
 * 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

import dorkbox.collections.Collections.allocateIterators
import java.util.Comparator

/**
 * A [ObjectSet] that also stores keys in an [ArrayList] using the insertion order. Null keys are not allowed.
 *
 *
 * [Iteration][.iterator] is ordered and faster than an unordered set.
 *
 * This class performs fast contains (typically O(1), worst case O(n) but that is rare in practice). Remove is somewhat slower due
 * to [.orderedItems]. 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 OrderedSet : ObjectSet where T : Any, T : Comparable {
    companion object {
        val version = Collections.version

        fun  with(vararg array: T): OrderedSet where T : Any, T : Comparable {
            val set = OrderedSet()
            set.addAll(*array)
            return set
        }
    }

    private val items: ArrayList

    @Transient
    var iterator1: OrderedSetIterator? = null

    @Transient
    var iterator2: OrderedSetIterator? = null

    constructor() {
        items = ArrayList()
    }

    constructor(initialCapacity: Int, loadFactor: Float) : super(initialCapacity, loadFactor) {
        items = ArrayList(initialCapacity)
    }

    constructor(initialCapacity: Int) : super(initialCapacity) {
        items = ArrayList(initialCapacity)
    }

    constructor(set: OrderedSet) : super(set) {
        items = ArrayList(set.items)
    }

    /** Sorts this array. The array elements must implement [Comparable]. This method is not thread safe.  */
    fun sort() {
        items.sort()
    }

    /** Sorts the array. This method is not thread safe.  */
    fun sort(comparator: Comparator) {
        items.sortWith(comparator)
    }

    override fun add(element: T): Boolean {
        if (!super.add(element)) return false
        items.add(element)
        return true
    }

    /**
     * Sets the key at the specfied index. Returns true if the key was added to the set or false if it was already in the set. If
     * this set already contains the key, the existing key's index is changed if needed and false is returned.
     */
    fun add(key: T, index: Int): Boolean {
        if (!super.add(key)) {
            var oldIndex = -1
            items.forEachIndexed { i, item ->
                if (item === key) {
                    oldIndex = i
                    return@forEachIndexed
                }
            }

            if (oldIndex != index) {
                val oldItem = items.removeAt(oldIndex)
                items.add(index, oldItem)
            }
            return false
        }
        items.add(index, key)
        return true
    }

    fun addAll(set: OrderedSet) {
        ensureCapacity(set.size)

        val keys = set.items
        var i = 0
        val n = set.items.size
        while (i < n) {
            add(keys[i])
            i++
        }
    }

    override fun remove(element: T): Boolean {
        if (!super.remove(element)) return false
        items.remove(element)
        return true
    }

    fun removeIndex(index: Int): T {
        val itemAtIndex = items.removeAt(index)
        super.remove(itemAtIndex)
        return itemAtIndex
    }

    /**
     * Changes the item `before` to `after` without changing its position in the order. Returns true if `after`
     * has been added to the OrderedSet and `before` has been removed; returns false if `after` is already present or
     * `before` is not present. If you are iterating over an OrderedSet 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 an item that must be present for this to succeed
     * @param after an item that must not be in this set for this to succeed
     *
     * @return true if `before` was removed and `after` was added, false otherwise
     */
    fun alter(before: T, after: T): Boolean {
        if (contains(after)) return false
        if (!super.remove(before)) return false
        super.add(after)
        items[items.indexOf(before)] = after
        return true
    }

    /**
     * Changes the item at the given `index` in the order to `after`, without changing the ordering of other items. If
     * `after` is already present, this returns false; it will also return false if `index` is invalid for the size of
     * this set. Otherwise, it returns true. Unlike [.alter], this operates in constant time.
     *
     * @param index the index in the order of the item to change; must be non-negative and less than [.size]
     * @param after the item that will replace the contents at `index`; this item must not be present for this to succeed
     *
     * @return true if `after` successfully replaced the contents at `index`, false otherwise
     */
    fun alterIndex(index: Int, after: T): Boolean {
        if (index < 0 || index >= size || contains(after)) return false
        super.remove(items[index])
        super.add(after)
        items[index] = after
        return true
    }

    override fun clear(maximumCapacity: Int) {
        items.clear()
        super.clear(maximumCapacity)
    }

    override fun clear() {
        items.clear()
        super.clear()
    }

    fun orderedItems(): ArrayList {
        return items
    }

    override fun iterator(): OrderedSetIterator {
        if (allocateIterators) return OrderedSetIterator(this)
        if (iterator1 == null) {
            iterator1 = OrderedSetIterator(this)
            iterator2 = OrderedSetIterator(this)
        }
        if (!iterator1!!.valid) {
            iterator1!!.reset()
            iterator1!!.valid = true
            iterator2!!.valid = false
            return iterator1 as OrderedSetIterator
        }
        iterator2!!.reset()
        iterator2!!.valid = true
        iterator1!!.valid = false
        return iterator2 as OrderedSetIterator
    }

    override fun toString(): String {
        if (size == 0) return "{}"
        val items = items
        val buffer = StringBuilder(32)
        buffer.append('{')
        buffer.append(items[0])
        for (i in 1 until size) {
            buffer.append(", ")
            buffer.append(items[i])
        }
        buffer.append('}')
        return buffer.toString()
    }

    override fun toString(separator: String): String {
        return items.joinToString(separator)
    }

    class OrderedSetIterator(set: OrderedSet) : ObjectSetIterator(set) where T : Any, T : Comparable {
        private val items: ArrayList

        init {
            items = set.items
        }

        override fun reset() {
            nextIndex = 0
            hasNext = set.size > 0
        }

        override fun next(): T {
            if (!hasNext) throw NoSuchElementException()
            if (!valid) throw RuntimeException("#iterator() cannot be used nested.")
            val key = items[nextIndex]
            nextIndex++
            hasNext = nextIndex < set.size
            return key
        }

        override fun remove() {
            check(nextIndex >= 0) { "next must be called before remove." }
            nextIndex--
            (set as OrderedSet<*>).removeIndex(nextIndex)
        }

        @Suppress("USELESS_CAST", "UNCHECKED_CAST")
        override fun toArray(): Array {
            return Array(set.size - nextIndex) { next() as Any } as Array
        }

        override fun toArray(array: Array): Array {
            var i = nextIndex
            while(hasNext) {
                array[i++] = next()
            }

            nextIndex = items.size
            hasNext = false
            return array
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy