commonMain.com.diffplug.selfie.ArrayMap.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of selfie-lib-jvm Show documentation
Show all versions of selfie-lib-jvm Show documentation
Core logic and parsing for Selfie
/*
* Copyright (C) 2023-2024 DiffPlug
*
* 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
*
* https://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 com.diffplug.selfie
import kotlin.collections.binarySearch
private val STRING_SLASHFIRST =
Comparator { a, b ->
var i = 0
while (i < a.length && i < b.length) {
val charA = a[i]
val charB = b[i]
if (charA != charB) {
return@Comparator ( // treat
if (charA == '/') -1 // slash as
else if (charB == '/') 1 // the lowest
else charA.compareTo(charB) // character
)
}
i++
}
a.length.compareTo(b.length)
}
private val PAIR_STRING_SLASHFIRST =
Comparator> { a, b -> STRING_SLASHFIRST.compare(a.first, b.first) }
abstract class ListBackedSet() : AbstractSet() {
abstract operator fun get(index: Int): T
override fun iterator(): Iterator =
object : Iterator {
private var index = 0
override fun hasNext(): Boolean = index < size
override fun next(): T {
if (!hasNext()) throw NoSuchElementException()
return get(index++)
}
}
}
private fun > ListBackedSet.binarySearch(element: T): Int {
val list =
object : AbstractList() {
override val size: Int
get() = [email protected]
override fun get(index: Int): T = this@binarySearch[index]
}
return if (element is String) {
(list as List).binarySearch(element, STRING_SLASHFIRST)
} else list.binarySearch(element)
}
internal expect fun entry(key: K, value: V): Map.Entry
/**
* An immutable, sorted, array-backed map - if keys are [String], they have a special sort order
* where `/` is the lowest key.
*/
class ArrayMap, V : Any>(private val data: Array) : Map {
fun minusSortedIndices(indicesToRemove: List): ArrayMap {
if (indicesToRemove.isEmpty()) {
return this
}
val newData = arrayOfNulls(data.size - indicesToRemove.size * 2)
var newDataIdx = 0
var currentPairIdx = 0 // Index for key-value pairs
var toRemoveIdx = 0
while (currentPairIdx < data.size / 2) {
if (toRemoveIdx < indicesToRemove.size && currentPairIdx == indicesToRemove[toRemoveIdx]) {
toRemoveIdx++
} else {
check(newDataIdx < newData.size) {
"The indices weren't sorted or were >= size=$size: $indicesToRemove"
}
newData[newDataIdx++] = data[2 * currentPairIdx] // Copy key
newData[newDataIdx++] = data[2 * currentPairIdx + 1] // Copy value
}
currentPairIdx++
}
// Ensure all indices to remove have been processed
check(toRemoveIdx == indicesToRemove.size) {
"The indices weren't sorted or were >= size($size): $indicesToRemove"
}
return ArrayMap(newData as Array)
}
/**
* Returns a new ArrayMap which has added the given key. Throws an exception if the key already
* exists.
*/
fun plus(key: K, value: V): ArrayMap {
val next = plusOrNoOp(key, value)
if (next === this) {
throw IllegalArgumentException("Key already exists: $key")
}
return next
}
/**
* Returns a new ArrayMap which has added the given key, or the current map if the key was already
* in the map.
*/
fun plusOrNoOp(key: K, value: V): ArrayMap {
val idxExisting = dataAsKeys.binarySearch(key)
return if (idxExisting >= 0) this
else {
val idxInsert = -(idxExisting + 1)
insert(idxInsert, key, value)
}
}
/**
* Returns an ArrayMap which has added or overwritten the given key/value. If the map already
* contained that mapping (equal keys and values) then it returns the identically same map.
*/
fun plusOrNoOpOrReplace(key: K, newValue: V): ArrayMap {
val idxExisting = dataAsKeys.binarySearch(key)
if (idxExisting >= 0) {
val existingValue = data[idxExisting * 2 + 1] as V
return if (newValue == existingValue) this
else {
val copy = data.copyOf()
copy[idxExisting * 2 + 1] = newValue
return ArrayMap(copy)
}
} else {
val idxInsert = -(idxExisting + 1)
return insert(idxInsert, key, newValue)
}
}
private fun insert(idxInsert: Int, key: K, value: V): ArrayMap {
return when (data.size) {
0 -> ArrayMap(arrayOf(key, value))
1 -> {
if (idxInsert == 0) ArrayMap(arrayOf(key, value, data[0], data[1]))
else ArrayMap(arrayOf(data[0], data[1], key, value))
}
else ->
// TODO: use idxInsert and arrayCopy to do this faster, see ArraySet#plusOrThis
of(
MutableList(size + 1) {
if (it < size) Pair(data[it * 2] as K, data[it * 2 + 1] as V) else Pair(key, value)
})
}
}
/** list-backed set of guaranteed-sorted keys at even indices. */
private val dataAsKeys =
object : ListBackedSet() {
override val size: Int
get() = data.size / 2
override fun get(index: Int): K {
return data[index * 2] as K
}
}
override val keys: ListBackedSet
get() = dataAsKeys
override fun get(key: K): V? {
val idx = dataAsKeys.binarySearch(key)
return if (idx < 0) null else data[idx * 2 + 1] as V
}
override fun containsKey(key: K): Boolean = dataAsKeys.binarySearch(key) >= 0
/** list-backed collection of values at odd indices. */
override val values: List
get() =
object : AbstractList() {
override val size: Int
get() = data.size / 2
override fun get(index: Int): V = data[index * 2 + 1] as V
}
override fun containsValue(value: V): Boolean = values.contains(value)
/** list-backed set of entries. */
override val entries: ListBackedSet>
get() =
object : ListBackedSet>() {
override val size: Int
get() = data.size / 2
override fun get(index: Int): Map.Entry {
val key = data[index * 2] as K
val value = data[index * 2 + 1] as V
return entry(key, value)
}
}
override val size: Int
get() = data.size / 2
override fun isEmpty(): Boolean = data.isEmpty()
override fun equals(other: Any?): Boolean {
if (other === this) return true
if (other !is Map<*, *>) return false
if (size != other.size) return false
return if (other is ArrayMap<*, *>) {
data.contentEquals(other.data)
} else {
other.entries.all { (key, value) -> this[key] == value }
}
}
override fun hashCode(): Int = entries.hashCode()
override fun toString() = this.toMutableMap().toString()
companion object {
private val EMPTY = ArrayMap(arrayOf())
fun , V : Any> empty() = EMPTY as ArrayMap
fun , V : Any> of(pairs: MutableList>): ArrayMap {
if (pairs.size > 1) {
if (pairs[0].first is String) {
(pairs as (MutableList>)).sortWith(PAIR_STRING_SLASHFIRST)
} else {
pairs.sortBy { it.first }
}
}
val array = arrayOfNulls(pairs.size * 2)
for (i in 0 until pairs.size) {
array[i * 2] = pairs[i].first
array[i * 2 + 1] = pairs[i].second
}
return ArrayMap(array as Array)
}
}
}
/**
* An immutable, sorted, array-backed set - if keys are [String], they have a special sort order
* where `/` is the lowest key.
*/
class ArraySet>(private val data: Array) : ListBackedSet() {
override val size: Int
get() = data.size
override fun get(index: Int): K = data[index] as K
override fun contains(element: K): Boolean = binarySearch(element) >= 0
fun plusOrThis(key: K): ArraySet {
val idxExisting = binarySearch(key)
if (idxExisting >= 0) {
return this
}
val idxInsert = -(idxExisting + 1)
return when (data.size) {
0 -> ArraySet(arrayOf(key))
1 -> {
if (idxInsert == 0)
ArraySet(
arrayOf(
key,
data[0],
))
else ArraySet(arrayOf(data[0], key))
}
else -> {
// TODO: use idxInsert and arrayCopy to do this faster, see ArrayMap#insert
val array = Array(size + 1) { if (it < size) data[it] else key }
if (key is String) {
array.sortWith(STRING_SLASHFIRST as Comparator)
} else {
(array as Array).sort()
}
ArraySet(array)
}
}
}
companion object {
private val EMPTY = ArraySet(arrayOf())
fun > empty() = EMPTY as ArraySet
}
}