commonTest.stress.map.PersistentHashMapTest.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kotlinx-collections-immutable-metadata Show documentation
Show all versions of kotlinx-collections-immutable-metadata Show documentation
Kotlin Immutable Collections multiplatform library
The newest version!
/*
* 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 tests.stress.map
import kotlinx.collections.immutable.PersistentMap
import kotlinx.collections.immutable.persistentHashMapOf
import tests.NForAlgorithmComplexity
import tests.distinctStringValues
import tests.remove
import tests.stress.ExecutionTimeMeasuringTest
import tests.stress.IntWrapper
import tests.stress.WrapperGenerator
import kotlin.random.Random
import kotlin.test.*
class PersistentHashMapTest : ExecutionTimeMeasuringTest() {
@Test
fun isEmptyTests() {
var map = persistentHashMapOf()
assertTrue(map.isEmpty())
assertFalse(map.put(0, "last").isEmpty())
val elementsToAdd = NForAlgorithmComplexity.O_NlogN
val values = distinctStringValues(elementsToAdd)
repeat(times = elementsToAdd) { index ->
map = map.put(index, values[index])
assertFalse(map.isEmpty())
}
repeat(times = elementsToAdd - 1) { index ->
map = map.remove(index)
assertFalse(map.isEmpty())
}
map = map.remove(elementsToAdd - 1)
assertTrue(map.isEmpty())
}
@Test
fun sizeTests() {
var map = persistentHashMapOf()
assertTrue(map.size == 0)
assertEquals(1, map.put(1, 1).size)
val elementsToAdd = NForAlgorithmComplexity.O_NlogN
repeat(times = elementsToAdd) { index ->
map = map.put(index, index)
assertEquals(index + 1, map.size)
map = map.put(index, index)
assertEquals(index + 1, map.size)
map = map.put(index, 7)
assertEquals(index + 1, map.size)
}
repeat(times = elementsToAdd) { index ->
map = map.remove(index)
assertEquals(elementsToAdd - index - 1, map.size)
map = map.remove(index)
assertEquals(elementsToAdd - index - 1, map.size)
}
}
@Test
fun keysValuesEntriesTests() {
fun testProperties(expectedKeys: Set, actualMap: PersistentMap) {
val values = actualMap.values
val keys = actualMap.keys
val entries = actualMap.entries
assertTrue(listOf(values.size, keys.size, entries.size).all { it == expectedKeys.size })
assertEquals>(expectedKeys, keys)
assertTrue(keys.containsAll(values))
entries.forEach { entry ->
assertEquals(entry.key, entry.value)
assertTrue(expectedKeys.contains(entry.key))
}
}
var map = persistentHashMapOf()
assertTrue(map.keys.isEmpty())
assertTrue(map.values.isEmpty())
val elementsToAdd = NForAlgorithmComplexity.O_NNlogN
val set = hashSetOf()
repeat(times = elementsToAdd) {
val key = Random.nextInt()
set.add(key)
map = map.put(key, key)
testProperties(set, map)
}
set.toMutableSet().forEach { key ->
set.remove(key)
map = map.remove(key)
testProperties(set, map)
}
}
@Test
fun removeTests() {
var map = persistentHashMapOf()
assertTrue(map.put(0, "0").remove(0).isEmpty())
val elementsToAdd = NForAlgorithmComplexity.O_NlogN
val values = distinctStringValues(elementsToAdd)
repeat(times = elementsToAdd) { index ->
map = map.put(index, values[index])
}
repeat(times = elementsToAdd) { index ->
assertEquals(elementsToAdd - index, map.size)
assertEquals(values[index], map[index])
map = map.remove(index)
assertNull(map[index])
}
assertTrue(map.isEmpty())
}
@Test
fun removeEntryTests() {
var map = persistentHashMapOf()
assertTrue(map.put(0, "0").remove(0, "0").isEmpty())
assertFalse(map.put(0, "0").remove(0, "x").isEmpty())
val elementsToAdd = NForAlgorithmComplexity.O_NlogN
val values = distinctStringValues(elementsToAdd + 1)
repeat(times = elementsToAdd) { index ->
map = map.put(index, values[index])
}
repeat(times = elementsToAdd) { index ->
assertEquals(elementsToAdd - index, map.size)
assertEquals(values[index], map[index])
map = map.remove(index, values[index + 1])
assertEquals(values[index], map[index])
map = map.remove(index, values[index])
assertNull(map[index])
}
assertTrue(map.isEmpty())
}
@Test
fun getTests() {
var map = persistentHashMapOf()
assertEquals("1", map.put(1, "1")[1])
val elementsToAdd = NForAlgorithmComplexity.O_NNlogN
val values = distinctStringValues(elementsToAdd)
repeat(times = elementsToAdd) { index ->
map = map.put(index, values[index])
for (i in 0..index) {
assertEquals(values[i], map[i])
}
}
repeat(times = elementsToAdd) { index ->
for (i in elementsToAdd - 1 downTo index) {
assertEquals(values[i], map[i])
}
map = map.remove(index)
}
}
@Test
fun putTests() {
var map = persistentHashMapOf()
assertEquals("2", map.put(1, "1").put(1, "2")[1])
val elementsToAdd = NForAlgorithmComplexity.O_NNlogN
val values = distinctStringValues(2 * elementsToAdd)
repeat(times = elementsToAdd) { index ->
map = map.put(index, values[2 * index])
for (i in 0..index) {
val valueIndex = i + index
assertEquals(values[valueIndex], map[i])
map = map.put(i, values[valueIndex + 1])
assertEquals(values[valueIndex + 1], map[i])
}
}
repeat(times = elementsToAdd) { index ->
for (i in index until elementsToAdd) {
val valueIndex = elementsToAdd - index + i
assertEquals(values[valueIndex], map[i])
map = map.put(i, values[valueIndex - 1])
assertEquals(values[valueIndex - 1], map[i])
}
map = map.remove(index)
}
assertTrue(map.isEmpty())
}
@Test
fun collisionTests() {
var map = persistentHashMapOf()
val oneWrapper = IntWrapper(1, 1)
val twoWrapper = IntWrapper(2, 1)
assertEquals(1, map.put(oneWrapper, 1).put(twoWrapper, 2)[oneWrapper])
assertEquals(2, map.put(oneWrapper, 1).put(twoWrapper, 2)[twoWrapper])
repeat(times = 2) { removeEntryPredicate ->
val elementsToAdd = NForAlgorithmComplexity.O_NlogN
val maxHashCode = elementsToAdd / 5 // should be less than `elementsToAdd`
val keyGen = WrapperGenerator(maxHashCode)
fun key(key: Int): IntWrapper {
return keyGen.wrapper(key)
}
repeat(times = elementsToAdd) { index ->
map = map.put(key(index), Int.MIN_VALUE)
assertEquals(Int.MIN_VALUE, map[key(index)])
assertEquals(index + 1, map.size)
map = map.put(key(index), index)
assertEquals(index + 1, map.size)
val collisions = keyGen.wrappersByHashCode(key(index).hashCode)
assertTrue(collisions.contains(key(index)))
for (key in collisions) {
assertEquals(key.obj, map[key])
}
}
repeat(times = elementsToAdd) { index ->
val collisions = keyGen.wrappersByHashCode(key(index).hashCode)
assertTrue(collisions.contains(key(index)))
if (map[key(index)] == null) {
for (key in collisions) {
assertNull(map[key])
}
} else {
for (key in collisions) {
assertEquals(key.obj, map[key])
map = if (removeEntryPredicate == 1) {
val nonExistingValue = Int.MIN_VALUE
val sameMap = map.remove(key, nonExistingValue)
assertEquals(map.size, sameMap.size)
assertEquals(key.obj, sameMap[key])
map.remove(key, key.obj)
} else {
val nonExistingKey = IntWrapper(Int.MIN_VALUE, key.hashCode)
val sameMap = map.remove(nonExistingKey)
assertEquals(map.size, sameMap.size)
assertEquals(key.obj, sameMap[key])
map.remove(key)
}
assertNull(map[key])
}
}
}
assertTrue(map.isEmpty())
}
}
@Test
fun randomOperationsTests() {
repeat(times = 1) {
val mutableMaps = List(10) { hashMapOf() }
val immutableMaps = MutableList(10) { persistentHashMapOf() }
val operationCount = NForAlgorithmComplexity.O_NlogN
val numberOfDistinctHashCodes = operationCount / 3 // less than `operationCount` to increase collision cases
val hashCodes = List(numberOfDistinctHashCodes) { Random.nextInt() }
repeat(times = operationCount) {
val index = Random.nextInt(mutableMaps.size)
val mutableMap = mutableMaps[index]
val immutableMap = immutableMaps[index]
val shouldRemove = Random.nextDouble() < 0.3
val shouldOperateOnExistingKey = mutableMap.isNotEmpty() && Random.nextDouble().let { if (shouldRemove) it < 0.8 else it < 0.2 }
val key = when {
shouldOperateOnExistingKey -> mutableMap.keys.first()
Random.nextDouble() < 0.001 -> null
else -> IntWrapper(Random.nextInt(), hashCodes.random())
}
val shouldRemoveByKey = shouldRemove && Random.nextBoolean()
val shouldRemoveByKeyAndValue = shouldRemove && !shouldRemoveByKey
val newImmutableMap = when {
shouldRemoveByKey -> {
mutableMap.remove(key)
immutableMap.remove(key)
}
shouldRemoveByKeyAndValue -> {
val shouldBeCurrentValue = Random.nextDouble() < 0.8
val value = if (shouldOperateOnExistingKey && shouldBeCurrentValue) mutableMap[key] else Random.nextInt()
mutableMap.remove(key, value)
immutableMap.remove(key, value)
}
else -> {
val shouldPutNullValue = Random.nextDouble() < 0.001
val value = if (shouldPutNullValue) null else Random.nextInt()
mutableMap[key] = value
immutableMap.put(key, value)
}
}
testAfterOperation(mutableMap, newImmutableMap, key)
immutableMaps[index] = newImmutableMap
}
assertEquals>(mutableMaps, immutableMaps)
val maxSize = immutableMaps.maxBy { it.size }?.size
println("Largest persistent map size: $maxSize")
mutableMaps.forEachIndexed { index, mutableMap ->
var immutableMap = immutableMaps[index]
val keys = mutableMap.keys.toMutableList()
for (key in keys) {
mutableMap.remove(key)
immutableMap = immutableMap.remove(key)
testAfterOperation(mutableMap, immutableMap, key)
}
}
}
}
private fun testAfterOperation(
expected: Map,
actual: Map,
operationKey: IntWrapper?
) {
assertEquals(expected.size, actual.size)
assertEquals(expected[operationKey], actual[operationKey])
assertEquals(expected.containsKey(operationKey), actual.containsKey(operationKey))
// assertEquals(expected.containsValue(operationKey?.obj), actual.containsValue(operationKey?.obj))
// assertEquals(expected.keys, actual.keys)
// assertEquals(expected.values.sortedBy { it }, actual.values.sortedBy { it })
}
}