diff --git a/benchmarks/build.gradle.kts b/benchmarks/build.gradle.kts index 548e9c0..807566f 100644 --- a/benchmarks/build.gradle.kts +++ b/benchmarks/build.gradle.kts @@ -37,12 +37,12 @@ benchmark { register("compare") { param("size", "10", "1000", "10000") - param("implementation", "hash_map", "hamt", "treap") + param("implementation", "java", "treap") } register("named") { param("size", "10", "1000", "10000") - param("implementation", "hash_map", "hamt", "treap") + param("implementation", "java", "treap") include("${project.findProperty("benchmark")}") } @@ -62,7 +62,15 @@ benchmark { include("immutableMap.ParallelUpdateValues") } + register("list") { + param("size", "1", "10", "1000", "10000") + param("implementation", "treap", "java") + + include("benchmarks.immutableList") + } + configureEach { + reportFormat = "csv" warmups = 5 iterations = 10 iterationTime = 100 diff --git a/benchmarks/src/main/kotlin/benchmarks/FakePersistentList.kt b/benchmarks/src/main/kotlin/benchmarks/FakePersistentList.kt new file mode 100644 index 0000000..55637df --- /dev/null +++ b/benchmarks/src/main/kotlin/benchmarks/FakePersistentList.kt @@ -0,0 +1,33 @@ +package benchmarks + +import kotlinx.collections.immutable.* + +class FakePersistentList(val value: List) : PersistentList, List by value { + class Builder(val value: MutableList) : PersistentList.Builder, MutableList by value { + override fun equals(other: Any?) = value == other + override fun hashCode() = value.hashCode() + override fun build() = FakePersistentList(value) + } + + override fun equals(other: Any?) = value == other + override fun hashCode() = value.hashCode() + + override fun builder() = Builder(value.toMutableList()) + override fun clear() = FakePersistentList(emptyList()) + + override fun add(element: T): PersistentList = FakePersistentList(value + element) + override fun add(index: Int, element: T): PersistentList = FakePersistentList(value.toMutableList().apply { add(index, element) }) + override fun addAll(elements: Collection): PersistentList = FakePersistentList(value + elements) + override fun addAll(index: Int, c: Collection): PersistentList = FakePersistentList(value.toMutableList().apply { addAll(index, c) }) + override fun remove(element: T): PersistentList = FakePersistentList(value - element) + override fun removeAll(predicate: (T) -> Boolean): PersistentList = FakePersistentList(value.filterNot(predicate)) + override fun removeAll(elements: Collection): PersistentList = FakePersistentList(value - elements) + override fun removeAt(index: Int): PersistentList = FakePersistentList(value.toMutableList().apply { removeAt(index) }) + override fun retainAll(elements: Collection): PersistentList = FakePersistentList(value.filter { it !in elements}) + override fun set(index: Int, element: T): PersistentList = FakePersistentList(value.toMutableList().apply { set(index, element) }) + + override fun subList(fromIndex: Int, toIndex: Int): ImmutableList = + super.subList(fromIndex, toIndex) +} + +fun fakePersistentListOf(): PersistentList = FakePersistentList(emptyList()) diff --git a/benchmarks/src/main/kotlin/benchmarks/hashCodeTypes.kt b/benchmarks/src/main/kotlin/benchmarks/hashCodeTypes.kt index bde61b8..97f0e9b 100644 --- a/benchmarks/src/main/kotlin/benchmarks/hashCodeTypes.kt +++ b/benchmarks/src/main/kotlin/benchmarks/hashCodeTypes.kt @@ -35,7 +35,7 @@ private fun generateIntWrappers(hashCodeType: String, size: Int): List() + + @Setup + fun prepare() { + initial = persistentListAdd(implementation, size) + } + + @Benchmark + fun addLast(): ImmutableList { + return initial.add("another element") + } + + /** + * Adds [size] - 1 elements to an empty persistent list + * and then inserts one element at the beginning. + * + * Measures mean time and memory spent per `add` operation. + * + * Expected time: nearly constant. + * Expected memory: nearly constant. + */ + @Benchmark + fun addFirst(): ImmutableList { + return initial.add(0, "another element") + } + + /** + * Adds [size] - 1 elements to an empty persistent list + * and then inserts one element at the middle. + * + * Measures mean time and memory spent per `add` operation. + * + * Expected time: nearly constant. + * Expected memory: nearly constant. + */ + @Benchmark + fun addMiddle(): ImmutableList { + return initial.add(initial.size / 2, "another element") + } +} diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableList/AddAll.kt b/benchmarks/src/main/kotlin/benchmarks/immutableList/AddAll.kt new file mode 100644 index 0000000..9528c9f --- /dev/null +++ b/benchmarks/src/main/kotlin/benchmarks/immutableList/AddAll.kt @@ -0,0 +1,107 @@ +/* + * Modified from the kotlinx.collections.immutable sources, which contained the following notice: + * Copyright 2016-2019 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package benchmarks.immutableList + +import benchmarks.* +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.benchmark.* + +@State(Scope.Benchmark) +open class AddAll { + @Param(KOTLIN_IMPL, TREAP_IMPL, JAVA_IMPL) + var implementation = "" + + @Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000, BM_10000000) + var size: Int = 0 + + private var initialHalf = persistentListOf() + private var initialTwoThirds = persistentListOf() + + private var listToAdd = emptyList() + private var halfList = emptyList() + private var oneThirdList = emptyList() + + @Setup + fun prepare() { + listToAdd = persistentListAdd(implementation, size) + halfList = persistentListAdd(implementation, size / 2) + initialHalf = persistentListAdd(implementation, size - halfList.size) + oneThirdList = persistentListAdd(implementation, size / 3) + initialTwoThirds = persistentListAdd(implementation, size - oneThirdList.size) + } + + // Results of the following benchmarks do not indicate memory or time spent per operation, + // however regressions there do indicate changes. + // + // the benchmarks measure mean time and memory spent per added element. + // + // Expected time: nearly constant. + // Expected memory: nearly constant. + + /** + * Adds [size] elements to an empty persistent list using `addAll` operation. + */ + @Benchmark + fun addAllLast(): ImmutableList { + return emptyPersistentList(implementation).addAll(listToAdd) + } + + /** + * Adds `size / 2` elements to an empty persistent list + * and then adds `size - size / 2` elements using `addAll` operation. + */ + @Benchmark + fun addAllLast_Half(): ImmutableList { + return initialHalf.addAll(halfList) + } + + /** + * Adds `size - size / 3` elements to an empty persistent list + * and then adds `size / 3` elements using `addAll` operation. + */ + @Benchmark + fun addAllLast_OneThird(): ImmutableList { + return initialTwoThirds.addAll(oneThirdList) + } + + /** + * Adds `size / 2` elements to an empty persistent list + * and then inserts `size - size / 2` elements at the beginning using `addAll` operation. + */ + @Benchmark + fun addAllFirst_Half(): ImmutableList { + return initialHalf.addAll(0, halfList) + } + + /** + * Adds `size - size / 3` elements to an empty persistent list + * and then inserts `size / 3` elements at the beginning using `addAll` operation. + */ + @Benchmark + fun addAllFirst_OneThird(): ImmutableList { + return initialTwoThirds.addAll(0, oneThirdList) + } + + /** + * Adds `size / 2` elements to an empty persistent list + * and then inserts `size - size / 2` elements at the middle using `addAll` operation. + */ + @Benchmark + fun addAllMiddle_Half(): ImmutableList { + return initialHalf.addAll(initialHalf.size / 2, halfList) + } + + /** + * Adds `size - size / 3` elements to an empty persistent list builder + * and then inserts `size / 3` elements at the middle using `addAll` operation. + */ + @Benchmark + fun addAllMiddle_OneThird(): ImmutableList { + return initialTwoThirds.addAll(initialTwoThirds.size / 2, oneThirdList) + } +} diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableList/Construct.kt b/benchmarks/src/main/kotlin/benchmarks/immutableList/Construct.kt new file mode 100644 index 0000000..0d30abb --- /dev/null +++ b/benchmarks/src/main/kotlin/benchmarks/immutableList/Construct.kt @@ -0,0 +1,37 @@ +package benchmarks.immutableList + +import benchmarks.* +import kotlinx.collections.immutable.* +import kotlinx.benchmark.* + +@State(Scope.Benchmark) +open class Construct { + @Param(KOTLIN_IMPL, TREAP_IMPL, JAVA_IMPL) + var implementation = "" + + @Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000, BM_10000000) + var size: Int = 0 + + var toAdd = listOf() + + @Setup + fun prepare() { + toAdd = (1..size).toList() + } + + @Benchmark + fun oneAtATime(): ImmutableList { + var list = emptyPersistentList(implementation) + toAdd.forEach { + list = list.add(it) + } + return list + } + + @Benchmark + fun addAll(): ImmutableList { + var list = emptyPersistentList(implementation) + list = list.addAll(toAdd) + return list + } +} diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableList/Get.kt b/benchmarks/src/main/kotlin/benchmarks/immutableList/Get.kt new file mode 100644 index 0000000..91c5862 --- /dev/null +++ b/benchmarks/src/main/kotlin/benchmarks/immutableList/Get.kt @@ -0,0 +1,35 @@ +/* + * Modified from the kotlinx.collections.immutable sources, which contained the following notice: + * Copyright 2016-2019 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package benchmarks.immutableList + +import benchmarks.* +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.benchmark.* + +@State(Scope.Benchmark) +open class Get { + @Param(KOTLIN_IMPL, TREAP_IMPL, JAVA_IMPL) + var implementation = "" + + @Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000, BM_10000000) + var size: Int = 0 + + private var persistentList: PersistentList = persistentListOf() + + @Setup + fun prepare() { + persistentList = persistentListAdd(implementation, size) + } + + @Benchmark + fun getByIndex(bh: Blackhole) { + for (i in 0 until size) { + bh.consume(persistentList[i]) + } + } +} diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableList/Iterate.kt b/benchmarks/src/main/kotlin/benchmarks/immutableList/Iterate.kt new file mode 100644 index 0000000..5d601c8 --- /dev/null +++ b/benchmarks/src/main/kotlin/benchmarks/immutableList/Iterate.kt @@ -0,0 +1,57 @@ +/* + * Modified from the kotlinx.collections.immutable sources, which contained the following notice: + * Copyright 2016-2019 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package benchmarks.immutableList + +import benchmarks.* +import com.certora.collect.TreapList +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.benchmark.* + +@State(Scope.Benchmark) +open class Iterate { + @Param(KOTLIN_IMPL, TREAP_IMPL, JAVA_IMPL) + var implementation = "" + + @Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000, BM_10000000) + var size: Int = 0 + + private var persistentList: PersistentList = persistentListOf() + + @Setup + fun prepare() { + persistentList = persistentListAdd(implementation, size) + } + + @Benchmark + fun firstToLast(bh: Blackhole) { + for (e in persistentList) { + bh.consume(e) + } + } + + @Benchmark + fun lastToFirst(bh: Blackhole) { + val iterator = persistentList.listIterator(size) + + while (iterator.hasPrevious()) { + bh.consume(iterator.previous()) + } + } + + @Benchmark + fun forEachElement(bh: Blackhole) { + when (val list = persistentList) { + is TreapList<*> -> list.forEachElement { e -> + bh.consume(e) + } + else -> list.forEach { e -> + bh.consume(e) + } + } + } +} diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableList/Remove.kt b/benchmarks/src/main/kotlin/benchmarks/immutableList/Remove.kt new file mode 100644 index 0000000..9f950b7 --- /dev/null +++ b/benchmarks/src/main/kotlin/benchmarks/immutableList/Remove.kt @@ -0,0 +1,60 @@ +/* + * Modified from the kotlinx.collections.immutable sources, which contained the following notice: + * Copyright 2016-2019 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package benchmarks.immutableList + +import benchmarks.* +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.benchmark.* + +@State(Scope.Benchmark) +open class Remove { + @Param(KOTLIN_IMPL, TREAP_IMPL, JAVA_IMPL) + var implementation = "" + + @Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000, BM_10000000) + var size: Int = 0 + + private var list: PersistentList = persistentListOf() + + @Setup + fun prepare() { + list = persistentListAdd(implementation, size) + } + + @Benchmark + fun removeLast(): ImmutableList { + return list.removeAt(list.lastIndex) + } + + /** + * Removes one element from the beginning. + * + * Measures (time and memory spent on `removeAt` operation) / size. + * + * Expected time: nearly constant. + * Expected memory: nearly constant. + */ + @Benchmark + fun removeFirst(): ImmutableList { + return list.removeAt(0) + } + + /** + * Removes one element from the middle. + * + * Measures (time and memory spent on `removeAt` operation) / size. + * + * Expected time: nearly constant. + * Expected memory: nearly constant. + */ + @Benchmark + fun removeMiddle(): ImmutableList { + return list.removeAt(list.size / 2) + } +} diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableList/RemoveAll.kt b/benchmarks/src/main/kotlin/benchmarks/immutableList/RemoveAll.kt new file mode 100644 index 0000000..ecb4501 --- /dev/null +++ b/benchmarks/src/main/kotlin/benchmarks/immutableList/RemoveAll.kt @@ -0,0 +1,96 @@ +/* + * Modified from the kotlinx.collections.immutable sources, which contained the following notice: + * Copyright 2016-2019 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package benchmarks.immutableList + +import benchmarks.* +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.benchmark.* +import kotlin.random.Random + +@State(Scope.Benchmark) +open class RemoveAll { + @Param(KOTLIN_IMPL, TREAP_IMPL, JAVA_IMPL) + var implementation = "" + + @Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000, BM_10000000) + var size: Int = 0 + + private var persistentList: PersistentList = persistentListOf() + + @Setup + fun prepare() { + persistentList = emptyPersistentList(implementation).addAll(List(size) { it }) + } + + // Results of the following benchmarks do not indicate memory or time spent per operation, + // however regressions there do indicate changes. + // + // The benchmarks measure (time and memory spent on `removeAll` operation) / size + // + // Expected time: nearly constant + // Expected memory: nearly constant + + /** + * Removes all elements using `removeAll(elements)` operation. + */ + @Benchmark + fun removeAll_All(): PersistentList { + val list = persistentList + val elementsToRemove = List(size) { it } + return list.removeAll(elementsToRemove) + } + + /** + * Removes half of the elements using `removeAll(elements)` operation. + */ + @Benchmark + fun removeAll_RandomHalf(): PersistentList { + val list = persistentList + val elementsToRemove = randomIndexes(size / 2) + return list.removeAll(elementsToRemove) + } + + /** + * Removes 10 random elements using `removeAll(elements)` operation. + */ + @Benchmark + fun removeAll_RandomTen(): PersistentList { + val list = persistentList + val elementsToRemove = randomIndexes(10) + return list.removeAll(elementsToRemove) + } + + /** + * Removes last [tailSize] elements using `removeAll(elements)` operation. + */ + @Benchmark + fun removeAll_Tail(): PersistentList { + val list = persistentList + val elementsToRemove = List(tailSize()) { size - 1 - it } + return list.removeAll(elementsToRemove) + } + + /** + * Removes 10 non-existing elements using `removeAll(elements)` operation. + */ + @Benchmark + fun removeAll_NonExisting(): PersistentList { + val list = persistentList + val elementsToRemove = randomIndexes(10).map { size + it } + return list.removeAll(elementsToRemove) + } + + private fun randomIndexes(count: Int): List { + return List(count) { Random.nextInt(size) } + } + + private fun tailSize(): Int { + val bufferSize = 32 + return (size and (bufferSize - 1)).let { if (it == 0) bufferSize else it } + } +} diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableList/Set.kt b/benchmarks/src/main/kotlin/benchmarks/immutableList/Set.kt new file mode 100644 index 0000000..0bac944 --- /dev/null +++ b/benchmarks/src/main/kotlin/benchmarks/immutableList/Set.kt @@ -0,0 +1,47 @@ +/* + * Modified from the kotlinx.collections.immutable sources, which contained the following notice: + * Copyright 2016-2019 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package benchmarks.immutableList + +import benchmarks.* +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.benchmark.* + +@State(Scope.Benchmark) +open class Set { + @Param(KOTLIN_IMPL, TREAP_IMPL, JAVA_IMPL) + var implementation = "" + + @Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000, BM_10000000) + var size: Int = 0 + + private var persistentList: PersistentList = persistentListOf() + private var randomIndices = listOf() + + @Setup + fun prepare() { + persistentList = persistentListAdd(implementation, size) + randomIndices = List(size) { it }.shuffled() + } + + @Benchmark + fun setByIndex(): ImmutableList { + repeat(times = size) { index -> + persistentList = persistentList.set(index, "another element") + } + return persistentList + } + + @Benchmark + fun setByRandomIndex(): ImmutableList { + repeat(times = size) { index -> + persistentList = persistentList.set(randomIndices[index], "another element") + } + return persistentList + } +} diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableList/utils.kt b/benchmarks/src/main/kotlin/benchmarks/immutableList/utils.kt new file mode 100644 index 0000000..3ad6c00 --- /dev/null +++ b/benchmarks/src/main/kotlin/benchmarks/immutableList/utils.kt @@ -0,0 +1,33 @@ +/* + * Modified from the kotlinx.collections.immutable sources, which contained the following notice: + * Copyright 2016-2019 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package benchmarks.immutableList + +import benchmarks.* +import com.certora.collect.* +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.persistentListOf + +fun emptyPersistentList(implementation: String): PersistentList = when (implementation) { + KOTLIN_IMPL -> persistentListOf() + TREAP_IMPL -> treapListOf() + JAVA_IMPL -> fakePersistentListOf() + else -> throw AssertionError("Unknown PersistentList implementation: $implementation") +} + +fun persistentListAdd(implementation: String, size: Int): PersistentList { + var list = emptyPersistentList(implementation) + repeat(times = size) { + list = list.add("some element") + } + return list +} + +@Suppress("UNCHECKED_CAST") +fun PersistentList.removeLast(): PersistentList = when(this) { + is TreapList<*> -> this.removeLast() as PersistentList + else -> this.removeAt(this.size - 1) +} diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableMap/Equals.kt b/benchmarks/src/main/kotlin/benchmarks/immutableMap/Equals.kt index a04a4ba..37b3dda 100644 --- a/benchmarks/src/main/kotlin/benchmarks/immutableMap/Equals.kt +++ b/benchmarks/src/main/kotlin/benchmarks/immutableMap/Equals.kt @@ -14,7 +14,7 @@ open class Equals { @Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000) var size: Int = 0 - @Param(HAMT_IMPL, ORDERED_HAMT_IMPL, TREAP_IMPL) + @Param(KOTLIN_IMPL, KOTLIN_ORDERED_IMPL, TREAP_IMPL) var implementation = "" @Param(ASCENDING_HASH_CODE, RANDOM_HASH_CODE, COLLISION_HASH_CODE, NON_EXISTING_HASH_CODE) diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableMap/Get.kt b/benchmarks/src/main/kotlin/benchmarks/immutableMap/Get.kt index d58c8b7..9184162 100644 --- a/benchmarks/src/main/kotlin/benchmarks/immutableMap/Get.kt +++ b/benchmarks/src/main/kotlin/benchmarks/immutableMap/Get.kt @@ -15,7 +15,7 @@ open class Get { @Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000) var size: Int = 0 - @Param(HAMT_IMPL, ORDERED_HAMT_IMPL, TREAP_IMPL) + @Param(KOTLIN_IMPL, KOTLIN_ORDERED_IMPL, TREAP_IMPL) var implementation = "" @Param(ASCENDING_HASH_CODE, RANDOM_HASH_CODE, COLLISION_HASH_CODE, NON_EXISTING_HASH_CODE) diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableMap/Iterate.kt b/benchmarks/src/main/kotlin/benchmarks/immutableMap/Iterate.kt index 03b613c..dd5ecef 100644 --- a/benchmarks/src/main/kotlin/benchmarks/immutableMap/Iterate.kt +++ b/benchmarks/src/main/kotlin/benchmarks/immutableMap/Iterate.kt @@ -15,7 +15,7 @@ open class Iterate { @Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000) var size: Int = 0 - @Param(HAMT_IMPL, ORDERED_HAMT_IMPL, TREAP_IMPL) + @Param(KOTLIN_IMPL, KOTLIN_ORDERED_IMPL, TREAP_IMPL) var implementation = "" @Param(ASCENDING_HASH_CODE, RANDOM_HASH_CODE, COLLISION_HASH_CODE) diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableMap/Put.kt b/benchmarks/src/main/kotlin/benchmarks/immutableMap/Put.kt index 480a05a..227aceb 100644 --- a/benchmarks/src/main/kotlin/benchmarks/immutableMap/Put.kt +++ b/benchmarks/src/main/kotlin/benchmarks/immutableMap/Put.kt @@ -15,7 +15,7 @@ open class Put { @Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000) var size: Int = 0 - @Param(HAMT_IMPL, ORDERED_HAMT_IMPL, TREAP_IMPL) + @Param(KOTLIN_IMPL, KOTLIN_ORDERED_IMPL, TREAP_IMPL) var implementation = "" @Param(ASCENDING_HASH_CODE, RANDOM_HASH_CODE, COLLISION_HASH_CODE) diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableMap/PutAll.kt b/benchmarks/src/main/kotlin/benchmarks/immutableMap/PutAll.kt index 4bb3403..64ca85b 100644 --- a/benchmarks/src/main/kotlin/benchmarks/immutableMap/PutAll.kt +++ b/benchmarks/src/main/kotlin/benchmarks/immutableMap/PutAll.kt @@ -15,7 +15,7 @@ open class PutAll { @Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000) var size: Int = 0 - @Param(HAMT_IMPL, ORDERED_HAMT_IMPL, TREAP_IMPL) + @Param(KOTLIN_IMPL, KOTLIN_ORDERED_IMPL, TREAP_IMPL) var implementation = "" @Param(ASCENDING_HASH_CODE, RANDOM_HASH_CODE, COLLISION_HASH_CODE) diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableMap/Remove.kt b/benchmarks/src/main/kotlin/benchmarks/immutableMap/Remove.kt index 5deca15..09c623d 100644 --- a/benchmarks/src/main/kotlin/benchmarks/immutableMap/Remove.kt +++ b/benchmarks/src/main/kotlin/benchmarks/immutableMap/Remove.kt @@ -16,7 +16,7 @@ open class Remove { @Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000) var size: Int = 0 - @Param(HAMT_IMPL, ORDERED_HAMT_IMPL, TREAP_IMPL) + @Param(KOTLIN_IMPL, KOTLIN_ORDERED_IMPL, TREAP_IMPL) var implementation = "" @Param(ASCENDING_HASH_CODE, RANDOM_HASH_CODE, COLLISION_HASH_CODE, NON_EXISTING_HASH_CODE) diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableMap/builder/Equals.kt b/benchmarks/src/main/kotlin/benchmarks/immutableMap/builder/Equals.kt deleted file mode 100644 index db754e6..0000000 --- a/benchmarks/src/main/kotlin/benchmarks/immutableMap/builder/Equals.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2016-2021 JetBrains s.r.o. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package benchmarks.immutableMap.builder - -import benchmarks.* -import kotlinx.benchmark.* -import kotlinx.collections.immutable.persistentMapOf - -@State(Scope.Benchmark) -open class Equals { - @Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000) - var size: Int = 0 - - @Param(HAMT_IMPL, ORDERED_HAMT_IMPL, TREAP_IMPL) - var implementation = "" - - @Param(ASCENDING_HASH_CODE, RANDOM_HASH_CODE, COLLISION_HASH_CODE, NON_EXISTING_HASH_CODE) - var hashCodeType = "" - - private var persistentMap = persistentMapOf().builder() - private var sameMap = persistentMapOf().builder() - private var slightlyDifferentMap = persistentMapOf().builder() - private var veryDifferentMap = persistentMapOf().builder() - - @Setup - fun prepare() { - val keys = generateKeys(hashCodeType, size * 2) - persistentMap = persistentMapBuilderPut(implementation, keys.take(size), 0.0) - sameMap = persistentMapBuilderPut(implementation, keys.take(size), 0.0) - slightlyDifferentMap = sameMap.build().builder() - slightlyDifferentMap.put(keys[size], "different value") - slightlyDifferentMap.remove(keys[0]) - veryDifferentMap = persistentMapBuilderPut(implementation, keys.drop(size), 0.0) - - check(sameMap == persistentMap) - check(slightlyDifferentMap != persistentMap) - check(veryDifferentMap != persistentMap) - } - - @Benchmark - fun equalsTrue() = persistentMap == sameMap - @Benchmark - fun nearlyEquals() = persistentMap == slightlyDifferentMap - @Benchmark - fun notEquals() = persistentMap == veryDifferentMap - -} diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableMap/builder/Get.kt b/benchmarks/src/main/kotlin/benchmarks/immutableMap/builder/Get.kt deleted file mode 100644 index a678c87..0000000 --- a/benchmarks/src/main/kotlin/benchmarks/immutableMap/builder/Get.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Modified from the kotlinx.collections.immutable sources, which contained the following notice: - * Copyright 2016-2019 JetBrains s.r.o. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package benchmarks.immutableMap.builder - -import benchmarks.* -import kotlinx.collections.immutable.persistentMapOf -import kotlinx.benchmark.* - -@State(Scope.Benchmark) -open class Get { - @Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000) - var size: Int = 0 - - @Param(HAMT_IMPL, ORDERED_HAMT_IMPL, TREAP_IMPL) - var implementation = "" - - @Param(ASCENDING_HASH_CODE, RANDOM_HASH_CODE, COLLISION_HASH_CODE, NON_EXISTING_HASH_CODE) - var hashCodeType = "" - - @Param(IP_100, IP_99_09, IP_95, IP_70, IP_50, IP_30, IP_0) - var immutablePercentage: Double = 0.0 - - private var keys = listOf() - private var builder = persistentMapOf().builder() - - @Setup - fun prepare() { - keys = generateKeys(hashCodeType, size) - builder = persistentMapBuilderPut(implementation, keys, immutablePercentage) - - if (hashCodeType == NON_EXISTING_HASH_CODE) - keys = generateKeys(hashCodeType, size) - } - - @Benchmark - fun get(bh: Blackhole) { - repeat(times = size) { index -> - bh.consume(builder[keys[index]]) - } - } -} diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableMap/builder/Iterate.kt b/benchmarks/src/main/kotlin/benchmarks/immutableMap/builder/Iterate.kt deleted file mode 100644 index 889e907..0000000 --- a/benchmarks/src/main/kotlin/benchmarks/immutableMap/builder/Iterate.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Modified from the kotlinx.collections.immutable sources, which contained the following notice: - * Copyright 2016-2019 JetBrains s.r.o. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package benchmarks.immutableMap.builder - -import benchmarks.* -import kotlinx.collections.immutable.persistentMapOf -import kotlinx.benchmark.* - -@State(Scope.Benchmark) -open class Iterate { - @Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000) - var size: Int = 0 - - @Param(HAMT_IMPL, ORDERED_HAMT_IMPL, TREAP_IMPL) - var implementation = "" - - @Param(ASCENDING_HASH_CODE, RANDOM_HASH_CODE, COLLISION_HASH_CODE) - var hashCodeType = "" - - @Param(IP_100, IP_99_09, IP_95, IP_70, IP_50, IP_30, IP_0) - var immutablePercentage: Double = 0.0 - - private var builder = persistentMapOf().builder() - - @Setup - fun prepare() { - val keys = generateKeys(hashCodeType, size) - builder = persistentMapBuilderPut(implementation, keys, immutablePercentage) - } - - @Benchmark - fun iterateKeys(bh: Blackhole) { - for (k in builder.keys) { - bh.consume(k) - } - } - - @Benchmark - fun iterateValues(bh: Blackhole) { - for (v in builder.values) { - bh.consume(v) - } - } - - @Benchmark - fun iterateEntries(bh: Blackhole) { - for (e in builder) { - bh.consume(e.key) - bh.consume(e.value) - } - } -} diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableMap/builder/Put.kt b/benchmarks/src/main/kotlin/benchmarks/immutableMap/builder/Put.kt deleted file mode 100644 index 21c4494..0000000 --- a/benchmarks/src/main/kotlin/benchmarks/immutableMap/builder/Put.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Modified from the kotlinx.collections.immutable sources, which contained the following notice: - * Copyright 2016-2019 JetBrains s.r.o. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package benchmarks.immutableMap.builder - -import benchmarks.* -import kotlinx.collections.immutable.PersistentMap -import kotlinx.benchmark.* - -@State(Scope.Benchmark) -open class Put { - @Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000) - var size: Int = 0 - - @Param(HAMT_IMPL, ORDERED_HAMT_IMPL, TREAP_IMPL) - var implementation = "" - - @Param(ASCENDING_HASH_CODE, RANDOM_HASH_CODE, COLLISION_HASH_CODE) - var hashCodeType = "" - - @Param(IP_100, IP_99_09, IP_95, IP_70, IP_50, IP_30, IP_0) - var immutablePercentage: Double = 0.0 - - private var keys = listOf() - - @Setup - fun prepare() { - keys = generateKeys(hashCodeType, size) - } - - @Benchmark - fun put(): PersistentMap.Builder { - return persistentMapBuilderPut(implementation, keys, immutablePercentage) - } - - @Benchmark - fun putAndGet(bh: Blackhole) { - val builder = persistentMapBuilderPut(implementation, keys, immutablePercentage) - repeat(times = size) { index -> - bh.consume(builder[keys[index]]) - } - } - - @Benchmark - fun putAndIterateKeys(bh: Blackhole) { - val builder = persistentMapBuilderPut(implementation, keys, immutablePercentage) - for (key in builder.keys) { - bh.consume(key) - } - } -} diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableMap/builder/Remove.kt b/benchmarks/src/main/kotlin/benchmarks/immutableMap/builder/Remove.kt deleted file mode 100644 index dce2997..0000000 --- a/benchmarks/src/main/kotlin/benchmarks/immutableMap/builder/Remove.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Modified from the kotlinx.collections.immutable sources, which contained the following notice: - * Copyright 2016-2019 JetBrains s.r.o. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package benchmarks.immutableMap.builder - -import benchmarks.* -import kotlinx.collections.immutable.PersistentMap -import kotlinx.benchmark.* - -@State(Scope.Benchmark) -open class Remove { - @Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000) - var size: Int = 0 - - @Param(HAMT_IMPL, ORDERED_HAMT_IMPL, TREAP_IMPL) - var implementation = "" - - @Param(ASCENDING_HASH_CODE, RANDOM_HASH_CODE, COLLISION_HASH_CODE, NON_EXISTING_HASH_CODE) - var hashCodeType = "" - - @Param(IP_100, IP_99_09, IP_95, IP_70, IP_50, IP_30, IP_0) - var immutablePercentage: Double = 0.0 - - private var keys = listOf() - private var keysToRemove = listOf() - - @Setup - fun prepare() { - keys = generateKeys(hashCodeType, size) - - keysToRemove = if (hashCodeType == NON_EXISTING_HASH_CODE) { - generateKeys(hashCodeType, size) - } else { - keys - } - } - - // Q: Why not to benchmark pure remove method? - // A: Each invocation of remove benchmark method would clear the builder and creating new one would be needed each time. - // Setting `@Setup(Level.Invocation)` may cause bad benchmark accuracy amid frequent `prepare` calls, especially for small `size`. - @Benchmark - fun putAndRemove(): PersistentMap.Builder { - val builder = persistentMapBuilderPut(implementation, keys, immutablePercentage) - repeat(times = size) { index -> - builder.remove(keysToRemove[index]) - } - return builder - } -} diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableMap/builder/utils.kt b/benchmarks/src/main/kotlin/benchmarks/immutableMap/builder/utils.kt deleted file mode 100644 index 55adfcf..0000000 --- a/benchmarks/src/main/kotlin/benchmarks/immutableMap/builder/utils.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Modified from the kotlinx.collections.immutable sources, which contained the following notice: - * Copyright 2016-2019 JetBrains s.r.o. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package benchmarks.immutableMap.builder - -import benchmarks.* -import benchmarks.immutableMap.emptyPersistentMap -import kotlinx.collections.immutable.PersistentMap - - -fun persistentMapBuilderPut( - implementation: String, - keys: List, - immutablePercentage: Double -): PersistentMap.Builder { - val immutableSize = immutableSize(keys.size, immutablePercentage) - - var map = emptyPersistentMap(implementation) - for (index in 0 until immutableSize) { - map = map.put(keys[index], "some value") - } - - val builder = map.builder() - for (index in immutableSize until keys.size) { - builder[keys[index]] = "some value" - } - - return builder -} diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableMap/utils.kt b/benchmarks/src/main/kotlin/benchmarks/immutableMap/utils.kt index dbbb41c..5fb9d37 100644 --- a/benchmarks/src/main/kotlin/benchmarks/immutableMap/utils.kt +++ b/benchmarks/src/main/kotlin/benchmarks/immutableMap/utils.kt @@ -14,10 +14,10 @@ import kotlin.math.log fun emptyPersistentMap(implementation: String): PersistentMap = when (implementation) { - ORDERED_HAMT_IMPL -> persistentMapOf() - HAMT_IMPL -> persistentHashMapOf() + KOTLIN_ORDERED_IMPL -> persistentMapOf() + KOTLIN_IMPL -> persistentHashMapOf() TREAP_IMPL -> treapMapOf() - HASH_MAP_IMPL -> fakePersistentMapOf() + JAVA_IMPL -> fakePersistentMapOf() else -> throw AssertionError("Unknown PersistentMap implementation: $implementation") } diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableSet/Add.kt b/benchmarks/src/main/kotlin/benchmarks/immutableSet/Add.kt index 1ed91fc..1a66f13 100644 --- a/benchmarks/src/main/kotlin/benchmarks/immutableSet/Add.kt +++ b/benchmarks/src/main/kotlin/benchmarks/immutableSet/Add.kt @@ -15,7 +15,7 @@ open class Add { @Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000) var size: Int = 0 - @Param(HAMT_IMPL, ORDERED_HAMT_IMPL, TREAP_IMPL) + @Param(KOTLIN_IMPL, KOTLIN_ORDERED_IMPL, TREAP_IMPL) var implementation = "" @Param(ASCENDING_HASH_CODE, RANDOM_HASH_CODE, COLLISION_HASH_CODE) diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableSet/Contains.kt b/benchmarks/src/main/kotlin/benchmarks/immutableSet/Contains.kt index 630728a..67fe465 100644 --- a/benchmarks/src/main/kotlin/benchmarks/immutableSet/Contains.kt +++ b/benchmarks/src/main/kotlin/benchmarks/immutableSet/Contains.kt @@ -15,7 +15,7 @@ open class Contains { @Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000) var size: Int = 0 - @Param(HAMT_IMPL, ORDERED_HAMT_IMPL, TREAP_IMPL) + @Param(KOTLIN_IMPL, KOTLIN_ORDERED_IMPL, TREAP_IMPL) var implementation = "" @Param(ASCENDING_HASH_CODE, RANDOM_HASH_CODE, COLLISION_HASH_CODE, NON_EXISTING_HASH_CODE) diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableSet/Equals.kt b/benchmarks/src/main/kotlin/benchmarks/immutableSet/Equals.kt index 7b57853..7058b0c 100644 --- a/benchmarks/src/main/kotlin/benchmarks/immutableSet/Equals.kt +++ b/benchmarks/src/main/kotlin/benchmarks/immutableSet/Equals.kt @@ -14,7 +14,7 @@ open class Equals { @Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000) var size: Int = 0 - @Param(HAMT_IMPL, ORDERED_HAMT_IMPL, TREAP_IMPL) + @Param(KOTLIN_IMPL, KOTLIN_ORDERED_IMPL, TREAP_IMPL) var implementation = "" @Param(ASCENDING_HASH_CODE, RANDOM_HASH_CODE, COLLISION_HASH_CODE, NON_EXISTING_HASH_CODE) diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableSet/Iterate.kt b/benchmarks/src/main/kotlin/benchmarks/immutableSet/Iterate.kt index 84a2bc5..a1978f4 100644 --- a/benchmarks/src/main/kotlin/benchmarks/immutableSet/Iterate.kt +++ b/benchmarks/src/main/kotlin/benchmarks/immutableSet/Iterate.kt @@ -15,7 +15,7 @@ open class Iterate { @Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000) var size: Int = 0 - @Param(HAMT_IMPL, ORDERED_HAMT_IMPL, TREAP_IMPL) + @Param(KOTLIN_IMPL, KOTLIN_ORDERED_IMPL, TREAP_IMPL) var implementation = "" @Param(ASCENDING_HASH_CODE, RANDOM_HASH_CODE, COLLISION_HASH_CODE) diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableSet/Remove.kt b/benchmarks/src/main/kotlin/benchmarks/immutableSet/Remove.kt index 6f21dc6..47b435a 100644 --- a/benchmarks/src/main/kotlin/benchmarks/immutableSet/Remove.kt +++ b/benchmarks/src/main/kotlin/benchmarks/immutableSet/Remove.kt @@ -16,7 +16,7 @@ open class Remove { @Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000) var size: Int = 0 - @Param(HAMT_IMPL, ORDERED_HAMT_IMPL, TREAP_IMPL) + @Param(KOTLIN_IMPL, KOTLIN_ORDERED_IMPL, TREAP_IMPL) var implementation = "" @Param(ASCENDING_HASH_CODE, RANDOM_HASH_CODE, COLLISION_HASH_CODE, NON_EXISTING_HASH_CODE) diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableSet/SetOperators.kt b/benchmarks/src/main/kotlin/benchmarks/immutableSet/SetOperators.kt index 42e7d7f..bc741e3 100644 --- a/benchmarks/src/main/kotlin/benchmarks/immutableSet/SetOperators.kt +++ b/benchmarks/src/main/kotlin/benchmarks/immutableSet/SetOperators.kt @@ -9,7 +9,7 @@ open class SetOperators { @Param(ASCENDING_HASH_CODE, RANDOM_HASH_CODE, COLLISION_HASH_CODE, NON_EXISTING_HASH_CODE) var hashCodeType = "" - @Param(HAMT_IMPL, ORDERED_HAMT_IMPL, TREAP_IMPL) + @Param(KOTLIN_IMPL, KOTLIN_ORDERED_IMPL, TREAP_IMPL) var implementation = "" @Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000) diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableSet/builder/Add.kt b/benchmarks/src/main/kotlin/benchmarks/immutableSet/builder/Add.kt deleted file mode 100644 index 285acff..0000000 --- a/benchmarks/src/main/kotlin/benchmarks/immutableSet/builder/Add.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Modified from the kotlinx.collections.immutable sources, which contained the following notice: - * Copyright 2016-2019 JetBrains s.r.o. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package benchmarks.immutableSet.builder - -import benchmarks.* -import kotlinx.collections.immutable.PersistentSet -import kotlinx.benchmark.* - -@State(Scope.Benchmark) -open class Add { - @Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000) - var size: Int = 0 - - @Param(HAMT_IMPL, ORDERED_HAMT_IMPL, TREAP_IMPL) - var implementation = "" - - @Param(ASCENDING_HASH_CODE, RANDOM_HASH_CODE, COLLISION_HASH_CODE) - var hashCodeType = "" - - @Param(IP_100, IP_99_09, IP_95, IP_70, IP_50, IP_30, IP_0) - var immutablePercentage: Double = 0.0 - - private var elements = listOf() - - @Setup - fun prepare() { - elements = generateElements(hashCodeType, size) - } - - @Benchmark - fun add(): PersistentSet.Builder { - return persistentSetBuilderAdd(implementation, elements, immutablePercentage) - } - - @Benchmark - fun addAndContains(bh: Blackhole) { - val builder = persistentSetBuilderAdd(implementation, elements, immutablePercentage) - repeat(times = size) { index -> - bh.consume(builder.contains(elements[index])) - } - } - - @Benchmark - fun addAndIterate(bh: Blackhole) { - val set = persistentSetBuilderAdd(implementation, elements, immutablePercentage) - for (element in set) { - bh.consume(element) - } - } -} diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableSet/builder/Contains.kt b/benchmarks/src/main/kotlin/benchmarks/immutableSet/builder/Contains.kt deleted file mode 100644 index 34bf799..0000000 --- a/benchmarks/src/main/kotlin/benchmarks/immutableSet/builder/Contains.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Modified from the kotlinx.collections.immutable sources, which contained the following notice: - * Copyright 2016-2019 JetBrains s.r.o. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package benchmarks.immutableSet.builder - -import benchmarks.* -import kotlinx.collections.immutable.persistentSetOf -import kotlinx.benchmark.* - -@State(Scope.Benchmark) -open class Contains { - @Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000) - var size: Int = 0 - - @Param(HAMT_IMPL, ORDERED_HAMT_IMPL, TREAP_IMPL) - var implementation = "" - - @Param(ASCENDING_HASH_CODE, RANDOM_HASH_CODE, COLLISION_HASH_CODE, NON_EXISTING_HASH_CODE) - var hashCodeType = "" - - @Param(IP_100, IP_99_09, IP_95, IP_70, IP_50, IP_30, IP_0) - var immutablePercentage: Double = 0.0 - - private var elements = listOf() - private var builder = persistentSetOf().builder() - - @Setup - fun prepare() { - elements = generateElements(hashCodeType, size) - builder = persistentSetBuilderAdd(implementation, elements, immutablePercentage) - - if (hashCodeType == NON_EXISTING_HASH_CODE) - elements = generateElements(hashCodeType, size) - } - - @Benchmark - fun contains(bh: Blackhole) { - repeat(times = size) { index -> - bh.consume(builder.contains(elements[index])) - } - } -} diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableSet/builder/Equals.kt b/benchmarks/src/main/kotlin/benchmarks/immutableSet/builder/Equals.kt deleted file mode 100644 index d79543f..0000000 --- a/benchmarks/src/main/kotlin/benchmarks/immutableSet/builder/Equals.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2016-2021 JetBrains s.r.o. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package benchmarks.immutableSet.builder - -import benchmarks.* -import kotlinx.benchmark.* -import kotlinx.collections.immutable.persistentSetOf - -@State(Scope.Benchmark) -open class Equals { - @Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000) - var size: Int = 0 - - @Param(HAMT_IMPL, ORDERED_HAMT_IMPL, TREAP_IMPL) - var implementation = "" - - @Param(ASCENDING_HASH_CODE, RANDOM_HASH_CODE, COLLISION_HASH_CODE, NON_EXISTING_HASH_CODE) - var hashCodeType = "" - - private var persistentSet = persistentSetOf().builder() - private var sameSet = persistentSetOf().builder() - private var slightlyDifferentSet = persistentSetOf().builder() - private var veryDifferentSet = persistentSetOf().builder() - - @Setup - fun prepare() { - val keys = generateKeys(hashCodeType, size * 2) - persistentSet = persistentSetBuilderAdd(implementation, keys.take(size), 0.0) - sameSet = persistentSetBuilderAdd(implementation, keys.take(size), 0.0) - slightlyDifferentSet = sameSet.build().builder() - slightlyDifferentSet.add(keys[size]) - slightlyDifferentSet.remove(keys[0]) - veryDifferentSet = persistentSetBuilderAdd(implementation, keys.drop(size), 0.0) - - check(sameSet == persistentSet) - check(slightlyDifferentSet != persistentSet) - check(veryDifferentSet != persistentSet) - } - - @Benchmark - fun equalsTrue() = persistentSet == sameSet - @Benchmark - fun nearlyEquals() = persistentSet == slightlyDifferentSet - @Benchmark - fun notEquals() = persistentSet == veryDifferentSet -} diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableSet/builder/Iterate.kt b/benchmarks/src/main/kotlin/benchmarks/immutableSet/builder/Iterate.kt deleted file mode 100644 index 54d15b8..0000000 --- a/benchmarks/src/main/kotlin/benchmarks/immutableSet/builder/Iterate.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Modified from the kotlinx.collections.immutable sources, which contained the following notice: - * Copyright 2016-2019 JetBrains s.r.o. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package benchmarks.immutableSet.builder - -import benchmarks.* -import kotlinx.collections.immutable.persistentSetOf -import kotlinx.benchmark.* - -@State(Scope.Benchmark) -open class Iterate { - @Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000) - var size: Int = 0 - - @Param(HAMT_IMPL, ORDERED_HAMT_IMPL, TREAP_IMPL) - var implementation = "" - - @Param(ASCENDING_HASH_CODE, RANDOM_HASH_CODE, COLLISION_HASH_CODE) - var hashCodeType = "" - - @Param(IP_100, IP_99_09, IP_95, IP_70, IP_50, IP_30, IP_0) - var immutablePercentage: Double = 0.0 - - private var builder = persistentSetOf().builder() - - @Setup - fun prepare() { - val elements = generateElements(hashCodeType, size) - builder = persistentSetBuilderAdd(implementation, elements, immutablePercentage) - } - - @Benchmark - fun iterate(bh: Blackhole) { - for (e in builder) { - bh.consume(e) - } - } -} diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableSet/builder/Remove.kt b/benchmarks/src/main/kotlin/benchmarks/immutableSet/builder/Remove.kt deleted file mode 100644 index fc5d375..0000000 --- a/benchmarks/src/main/kotlin/benchmarks/immutableSet/builder/Remove.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Modified from the kotlinx.collections.immutable sources, which contained the following notice: - * Copyright 2016-2019 JetBrains s.r.o. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package benchmarks.immutableSet.builder - -import benchmarks.* -import kotlinx.collections.immutable.PersistentSet -import kotlinx.benchmark.* - -@State(Scope.Benchmark) -open class Remove { - @Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000) - var size: Int = 0 - - @Param(HAMT_IMPL, ORDERED_HAMT_IMPL, TREAP_IMPL) - var implementation = "" - - @Param(ASCENDING_HASH_CODE, RANDOM_HASH_CODE, COLLISION_HASH_CODE, NON_EXISTING_HASH_CODE) - var hashCodeType = "" - - @Param(IP_100, IP_99_09, IP_95, IP_70, IP_50, IP_30, IP_0) - var immutablePercentage: Double = 0.0 - - private var elements = listOf() - private var elementsToRemove = listOf() - - @Setup - fun prepare() { - elements = generateElements(hashCodeType, size) - - elementsToRemove = if (hashCodeType == NON_EXISTING_HASH_CODE) { - generateElements(hashCodeType, size) - } else { - elements - } - } - - @Benchmark - fun addAndRemove(): PersistentSet.Builder { - val builder = persistentSetBuilderAdd(implementation, elements, immutablePercentage) - repeat(times = size) { index -> - builder.remove(elementsToRemove[index]) - } - return builder - } -} diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableSet/builder/utils.kt b/benchmarks/src/main/kotlin/benchmarks/immutableSet/builder/utils.kt deleted file mode 100644 index c80379b..0000000 --- a/benchmarks/src/main/kotlin/benchmarks/immutableSet/builder/utils.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Modified from the kotlinx.collections.immutable sources, which contained the following notice: - * Copyright 2016-2019 JetBrains s.r.o. - * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. - */ - -package benchmarks.immutableSet.builder - -import benchmarks.* -import benchmarks.immutableSet.emptyPersistentSet -import kotlinx.collections.immutable.PersistentSet - -fun persistentSetBuilderAdd( - implementation: String, - elements: List, - immutablePercentage: Double -): PersistentSet.Builder { - val immutableSize = immutableSize(elements.size, immutablePercentage) - - var set = emptyPersistentSet(implementation) - for (index in 0 until immutableSize) { - set = set.add(elements[index]) - } - - val builder = set.builder() - for (index in immutableSize until elements.size) { - builder.add(elements[index]) - } - - return builder -} diff --git a/benchmarks/src/main/kotlin/benchmarks/immutableSet/utils.kt b/benchmarks/src/main/kotlin/benchmarks/immutableSet/utils.kt index 432d42e..2cb8b75 100644 --- a/benchmarks/src/main/kotlin/benchmarks/immutableSet/utils.kt +++ b/benchmarks/src/main/kotlin/benchmarks/immutableSet/utils.kt @@ -14,10 +14,10 @@ import kotlin.math.log fun emptyPersistentSet(implementation: String): PersistentSet = when (implementation) { - ORDERED_HAMT_IMPL -> persistentSetOf() - HAMT_IMPL -> persistentHashSetOf() + KOTLIN_ORDERED_IMPL -> persistentSetOf() + KOTLIN_IMPL -> persistentHashSetOf() TREAP_IMPL -> treapSetOf() - HASH_MAP_IMPL -> fakePersistentSetOf() + JAVA_IMPL -> fakePersistentSetOf() else -> throw AssertionError("Unknown PersistentSet implementation: $implementation") } diff --git a/benchmarks/src/main/kotlin/benchmarks/size/ListCase.kt b/benchmarks/src/main/kotlin/benchmarks/size/ListCase.kt new file mode 100644 index 0000000..c7bea36 --- /dev/null +++ b/benchmarks/src/main/kotlin/benchmarks/size/ListCase.kt @@ -0,0 +1,59 @@ +package benchmarks.size + +import benchmarks.* +import com.certora.collect.* +import kotlinx.collections.immutable.* +import kotlinx.serialization.Serializable +import org.openjdk.jol.info.GraphLayout +import java.util.IdentityHashMap + +data class ListCase( + val scenario: String, + val scenarioSize: Int, + val javaList: Sequence>, + val persistentList: Sequence>, + val treapList: Sequence>, +) { + class Context( + val empty: () -> PersistentList, + val key: (Int) -> Any + ) + + constructor(scenario: String, scenarioSize: Int, test: Context.() -> Sequence>) : this( + scenario = scenario, + scenarioSize = scenarioSize, + javaList = Context({ fakePersistentListOf() }, { HashableSizeKey(it) }).test(), + persistentList = Context({ persistentListOf() }, { HashableSizeKey(it) }).test(), + treapList = Context({ treapListOf() }, { HashableSizeKey(it) }).test(), + ) + + @Serializable + data class Sizes( + val scenario: String, + val scenarioSize: Int, + val javaList: Long, + val persistentList: Long, + val treapList: Long, + ) + + private fun Sequence>.computeSize(): Long { + val unwrapped = this.map { (it as? FakePersistentList<*>)?.value ?: it }.toList() + + val keys = IdentityHashMap() + unwrapped.forEach { + it.forEach { keys[it] = DummyValue } + } + + return GraphLayout.parseInstance(*unwrapped.toTypedArray()) + .subtract(GraphLayout.parseInstance(*keys.keys.toTypedArray())) + .totalSize() + } + + val sizes get() = Sizes( + scenario = scenario, + scenarioSize = scenarioSize, + javaList = javaList.computeSize(), + persistentList = persistentList.computeSize(), + treapList = treapList.computeSize(), + ) +} diff --git a/benchmarks/src/main/kotlin/benchmarks/size/Lists.kt b/benchmarks/src/main/kotlin/benchmarks/size/Lists.kt new file mode 100644 index 0000000..80636f0 --- /dev/null +++ b/benchmarks/src/main/kotlin/benchmarks/size/Lists.kt @@ -0,0 +1,63 @@ +package benchmarks.size + +import benchmarks.* +import kotlinx.collections.immutable.* + +val lists = sequence { + yield(ListCase("Empty", 0) { sequenceOf(empty()) }) + scenarioSizes.forEach { + yield(ListCase("Fresh", it) { sequenceOf((1..it).toTestList()) }) + } + scenarioSizes.forEach { yield(ListCase("Append", it) { sequence { + val fresh = (1..it).toTestList() + yield(fresh) + repeat(16) { + yield(fresh) + } + }})} + scenarioSizes.forEach { yield(ListCase("AddLast", it) { sequence { + val fresh = (1..it).toTestList() + yield(fresh) + repeat(16) { + yield(fresh.add("hello")) + } + }})} + scenarioSizes.forEach { it -> yield(ListCase("AddFirst", it) { sequence { + val fresh = (1..it).toTestList() + yield(fresh) + repeat(16) { + yield(fresh.add(0, "hello")) + } + }})} + scenarioSizes.forEach { it -> yield(ListCase("AddMiddle", it) { sequence { + val fresh = (1..it).toTestList() + yield(fresh) + repeat(16) { + yield(fresh.add(fresh.size / 2, "hello")) + } + }})} + scenarioSizes.forEach { it -> yield(ListCase("SetMiddle", it) { sequence { + val fresh = (1..it).toTestList() + yield(fresh) + repeat(16) { + yield(fresh.set(fresh.size / 2, "hello")) + } + }})} + scenarioSizes.forEach { it -> yield(ListCase("SetFirst", it) { sequence { + val fresh = (1..it).toTestList() + yield(fresh) + repeat(16) { + yield(fresh.set(0, "hello")) + } + }})} + scenarioSizes.forEach { it -> yield(ListCase("SetLast", it) { sequence { + val fresh = (1..it).toTestList() + yield(fresh) + repeat(16) { + yield(fresh.set(fresh.lastIndex, "hello")) + } + }})} +} + +context(ListCase.Context) +private fun IntRange.toTestList(): PersistentList = empty() + this.map { key(it) } diff --git a/benchmarks/src/main/kotlin/benchmarks/size/Runner.kt b/benchmarks/src/main/kotlin/benchmarks/size/Runner.kt index 3480411..7972240 100644 --- a/benchmarks/src/main/kotlin/benchmarks/size/Runner.kt +++ b/benchmarks/src/main/kotlin/benchmarks/size/Runner.kt @@ -1,9 +1,9 @@ @file:OptIn(ExperimentalSerializationApi::class) package benchmarks.size -import java.nio.file.* import kotlinx.serialization.* import kotlinx.serialization.csv.* +import java.nio.file.* /** Computes the sizes of various sets and maps, for comparison. Invoked by the `sizesBenchmark` Gradle task. */ fun main(args: Array) { @@ -38,5 +38,18 @@ fun main(args: Array) { ) println("Wrote $mapsFile") } + + println("Computing list sizes...") + val listSizes = lists.map { it.sizes }.toList().groupBy { it.scenario } + listSizes.forEach { + val listsFile = outputDir.resolve("lists-${it.key}.csv") + Files.writeString( + listsFile, + csv.encodeToString( + it.value.toList() + ) + ) + println("Wrote $listsFile") + } } diff --git a/collect/src/main/kotlin/com/certora/collect/EmptyTreapList.kt b/collect/src/main/kotlin/com/certora/collect/EmptyTreapList.kt new file mode 100644 index 0000000..d686b45 --- /dev/null +++ b/collect/src/main/kotlin/com/certora/collect/EmptyTreapList.kt @@ -0,0 +1,66 @@ +package com.certora.collect + +internal class EmptyTreapList private constructor() : TreapList, java.io.Serializable, AbstractList() { + override fun hashCode() = 1 + override fun equals(other: Any?) = (other is List<*>) && (other.isEmpty()) + override fun toString() = "[]" + + override fun isEmpty(): Boolean = true + override val size: Int get() = 0 + override fun iterator() = emptyList().iterator() + + override fun forEachElement(action: (E) -> Unit) {} + override fun forEachElementIndexed(action: (Int, E) -> Unit) {} + override fun updateElements(transform: (E) -> E?): TreapList = this + override fun updateElementsIndexed(transform: (Int, E) -> E?): TreapList = this + + override fun builder(): TreapList.Builder = TreapListBuilder(this) + + override fun addFirst(element: E): TreapList = TreapListNode(element) + override fun addLast(element: E): TreapList = TreapListNode(element) + + override fun addAll(elements: Collection): TreapList = elements.toTreapList() + + override fun add(index: Int, element: E): TreapList = when { + index == 0 -> add(element) + else -> throw IndexOutOfBoundsException("Empty list") + } + override fun addAll(index: Int, c: Collection): TreapList = when { + index == 0 -> addAll(c) + else -> throw IndexOutOfBoundsException("Empty list") + } + + override fun remove(element: E): TreapList = this + override fun removeAll(elements: Collection<@UnsafeVariance E>): TreapList = this + override fun removeAll(predicate: (E) -> Boolean): TreapList = this + override fun retainAll(elements: Collection<@UnsafeVariance E>): TreapList = this + override fun removeAt(index: Int): TreapList = throw IndexOutOfBoundsException("Empty list") + + override fun clear(): TreapList = this + + override fun get(index: Int): E = throw IndexOutOfBoundsException("Empty list") + override fun set(index: Int, element: E): TreapList = throw IndexOutOfBoundsException("Empty list") + override fun indexOf(element: E): Int = -1 + override fun contains(element: E): Boolean = false + override fun containsAll(elements: Collection): Boolean = elements.isEmpty() + override fun lastIndexOf(element: E): Int = -1 + + override fun first(): E = throw NoSuchElementException("Empty list") + override fun firstOrNull(): E? = null + override fun last(): E = throw NoSuchElementException("Empty list") + override fun lastOrNull(): E? = null + + override fun removeFirst(): TreapList = throw NoSuchElementException("Empty list") + override fun removeLast(): TreapList = throw NoSuchElementException("Empty list") + + override fun subList(fromIndex: Int, toIndex: Int): TreapList = when { + fromIndex == 0 && toIndex == 0 -> this + else -> throw IndexOutOfBoundsException("Empty list") + } + + companion object { + private val instance = EmptyTreapList() + @Suppress("UNCHECKED_CAST") + operator fun invoke(): EmptyTreapList = instance as EmptyTreapList + } +} diff --git a/collect/src/main/kotlin/com/certora/collect/TreapList.kt b/collect/src/main/kotlin/com/certora/collect/TreapList.kt new file mode 100644 index 0000000..1d125a4 --- /dev/null +++ b/collect/src/main/kotlin/com/certora/collect/TreapList.kt @@ -0,0 +1,94 @@ +package com.certora.collect + +import kotlinx.collections.immutable.PersistentList + +/** + A PersistentList implemented as a [Treap](https://en.wikipedia.org/wiki/Treap). + */ +@Treapable +public interface TreapList : PersistentList { + override fun add(element: E): TreapList = addLast(element) + override fun addAll(elements: Collection): TreapList + override fun remove(element: E): TreapList + override fun removeAll(elements: Collection): TreapList + override fun removeAll(predicate: (E) -> Boolean): TreapList + override fun retainAll(elements: Collection): TreapList + override fun clear(): TreapList + override fun addAll(index: Int, c: Collection): TreapList + override fun set(index: Int, element: E): TreapList + override fun add(index: Int, element: E): TreapList + override fun removeAt(index: Int): TreapList + override fun subList(fromIndex: Int, toIndex: Int): TreapList + + public fun forEachElement(action: (E) -> Unit) + public fun forEachElementIndexed(action: (Int, E) -> Unit) + + public fun first(): E + public fun firstOrNull(): E? + public fun last(): E + public fun lastOrNull(): E? + + public fun addFirst(element: E): TreapList + public fun addLast(element: E): TreapList + + public fun removeFirst(): TreapList + public fun removeLast(): TreapList + + public fun updateElements(transform: (E) -> E?): TreapList + public fun updateElementsIndexed(transform: (Int, E) -> E?): TreapList + + /** + A [PersistentList.Builder] that produces a [TreapList]. + */ + public interface Builder: MutableList, PersistentList.Builder { + override fun build(): TreapList + + public fun first(): E + public fun firstOrNull(): E? + public fun last(): E + public fun lastOrNull(): E? + + public fun addFirst(element: E) + public fun addLast(element: E) + public fun removeFirst(): E + public fun removeLast(): E + } + + override fun builder(): Builder +} + +public fun treapListOf(): TreapList = EmptyTreapList() +public fun treapListOf(element: T): TreapList = treapListOf().add(element) +public fun treapListOf(vararg elements: T): TreapList = elements.asIterable().toTreapList() + +public inline fun TreapList.mutate(mutator: (MutableList) -> Unit): TreapList = builder().apply(mutator).build() + +public operator fun TreapList.plus(element: E): TreapList = add(element) +public operator fun TreapList.minus(element: E): TreapList = remove(element) +public operator fun TreapList.plus(elements: Iterable): TreapList = addAll(elements.toTreapList()) +public operator fun TreapList.plus(elements: Array): TreapList = addAll(elements.asIterable().toTreapList()) +public operator fun TreapList.plus(elements: Sequence): TreapList = addAll(elements.asIterable().toTreapList()) + +public operator fun TreapList.minus(elements: Iterable): TreapList = + if (elements is Collection) { removeAll(elements) } else { mutate { it.removeAll(elements) }} +public operator fun TreapList.minus(elements: Array): TreapList = mutate { it.removeAll(elements) } +public operator fun TreapList.minus(elements: Sequence): TreapList = minus(elements.asIterable()) + +public fun Iterable.toTreapList(): TreapList = when(this) { + is TreapList -> this + is TreapList.Builder -> this.build() + else -> { + val iterator = this.iterator() + when { + iterator.hasNext() -> TreapListNode.fromIterator(iterator) + else -> EmptyTreapList() + } + } +} + +public fun Sequence.toTreapList(): TreapList = asIterable().toTreapList() + +public fun CharSequence.toTreapList(): TreapList = + treapListOf().mutate { this.toCollection(it) } + +public fun TreapList?.orEmpty(): TreapList = this ?: EmptyTreapList() diff --git a/collect/src/main/kotlin/com/certora/collect/TreapListBuilder.kt b/collect/src/main/kotlin/com/certora/collect/TreapListBuilder.kt new file mode 100644 index 0000000..88489b0 --- /dev/null +++ b/collect/src/main/kotlin/com/certora/collect/TreapListBuilder.kt @@ -0,0 +1,90 @@ +package com.certora.collect + +internal class TreapListBuilder( + private var list: TreapList +) : TreapList.Builder, java.io.Serializable, AbstractMutableList() { + override val size: Int = list.size + override fun isEmpty(): Boolean = list.isEmpty() + + override fun get(index: Int): E = list[index] + + override fun indexOf(element: E): Int = list.indexOf(element) + override fun lastIndexOf(element: E): Int = list.lastIndexOf(element) + + override fun contains(element: E): Boolean = list.contains(element) + override fun containsAll(elements: Collection): Boolean = list.containsAll(elements) + + override fun add(element: E): Boolean = update(list.add(element)) + override fun add(index: Int, element: E) { update(list.add(index, element)) } + override fun addAll(elements: Collection): Boolean = update(list.addAll(elements)) + override fun addAll(index: Int, elements: Collection): Boolean = update(list.addAll(index, elements)) + + override fun remove(element: E): Boolean = update(list.remove(element)) + override fun removeAll(elements: Collection): Boolean = update(list.removeAll(elements)) + override fun removeAt(index: Int): E = get(index).also { list = list.removeAt(index) } + + override fun retainAll(elements: Collection): Boolean = update(list.retainAll(elements)) + + override fun set(index: Int, element: E): E = list.get(index).also { list = list.set(index, element) } + + override fun first(): E = list.first() + override fun firstOrNull(): E? = list.firstOrNull() + override fun last(): E = list.last() + override fun lastOrNull(): E? = list.lastOrNull() + + override fun addFirst(element: E) { list = list.addFirst(element) } + override fun addLast(element: E) { list = list.addLast(element) } + override fun removeFirst(): E = list.first().also { list = list.removeFirst() } + override fun removeLast(): E = list.last().also { list = list.removeLast() } + + override fun clear() { list = EmptyTreapList() } + + override fun iterator(): MutableIterator = listIterator() + override fun listIterator(): MutableListIterator = listIterator(0) + + override fun listIterator(index: Int): MutableListIterator = object : MutableListIterator { + var listIterator = list.listIterator(index) + var lastReturnedIndex = -1 + + override fun hasNext(): Boolean = listIterator.hasNext() + override fun hasPrevious(): Boolean = listIterator.hasPrevious() + override fun next(): E = listIterator.next().also { lastReturnedIndex = listIterator.previousIndex() } + override fun previous(): E = listIterator.previous().also { lastReturnedIndex = listIterator.nextIndex() } + override fun nextIndex(): Int = listIterator.nextIndex() + override fun previousIndex(): Int = listIterator.previousIndex() + + override fun add(element: E) { + val i = nextIndex() + add(i, element) + listIterator = list.listIterator(i + 1) + } + + override fun set(element: E) { + val i = lastReturnedIndex + if (i == -1) { + throw IllegalStateException("set() called before next()") + } + list = list.set(i, element) + } + + override fun remove() { + val i = lastReturnedIndex + if (i == -1) { + throw IllegalStateException("remove() called before next()") + } + removeAt(i) + listIterator = list.listIterator(i) + } + } + + override fun build(): TreapList = list + + private fun update(newList: TreapList): Boolean { + if (newList !== list) { + list = newList + return true + } else { + return false + } + } +} diff --git a/collect/src/main/kotlin/com/certora/collect/TreapListNode.kt b/collect/src/main/kotlin/com/certora/collect/TreapListNode.kt new file mode 100644 index 0000000..8f6c01f --- /dev/null +++ b/collect/src/main/kotlin/com/certora/collect/TreapListNode.kt @@ -0,0 +1,434 @@ +package com.certora.collect + +import kotlin.random.Random +import java.lang.Math.addExact + +/** + A [kotlinx.collections.immutable.PersistentList] implemented as a [treap](https://en.wikipedia.org/wiki/Treap). + + See [Treap] for an overview of the "treap" data structure. This implementation does *not* derive from [Treap], as + [TreapList]'s requirements are somewhat different from [TreapSet] and [TreapMap], but the description of [Treap] is + useful background for understanding the design of [TreapList]. + + Here we use the treap idea a little differently. "Left" and "right" are simply the list's insertion order. + "Priority" is randomly assigned for each new node, as in the original Treap literature. This allows our trees to + contain multiple copies of the same value, and yet remain balanced. + + To facilitate log-time indexing into the list, each node tracks the size of the sub-list represented by that node. + A given node's index in its own sub-list is thus the size of it's left-hand subtree. We do this rather than storing + indexes directly so that lists can be freely inserted into other lists without having to rewrite all of the indices. + */ +internal class TreapListNode private constructor( + private val elem: E, + private val priority: Int, + private val left: TreapListNode? = null, + private val right: TreapListNode? = null, + override val size: Int = addExact(1, addExact(left?.size?:0, right?.size?:0)) +) : TreapList, java.io.Serializable { + + constructor(elem: E) : this(elem, priority = Random.Default.nextInt(), size = 1) + + override fun hashCode() = computeHashCode(initial = 1) + override fun toString() = joinToString(", ", "[", "]") + + override fun equals(other: Any?): Boolean = when { + this === other -> true + other is List<*> -> this.size == other.size && this.zip(other).all { it.first == it.second } + else -> false + } + + override fun isEmpty(): Boolean = false + + override fun builder(): TreapList.Builder = TreapListBuilder(this) + + override fun clear() = EmptyTreapList() + + override fun addFirst(element: E): TreapListNode = TreapListNode(element) append this + override fun addLast(element: E): TreapListNode = this append TreapListNode(element) + + override fun addAll(elements: Collection): TreapList = when { + elements.isEmpty() -> this + else -> this append elements.toTreapList() as TreapListNode + } + + override fun add(index: Int, element: E): TreapList = validPosition(index).let { + when { + index == size -> addLast(element) + index == 0 -> addFirst(element) + else -> split(index).let { (l, r) -> l!!.addLast(element) append r!! } + } + } + + override fun addAll(index: Int, c: Collection): TreapList = validPosition(index).let { + when { + index == size -> addAll(c) + c.isEmpty() -> this + c is TreapListNode -> when { + index == 0 -> c append this + else -> split(index).let { (left, right) -> left!! append c append right!! } + } + else -> split(index).let { (left, right) -> + left.orEmpty().addAll(c).addAll(right!!) + } + } + } + + override fun remove(element: E): TreapList = removeNode(element).orEmpty() + override fun removeFirst(): TreapList = removeFirstNode().orEmpty() + override fun removeLast(): TreapList = removeLastNode().orEmpty() + override fun removeAt(index: Int): TreapList = removeNodeAt(validIndex(index)).orEmpty() + override fun removeAll(elements: Collection): TreapList = removeAll { it in elements } + override fun removeAll(predicate: (E) -> Boolean): TreapList = removeAllNodes(predicate).orEmpty() + override fun retainAll(elements: Collection): TreapList = removeAll { it !in elements } + + override fun get(index: Int): E = when (index) { + 0 -> first() + size - 1 -> last() + else -> getNodeAt(validIndex(index)).elem + } + + override fun set(index: Int, element: E): TreapList = setNodeAt(validIndex(index), element) + + override fun first(): E = firstNode().elem + override fun firstOrNull(): E? = first() + override fun last(): E = lastNode().elem + override fun lastOrNull(): E? = last() + + override fun indexOf(element: E): Int = indexOfFirstNode(rootIndex(), element) ?: -1 + override fun lastIndexOf(element: E): Int = indexOfLastNode(rootIndex(), element) ?: -1 + + override fun contains(element: E): Boolean = + this.elem == element || (left?.contains(element) == true) || (right?.contains(element) == true) + + override fun containsAll(elements: Collection): Boolean = elements.all { contains(it) } + + override fun subList(fromIndex: Int, toIndex: Int): TreapList = when { + validPosition(fromIndex) > validPosition(toIndex) -> + throw IndexOutOfBoundsException("fromIndex $fromIndex > toIndex $toIndex") + fromIndex == 0 -> split(toIndex).first.orEmpty() + toIndex == size -> split(fromIndex).second.orEmpty() + fromIndex == toIndex -> clear() + else -> split(fromIndex).second?.split(toIndex - fromIndex)?.first.orEmpty() + } + + override fun iterator(): Iterator = listIterator() + override fun listIterator(): ListIterator = listIterator(0) + + override fun listIterator(index: Int): ListIterator = object : ListIterator { + /** stores the path back to the original root of the tree. */ + val stack = ArrayDeque>() + + var current: TreapListNode? = null + var currentIndex = 0 + + fun setPosition(index: Int) { + stack.clear() + currentIndex = index + if (index < size) { + fun TreapListNode.buildStack(i: Int) { + stepToIndex( + i, + found = { current = this }, + goLeft = { + stack.addLast(this) + left!!.buildStack(it) + }, + goRight = { + stack.addLast(this) + right!!.buildStack(it) + }, + ) + } + buildStack(index) + } else { + current = null + } + } + + override fun hasNext() = current != null + override fun hasPrevious() = currentIndex > 0 + + override fun nextIndex() = currentIndex + override fun previousIndex() = currentIndex - 1 + + override fun next(): E { + val result = current ?: throw NoSuchElementException() + currentIndex++ + if (current!!.right != null) { + stack.addLast(current!!) + current = current!!.right + while (current!!.left != null) { + stack.addLast(current!!) + current = current!!.left + } + } else { + while (stack.lastOrNull()?.right === current) { + current = stack.removeLast() + } + current = stack.removeLastOrNull() + } + return result.elem + } + + override fun previous(): E { + if (currentIndex == 0) { + throw NoSuchElementException() + } + currentIndex-- + if (current == null) { + setPosition(currentIndex) + } else if (current!!.left != null) { + stack.addLast(current!!) + current = current!!.left + while (current!!.right != null) { + stack.addLast(current!!) + current = current!!.right + } + } else { + while (stack.lastOrNull()?.left === current) { + current = stack.removeLast() + } + current = stack.removeLastOrNull() + } + return current!!.elem + } + }.apply { setPosition(validPosition(index)) } + + override fun forEachElement(action: (E) -> Unit) = forEachNode(action) + override fun forEachElementIndexed(action: (Int, E) -> Unit) = forEachNodeIndexed(rootIndex(), action) + + override fun updateElements(transform: (E) -> E?): TreapList = + updateNodes(transform).orEmpty() + + override fun updateElementsIndexed(transform: (Int, E) -> E?): TreapList = + updateNodesIndexed(rootIndex(), transform).orEmpty() + + private fun with( + left: TreapListNode? = this.left, + right: TreapListNode? = this.right, + elem: E = this.elem + ) = when { + left === this.left && right === this.right && elem === this.elem -> this + else -> TreapListNode( + elem = elem, + left = left, + right = right, + priority = this.priority, + ) + } + + private fun validIndex(index: Int) = when { + index < 0 || index >= size -> + throw IndexOutOfBoundsException("Index $index is out of bounds for list of size $size") + else -> index + } + + private fun validPosition(index: Int) = when { + index < 0 || index > size -> + throw IndexOutOfBoundsException("Index $index is out of bounds for list of size $size") + else -> index + } + + private inline fun stepToIndex( + index: Int, + found: () -> T, + goLeft: (Int) -> T, + goRight: (Int) -> T + ) : T { + val leftSize = left?.size ?: 0 + return when { + index < leftSize -> goLeft(index) + index == leftSize -> found() + else -> goRight(index - leftSize - 1) + } + } + + private fun computeHashCode(initial: Int): Int { + // We use recursive descent rather than `forEachNode` to avoid allocating a closure in this perf-critical + // method. + var code = left?.computeHashCode(initial) ?: initial + code = 31 * code + elem.hashCode() + if (right != null) { + code = right.computeHashCode(code) + } + return code + } + + private infix fun append(that: TreapListNode): TreapListNode = when { + that.priority > this.priority -> that.with(left = this.append(that.left)) + else -> this.with(right = this.right append that) + } + + /** + Splits the list into two lists. This first list contains all elements before [index]; the second list contains + the [index]th element and beyond. + */ + private fun split(index: Int): Pair?, TreapListNode?> = stepToIndex( + index, + found = { left to this.with(left = null) }, + goLeft = { left!!.split(it).let { it.first to this.with(left = it.second) }}, + goRight = { right!!.split(it).let { this.with(right = it.first) to it.second }}, + ) + + private fun removeNode(element: E): TreapListNode? { + val newLeft = left?.removeNode(element) + return when { + newLeft !== left -> this.with(left = newLeft) + element == this.elem -> left append right + else -> this.with(right = this.right?.removeNode(element)) + } + } + + private fun removeFirstNode(): TreapListNode? = when { + left != null -> this.with(left = left.removeFirstNode()) + else -> right + } + + private fun removeLastNode(): TreapListNode? = when { + right != null -> this.with(right = right.removeLastNode()) + else -> left + } + + private fun removeNodeAt(index: Int): TreapListNode? = stepToIndex( + index, + found = { left append right }, + goLeft = { this.with(left = left!!.removeNodeAt(it)) }, + goRight = { this.with(right = right!!.removeNodeAt(it)) }, + ) + + private fun getNodeAt(index: Int): TreapListNode = stepToIndex( + index, + found = { this }, + goLeft = { left!!.getNodeAt(it) }, + goRight = { right!!.getNodeAt(it) }, + ) + + private fun firstNode(): TreapListNode = left?.firstNode() ?: this + private fun lastNode(): TreapListNode = right?.lastNode() ?: this + + private fun leftIndex(parentIndex: Int) = parentIndex - 1 - (right?.size ?: 0) + private fun rightIndex(parentIndex: Int) = parentIndex + 1 + (left?.size ?: 0) + private fun rootIndex() = leftIndex(size) + + private fun indexOfFirstNode(thisIndex: Int, element: E): Int? = + left?.indexOfFirstNode(left.leftIndex(thisIndex), element) + ?: thisIndex.takeIf { this.elem == element } + ?: right?.indexOfFirstNode(right.rightIndex(thisIndex), element) + + private fun indexOfLastNode(thisIndex: Int, element: E): Int? = + right?.indexOfLastNode(right.rightIndex(thisIndex), element) + ?: thisIndex.takeIf { this.elem == element } + ?: left?.indexOfLastNode(left.leftIndex(thisIndex), element) + + private fun setNodeAt(index: Int, element: E): TreapListNode = stepToIndex( + index, + found = { this.with(elem = element) }, + goLeft = { this.with(left = left!!.setNodeAt(it, element)) }, + goRight = { this.with(right = right!!.setNodeAt(it, element)) }, + ) + + private fun removeAllNodes(predicate: (E) -> Boolean): TreapListNode? = when { + predicate(this.elem) -> left?.removeAllNodes(predicate) append right?.removeAllNodes(predicate) + else -> this.with(left = left?.removeAllNodes(predicate), right = right?.removeAllNodes(predicate)) + } + + private fun updateNodes(transform: (E) -> E?): TreapListNode? { + val newLeft = left?.updateNodes(transform) + val newElem = transform(elem) + val newRight = right?.updateNodes(transform) + return when { + newElem == null -> newLeft append newRight + else -> this.with(left = newLeft, right = newRight, elem = newElem) + } + } + + private fun updateNodesIndexed(thisIndex: Int, transform: (Int, E) -> E?): TreapListNode? { + val newLeft = left?.updateNodesIndexed(left.leftIndex(thisIndex), transform) + val newElem = transform(thisIndex, elem) + val newRight = right?.updateNodesIndexed(right.rightIndex(thisIndex), transform) + return when { + newElem == null -> newLeft append newRight + else -> this.with(left = newLeft, right = newRight, elem = newElem) + } + } + + private fun forEachNode(action: (E) -> Unit) { + left?.forEachNode(action) + action(elem) + right?.forEachNode(action) + } + + private fun forEachNodeIndexed(thisIndex: Int, action: (Int, E) -> Unit) { + left?.forEachNodeIndexed(left.leftIndex(thisIndex), action) + action(thisIndex, elem) + right?.forEachNodeIndexed(right.rightIndex(thisIndex), action) + } + + companion object { + private infix fun TreapListNode?.append(that: TreapListNode?): TreapListNode? = when { + this == null -> that + that == null -> this + else -> this.append(that) + } + + /** + Given a non-empty iterator, produces a TreapList from the elements in O(N) time. + */ + fun fromIterator(elems: Iterator): TreapListNode { + + class Result(val node: TreapListNode, val hasNext: Boolean, val nextElem: E?, val nextPri: Int) { + constructor(node: TreapListNode) : this(node, false, null, 0) + constructor(node: TreapListNode, nextElem: E, nextPri: Int) : this(node, true, nextElem, nextPri) + } + + /* + Builds a TreapListNode containing consecutive elements pulled from [elems] until one is assigned a + higher priority than [upperPri]. Returns the node and optionally the higher-priority element. + */ + fun buildLowerPri(upperPri: Int, initialElem: E, initialPri: Int): Result { + var thisNode = TreapListNode(initialElem, initialPri) + + // If there are no more elements, we're done + if (!elems.hasNext()) { + return Result(thisNode) + } + + // Start the next element, and loop until we find one with a higher priority than [upperPri] + var nextElem = elems.next() + var nextPri = Random.Default.nextInt() + while (true) { + when { + nextPri > upperPri -> { + // Return this up the recursion stack; [thisNode] will end up on the left of the next node. + return Result(thisNode, nextElem, nextPri) + } + nextPri > thisNode.priority -> { + // The priority below the node above us, but above the current node. Push the current node + // to the left + thisNode = TreapListNode(nextElem, nextPri, left = thisNode) + if (!elems.hasNext()) { + return Result(thisNode) + } + nextElem = elems.next() + nextPri = Random.Default.nextInt() + } + else -> { + // The priority is below the current node; construct a subtree on the right. + val lowerResult = buildLowerPri(thisNode.priority, nextElem, nextPri) + thisNode = thisNode.with(right = lowerResult.node) + if (!lowerResult.hasNext) { + return Result(thisNode) + } + @Suppress("unchecked_cast") + nextElem = lowerResult.nextElem as E + nextPri = lowerResult.nextPri + } + } + } + } + + // Build the whole list + return buildLowerPri(Int.MAX_VALUE, elems.next(), Random.Default.nextInt()).node + } + } +} diff --git a/collect/src/test/kotlin/com/certora/collect/TreapListTest.kt b/collect/src/test/kotlin/com/certora/collect/TreapListTest.kt new file mode 100644 index 0000000..3482ef5 --- /dev/null +++ b/collect/src/test/kotlin/com/certora/collect/TreapListTest.kt @@ -0,0 +1,406 @@ +package com.certora.collect + +import kotlin.test.* +import kotlinx.collections.immutable.* + +/** Tests for [TreapList]. */ +class TreapListTest { + @Test + fun empty() { + val empty = treapListOf() + val emptyList = persistentListOf() + + assertTrue(empty.isEmpty()) + assertTrue(empty.equals(emptyList)) + assertSame>(empty, treapListOf()) + assertTrue(emptyList.equals(empty)) + assertEquals(0, empty.size) + assertEquals(emptyList.hashCode(), empty.hashCode()) + assertEquals(emptyList.toString(), empty.toString()) + assertEquals(emptyList.joinToString(), empty.joinToString()) + + empty.forEachElement { fail() } + empty.forEachElementIndexed { _, _ -> fail() } + empty.updateElements { fail() } + empty.updateElementsIndexed { _, _ -> fail() } + + assertEquals(listOf(1), empty.add(1)) + assertEquals(listOf(1), empty.addFirst(1)) + assertEquals(listOf(1), empty.addLast(1)) + assertEquals(listOf(1, 2), empty.addAll(listOf(1, 2))) + assertEquals(listOf(1, 2), empty.addAll(empty.addAll(listOf(1, 2)))) + + assertEquals(listOf(1), empty.add(0, 1)) + assertFailsWith { empty.add(1, 1) } + assertFailsWith { empty.add(-1, 1) } + + assertEquals(listOf(1, 2), empty.addAll(0, listOf(1, 2))) + assertFailsWith {empty.addAll(1, listOf(1, 2)) } + assertFailsWith {empty.addAll(-1, listOf(1, 2)) } + + assertSame(empty, empty.remove(1)) + assertSame(empty, empty.removeAll(listOf(1, 2))) + assertSame(empty, empty.removeAll { false }) + assertSame(empty, empty.removeAll { true }) + assertSame(empty, empty.retainAll(listOf(1))) + + assertFailsWith { empty.removeAt(0) } + assertFailsWith { empty.removeAt(1) } + assertFailsWith { empty.removeAt(-1) } + + assertSame(empty, empty.clear()) + + assertFailsWith { empty.get(0) } + assertFailsWith { empty.get(1) } + assertFailsWith { empty.get(-1) } + + assertFailsWith { empty.set(0, 1) } + assertFailsWith { empty.set(1, 1) } + assertFailsWith { empty.set(-1, 1) } + + assertEquals(-1, empty.indexOf(1)) + assertEquals(-1, empty.lastIndexOf(1)) + assertFalse(empty.contains(1)) + + assertTrue(empty.containsAll(listOf())) + assertFalse(empty.containsAll(listOf(1))) + + assertFailsWith { empty.first() } + assertNull(empty.firstOrNull()) + assertFailsWith { empty.last() } + assertNull(empty.lastOrNull()) + + assertFailsWith { empty.removeFirst() } + assertFailsWith { empty.removeLast() } + + empty.builder().also { + it.add(1) + assertEquals(listOf(1), it.build()) + } + } + + @Test + fun hashCodes() { + assertEquals(persistentListOf().hashCode(), treapListOf().hashCode()) + assertEquals(persistentListOf(1).hashCode(), treapListOf(1).hashCode()) + assertEquals(persistentListOf(1, 23).hashCode(), treapListOf(1, 23).hashCode()) + assertEquals(persistentListOf(1, 23, 456).hashCode(), treapListOf(1, 23, 456).hashCode()) + } + + @Test + fun toStrings() { + assertEquals(persistentListOf().toString(), treapListOf().toString()) + assertEquals(persistentListOf(1).toString(), treapListOf(1).toString()) + assertEquals(persistentListOf(1, 23).toString(), treapListOf(1, 23).toString()) + assertEquals(persistentListOf(1, 23, 456).toString(), treapListOf(1, 23, 456).toString()) + } + + @Test + fun clear() { + assertSame(treapListOf(), treapListOf(1, 2, 3, 4).clear()) + } + + @Test + fun add() { + val a = (1..1000).fold(treapListOf()) { acc, n -> acc + n } + val b = (1..1000).fold(treapListOf()) { acc, n -> acc + n } + val c = (1..1000).fold(persistentListOf()) { acc, n -> acc + n } + + assertFalse(a.isEmpty()) + assertEquals(c.size, a.size) + + assertEquals(c, a) + assertEquals(c, b) + assertTrue(a.equals(b)) + assertTrue(b.equals(a)) + assertTrue(a.equals(c)) + assertTrue(c.equals(a)) + } + + @Test + fun addFirst() { + val a = (1..1000).fold(treapListOf()) { acc, n -> acc.addFirst(n) } + val b = (1..1000).fold(treapListOf()) { acc, n -> acc.addFirst(n) } + val c = (1..1000).fold(persistentListOf()) { acc, n -> acc.add(n) }.asReversed() + + assertFalse(a.isEmpty()) + assertEquals(c.size, a.size) + + assertEquals(c, a) + assertEquals(c, b) + assertTrue(a.equals(b)) + assertTrue(b.equals(a)) + assertTrue(a.equals(c)) + assertTrue(c.equals(a)) + } + + @Test + fun addAll() { + val a = (1..1000).fold(treapListOf()) { acc, n -> acc + listOf(n, n+1) } + val b = (1..1000).fold(treapListOf()) { acc, n -> acc + listOf(n, n+1) } + val c = (1..1000).fold(persistentListOf()) { acc, n -> acc + listOf(n, n+1) } + + assertEquals(c, a) + assertEquals(c, b) + assertTrue(a.equals(b)) + assertTrue(b.equals(a)) + assertTrue(a.equals(c)) + assertTrue(c.equals(a)) + + assertEquals(c + c, a + b) + assertEquals(c + c, a + c) + assertEquals(c + c, c + a) + } + + @Test + fun addAt() { + assertEquals(listOf(1, 2, 3), treapListOf(2, 3).add(0, 1)) + assertEquals(listOf(1, 2, 3), treapListOf(1, 3).add(1, 2)) + assertEquals(listOf(1, 2, 3), treapListOf(1, 2).add(2, 3)) + assertFailsWith { treapListOf(1, 2).add(-1, 3) } + assertFailsWith { treapListOf(1, 2).add(3, 3) } + } + + @Test + fun addAllAt() { + assertEquals(listOf(1, 2, 3, 4), treapListOf(3, 4).addAll(0, treapListOf(1, 2))) + assertEquals(listOf(1, 2, 3, 4), treapListOf(3, 4).addAll(0, listOf(1, 2))) + assertEquals(listOf(1, 2, 3, 4), treapListOf(1, 4).addAll(1, treapListOf(2, 3))) + assertEquals(listOf(1, 2, 3, 4), treapListOf(1, 4).addAll(1, listOf(2, 3))) + assertEquals(listOf(1, 2, 3, 4), treapListOf(1, 2).addAll(2, treapListOf(3, 4))) + assertEquals(listOf(1, 2, 3, 4), treapListOf(1, 2).addAll(2, listOf(3, 4))) + assertFailsWith { treapListOf(1, 2).addAll(-1, listOf(3, 4)) } + assertFailsWith { treapListOf(1, 2).addAll(3, listOf(3, 4)) } + } + + @Test + fun remove() { + assertEquals(treapListOf(), treapListOf(1) - 1) + assertEquals(treapListOf(1), treapListOf(1) - 2) + assertEquals(treapListOf(2, 3), treapListOf(1, 2, 3) - 1) + assertEquals(treapListOf(1, 3), treapListOf(1, 2, 3) - 2) + assertEquals(treapListOf(1, 2), treapListOf(1, 2, 3) - 3) + assertEquals(treapListOf(1, 3, 2), treapListOf(1, 2, 3, 2) - 2) + } + + @Test + fun removeFirst() { + assertEquals(treapListOf(), treapListOf(1).removeFirst()) + assertEquals(treapListOf(2), treapListOf(1, 2).removeFirst()) + assertEquals(treapListOf(2, 3), treapListOf(1, 2, 3).removeFirst()) + assertEquals(treapListOf(2, 3, 4), treapListOf(1, 2, 3, 4).removeFirst()) + } + + @Test + fun removeLast() { + assertEquals(treapListOf(), treapListOf(1).removeLast()) + assertEquals(treapListOf(1), treapListOf(1, 2).removeLast()) + assertEquals(treapListOf(1, 2), treapListOf(1, 2, 3).removeLast()) + assertEquals(treapListOf(1, 2, 3), treapListOf(1, 2, 3, 4).removeLast()) + } + + @Test + fun removeAt() { + assertEquals(treapListOf(), treapListOf(1).removeAt(0)) + assertEquals(treapListOf(2, 3), treapListOf(1, 2, 3).removeAt(0)) + assertEquals(treapListOf(1, 3), treapListOf(1, 2, 3).removeAt(1)) + assertEquals(treapListOf(1, 2), treapListOf(1, 2, 3).removeAt(2)) + assertFailsWith { treapListOf(1, 2, 3).removeAt(-1) } + assertFailsWith { treapListOf(1, 2, 3).removeAt(3) } + } + + @Test + fun removeAll() { + assertEquals(treapListOf(2, 2, 2, 3, 3, 3), treapListOf(1, 1, 2, 2, 2, 1, 3, 3, 1, 1, 4, 3, 1, 5, 6).removeAll(setOf(1, 4, 5, 6))) + assertEquals(treapListOf(1, 2, 3), treapListOf(1, 2, 3).removeAll(setOf())) + assertEquals(treapListOf(1, 2, 3), treapListOf(1, 2, 3).removeAll(setOf(4, 5, 6))) + assertEquals(treapListOf(2, 4, 6, 8), treapListOf(1, 2, 3, 4, 5, 6, 7, 8).removeAll { (it % 2) == 1}) + } + + @Test + fun retainAll() { + assertEquals(treapListOf(1, 1, 1, 1, 1, 4, 1, 5, 6), treapListOf(1, 1, 2, 1, 3, 1, 1, 4, 1, 5, 6).retainAll(setOf(1, 4, 5, 6))) + assertEquals(treapListOf(), treapListOf(1, 2, 3).retainAll(setOf())) + assertEquals(treapListOf(1, 2, 3), treapListOf(1, 2, 3).retainAll(setOf(1, 2, 3))) + } + + @Test + fun get() { + assertEquals(1, treapListOf(1, 2, 3)[0]) + assertEquals(2, treapListOf(1, 2, 3)[1]) + assertEquals(3, treapListOf(1, 2, 3)[2]) + assertFailsWith { treapListOf(1, 2, 3)[-1] } + assertFailsWith { treapListOf(1, 2, 3)[3] } + } + + @Test + fun set() { + assertEquals(treapListOf(4, 2, 3), treapListOf(1, 2, 3).set(0, 4)) + assertEquals(treapListOf(1, 4, 3), treapListOf(1, 2, 3).set(1, 4)) + assertEquals(treapListOf(1, 2, 4), treapListOf(1, 2, 3).set(2, 4)) + assertFailsWith { treapListOf(1, 2, 3).set(-1, 4) } + assertFailsWith { treapListOf(1, 2, 3).set(3, 4) } + } + + @Test + fun first() { + assertEquals(1, treapListOf(1).first()) + assertEquals(1, treapListOf(1, 2).first()) + assertEquals(1, treapListOf(1, 2, 3).first()) + } + + @Test + fun firstOrNull() { + assertEquals(1, treapListOf(1).firstOrNull()) + assertEquals(1, treapListOf(1, 2).firstOrNull()) + assertEquals(1, treapListOf(1, 2, 3).firstOrNull()) + } + + @Test + fun last() { + assertEquals(1, treapListOf(1).last()) + assertEquals(2, treapListOf(1, 2).last()) + assertEquals(3, treapListOf(1, 2, 3).last()) + } + + @Test + fun lastOrNull() { + assertEquals(1, treapListOf(1).lastOrNull()) + assertEquals(2, treapListOf(1, 2).lastOrNull()) + assertEquals(3, treapListOf(1, 2, 3).lastOrNull()) + } + + @Test + fun indexOf() { + assertEquals(0, treapListOf(1, 1, 2, 2, 3, 3).indexOf(1)) + assertEquals(2, treapListOf(1, 1, 2, 2, 3, 3).indexOf(2)) + assertEquals(4, treapListOf(1, 1, 2, 2, 3, 3).indexOf(3)) + assertEquals(-1, treapListOf(1, 1, 2, 2, 3, 3).indexOf(4)) + } + + @Test + fun lastIndexOf() { + assertEquals(1, treapListOf(1, 1, 2, 2, 3, 3).lastIndexOf(1)) + assertEquals(3, treapListOf(1, 1, 2, 2, 3, 3).lastIndexOf(2)) + assertEquals(5, treapListOf(1, 1, 2, 2, 3, 3).lastIndexOf(3)) + assertEquals(-1, treapListOf(1, 1, 2, 2, 3, 3).lastIndexOf(4)) + } + + @Test + fun contains() { + assertTrue(treapListOf(1, 2, 3).contains(1)) + assertTrue(treapListOf(1, 2, 3).contains(2)) + assertTrue(treapListOf(1, 2, 3).contains(3)) + assertFalse(treapListOf(1, 2, 3).contains(4)) + } + + @Test + fun containsAll() { + assertTrue(treapListOf(1, 2, 3).containsAll(setOf(1))) + assertTrue(treapListOf(1, 2, 3).containsAll(setOf(1, 2))) + assertTrue(treapListOf(1, 2, 3).containsAll(setOf(1, 3))) + assertTrue(treapListOf(1, 2, 3).containsAll(setOf(1, 2, 3))) + assertFalse(treapListOf(1, 2, 3).containsAll(setOf(1, 2, 3, 4))) + assertTrue(treapListOf(1, 2, 3).containsAll(setOf())) + } + + @Test + fun forEachElement() { + val buf = mutableListOf() + val l = (0..1000).toList() + + l.toTreapList().forEachElement { buf.add(it) } + assertEquals(l, buf) + } + + @Test + fun forEachElementIndexed() { + val buf = mutableListOf() + val l = (0..1000).toList() + + l.toTreapList().forEachElementIndexed { i, it -> + assertEquals(it, i) + buf.add(it) + } + assertEquals(l, buf) + } + + @Test + fun updateElements() { + assertEquals( + treapListOf(1, 3, 42, 7, 9), + treapListOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9).updateElements { + when { + it % 2 == 0 -> null + it == 5 -> 42 + else -> it + } + } + ) + } + + @Test + fun updateElementsIndexed() { + assertEquals( + treapListOf(1, 3, 42, 7, 9), + treapListOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9).updateElementsIndexed { i, it -> + assertEquals(it, i) + when { + it % 2 == 0 -> null + it == 5 -> 42 + else -> it + } + } + ) + } + + @Test + fun forwardIteration() { + val a = (1..1000).fold(treapListOf()) { acc, n -> acc + n } + val c = (1..1000).fold(persistentListOf()) { acc, n -> acc + n } + assertEquals(c, a.toList()) + } + + @Test + fun emptyListIterator() { + val empty = treapListOf().listIterator() + assertFalse(empty.hasNext()) + assertFalse(empty.hasPrevious()) + assertEquals(0, empty.nextIndex()) + assertEquals(-1, empty.previousIndex()) + assertFailsWith { empty.next() } + assertFailsWith { empty.previous() } + } + + @Test + fun listIterator() { + val it = treapListOf(1, 2, 3, 4, 5).listIterator() + assertTrue(it.hasNext()) + assertFalse(it.hasPrevious()) + assertFailsWith { it.previous() } + assertEquals(1, it.next()) + assertEquals(1, it.previous()) + assertEquals(1, it.next()) + assertEquals(2, it.next()) + assertEquals(3, it.next()) + assertEquals(4, it.next()) + assertTrue(it.hasNext()) + assertTrue(it.hasPrevious()) + assertEquals(5, it.next()) + assertFalse(it.hasNext()) + assertTrue(it.hasPrevious()) + assertFailsWith { it.next() } + assertEquals(5, it.previous()) + assertEquals(4, it.previous()) + } + + @Test + fun toTreapList() { + assertEquals((1..4).toList(), (1..4).toTreapList()) + + // Ensure that large lists constructed by toTreapList are balanced. + // If not, the removeAt call will overflow the stack. + val large = (1..100000).toTreapList() + large.removeAt(large.size / 2) + } +}