From 8439df8ae59126ccee7bd240d81e9d8a1254d328 Mon Sep 17 00:00:00 2001 From: alex_s168 <63254202+alex-s168@users.noreply.github.com> Date: Tue, 30 Jul 2024 17:45:55 +0000 Subject: [PATCH] hash map (-> 0.18) --- build.gradle.kts | 2 +- .../kotlin/blitz/collections/BlitzHashMap.kt | 45 +++++++++++ src/main/kotlin/blitz/collections/BlitzMap.kt | 11 +++ src/main/kotlin/blitz/collections/Buckets.kt | 75 +++++++++++++++++++ src/main/kotlin/blitz/collections/Contents.kt | 7 ++ .../kotlin/blitz/collections/I2HashMap.kt | 28 +++++++ .../kotlin/blitz/collections/I3HashMap.kt | 29 +++++++ src/main/kotlin/blitz/math/Cantor.kt | 4 + src/test/kotlin/map.kt | 16 ++++ 9 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/blitz/collections/BlitzHashMap.kt create mode 100644 src/main/kotlin/blitz/collections/BlitzMap.kt create mode 100644 src/main/kotlin/blitz/collections/Buckets.kt create mode 100644 src/main/kotlin/blitz/collections/I2HashMap.kt create mode 100644 src/main/kotlin/blitz/collections/I3HashMap.kt create mode 100644 src/main/kotlin/blitz/math/Cantor.kt create mode 100644 src/test/kotlin/map.kt diff --git a/build.gradle.kts b/build.gradle.kts index 5be3ca7..40068d0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,7 +5,7 @@ plugins { } group = "me.alex_s168" -version = "0.17" +version = "0.18" repositories { mavenCentral() diff --git a/src/main/kotlin/blitz/collections/BlitzHashMap.kt b/src/main/kotlin/blitz/collections/BlitzHashMap.kt new file mode 100644 index 0000000..c055ca9 --- /dev/null +++ b/src/main/kotlin/blitz/collections/BlitzHashMap.kt @@ -0,0 +1,45 @@ +package blitz.collections + +class BlitzHashMap( + private val bucketCount: Int = 16, + private val bucketSrc: DynBuckets, + private val hash: (K) -> Int, +): BlitzMap> { + private val buckets = Array(bucketCount) { bucketSrc.new() } + + override fun index(key: K): Index = + IndexImpl(buckets[hash(key) % bucketCount], key) + + private inline fun index(idx: Index) = + idx as IndexImpl + + override operator fun get(idx: Index): V? = + index(idx).let { idx -> + bucketSrc.get(idx.bucket, idx.key) + } + + override operator fun set(idx: Index, value: V?) { + index(idx).let { idx -> + if (value == null) { + bucketSrc.remove(idx.bucket, idx.key) + } else { + bucketSrc.set(idx.bucket, idx.key, value) + } + } + } + + sealed interface Index + + private class IndexImpl( + val bucket: Any?, + val key: K, + ): Index + + override val contents: Contents> + get() = buckets + .map { bucketSrc.contents(it) } + .reduce { acc, pairs -> acc + pairs } + + val bucketStats + get() = Contents(buckets.map { bucketSrc.contents(it).count() }) +} \ No newline at end of file diff --git a/src/main/kotlin/blitz/collections/BlitzMap.kt b/src/main/kotlin/blitz/collections/BlitzMap.kt new file mode 100644 index 0000000..c4077de --- /dev/null +++ b/src/main/kotlin/blitz/collections/BlitzMap.kt @@ -0,0 +1,11 @@ +package blitz.collections + +interface BlitzMap { + fun index(key: K): I + operator fun get(index: I): V? + operator fun set(index: I, value: V?) + val contents: Contents> +} + +fun BlitzMap.remove(index: I) = + set(index, null) \ No newline at end of file diff --git a/src/main/kotlin/blitz/collections/Buckets.kt b/src/main/kotlin/blitz/collections/Buckets.kt new file mode 100644 index 0000000..6975935 --- /dev/null +++ b/src/main/kotlin/blitz/collections/Buckets.kt @@ -0,0 +1,75 @@ +package blitz.collections + +interface Buckets { + fun new(): B + fun get(bucket: B, key: K): V? + fun set(bucket: B, key: K, value: V) + fun remove(bucket: B, key: K) + fun contents(bucket: B): Contents> +} + +class DynBucketsT( + private val parent: Buckets +): Buckets { + override fun new(): Any? = + parent.new() + + override fun contents(bucket: Any?): Contents> = + parent.contents(bucket as B) + + override fun remove(bucket: Any?, key: K) = + parent.remove(bucket as B, key) + + override fun set(bucket: Any?, key: K, value: V) = + parent.set(bucket as B, key, value) + + override fun get(bucket: Any?, key: K): V? = + parent.get(bucket as B, key) +} + +typealias DynBuckets = DynBucketsT + +class ListBuckets( + private val eq: (K, K) -> Boolean = { a, b -> a == b }, + private val construct: () -> MutableList> +): Buckets>> { + class Entry(val key: K, var value: V): Comparable> { + override fun compareTo(other: ListBuckets.Entry): Int = + (key as? Comparable)?.compareTo(other.key) ?: 0 + + override fun equals(other: Any?): Boolean = + key == other + } + + private fun entry(bucket: MutableList>, key: K) = + bucket.firstOrNull { eq(it.key, key) } + + override fun get(bucket: MutableList>, key: K): V? = + entry(bucket, key)?.value + + override fun set(bucket: MutableList>, key: K, value: V) { + val entry = entry(bucket, key) + if (entry != null) { + entry.value = value + } else { + bucket.add(Entry(key, value)) + } + } + + override fun remove(bucket: MutableList>, key: K) { + entry(bucket, key)?.let(bucket::remove) + } + + override fun new(): MutableList> = + construct() + + override fun contents(bucket: MutableList>): Contents> = + bucket.map { it.key to it.value }.contents +} + +fun , V> SortedListBuckets( + underlying: () -> MutableList>, + eq: (K, K) -> Boolean = { a, b -> a == b }, +) = ListBuckets(eq) { + SortedList(underlying()) { it } +} \ No newline at end of file diff --git a/src/main/kotlin/blitz/collections/Contents.kt b/src/main/kotlin/blitz/collections/Contents.kt index 97c1d37..8e08256 100644 --- a/src/main/kotlin/blitz/collections/Contents.kt +++ b/src/main/kotlin/blitz/collections/Contents.kt @@ -3,6 +3,13 @@ package blitz.collections class Contents internal constructor( private val iterable: Iterable ): Iterable { + operator fun plus(other: Contents): Contents { + val li = mutableListOf() + li.addAll(this) + li.addAll(other) + return li.contents + } + override fun iterator(): Iterator = iterable.iterator() diff --git a/src/main/kotlin/blitz/collections/I2HashMap.kt b/src/main/kotlin/blitz/collections/I2HashMap.kt new file mode 100644 index 0000000..e97e4b6 --- /dev/null +++ b/src/main/kotlin/blitz/collections/I2HashMap.kt @@ -0,0 +1,28 @@ +package blitz.collections + +import blitz.math.cantor + +class I2HashMapKey( + val a: Int, + val b: Int +): Comparable { + val cantorHash = cantor(a, b) + override fun toString() = + "($a, $b)" + override fun hashCode(): Int = + cantorHash + override fun equals(other: Any?) = + other is I2HashMapKey && other.a == a && other.b == b + override fun compareTo(other: I2HashMapKey): Int = + cantorHash.compareTo(other.cantorHash) +} + +fun I2HashMap( + underlying: () -> MutableList>, + bucketCount: Int = 16, +): BlitzHashMap = + BlitzHashMap( + bucketCount, + DynBuckets(SortedListBuckets(underlying)), + I2HashMapKey::cantorHash + ) \ No newline at end of file diff --git a/src/main/kotlin/blitz/collections/I3HashMap.kt b/src/main/kotlin/blitz/collections/I3HashMap.kt new file mode 100644 index 0000000..d840736 --- /dev/null +++ b/src/main/kotlin/blitz/collections/I3HashMap.kt @@ -0,0 +1,29 @@ +package blitz.collections + +import blitz.math.cantor + +class I3HashMapKey( + val a: Int, + val b: Int, + val c: Int, +): Comparable { + val cantorHash = cantor(a, cantor(b, c)) + override fun toString() = + "($a, $b, $c)" + override fun hashCode(): Int = + cantorHash + override fun equals(other: Any?) = + other is I3HashMapKey && other.a == a && other.b == b && other.c == c + override fun compareTo(other: I3HashMapKey): Int = + cantorHash.compareTo(other.cantorHash) +} + +fun I3HashMap( + underlying: () -> MutableList>, + bucketCount: Int = 16, +): BlitzHashMap = + BlitzHashMap( + bucketCount, + DynBuckets(SortedListBuckets(underlying)), + I3HashMapKey::cantorHash + ) \ No newline at end of file diff --git a/src/main/kotlin/blitz/math/Cantor.kt b/src/main/kotlin/blitz/math/Cantor.kt new file mode 100644 index 0000000..cea93d1 --- /dev/null +++ b/src/main/kotlin/blitz/math/Cantor.kt @@ -0,0 +1,4 @@ +package blitz.math + +fun cantor(a: Int, b: Int) = + (a + b + 1) * (a + b) / 2 + b \ No newline at end of file diff --git a/src/test/kotlin/map.kt b/src/test/kotlin/map.kt new file mode 100644 index 0000000..9221715 --- /dev/null +++ b/src/test/kotlin/map.kt @@ -0,0 +1,16 @@ +import blitz.collections.I2HashMap +import blitz.collections.I2HashMapKey +import kotlin.test.Test + +class Maps { + @Test + fun i2hashmap() { + val a = I2HashMap(::mutableListOf) + a[a.index(I2HashMapKey(1, 2390))] = "hi" + a[a.index(I2HashMapKey(320, 23))] = "bye" + a[a.index(I2HashMapKey(320, 25))] = "bye2" + a[a.index(I2HashMapKey(32, 344))] = "bye3" + println(a.contents) + println(a.bucketStats) + } +} \ No newline at end of file