Improve performance of clusterization used in chart rendering

This commit is contained in:
DBotThePony 2024-11-15 15:24:38 +07:00
parent ac2c387601
commit 4fa46505b6
Signed by: DBot
GPG Key ID: DCC23B5715498507

View File

@ -13,11 +13,11 @@ interface Cluster<V : Any> {
}
private class ClusterValue<V : Comparable<V>>(val value: V, var cluster: MutableCluster<V>, var error: V) {
inline fun updateError(abs: (V) -> V, minus: (V, V) -> V) {
fun updateError(abs: (V) -> V, minus: (V, V) -> V) {
error = abs(minus(cluster.center, value))
}
inline fun maybeSwitchCluster(cluster: MutableCluster<V>, abs: (V) -> V, minus: (V, V) -> V): Boolean {
fun maybeSwitchCluster(cluster: MutableCluster<V>, abs: (V) -> V, minus: (V, V) -> V): Boolean {
if (cluster == this.cluster) return false
val newError = abs(minus(cluster.center, value))
@ -26,6 +26,8 @@ private class ClusterValue<V : Comparable<V>>(val value: V, var cluster: Mutable
error = newError
this.cluster.values.remove(this)
cluster.values.add(this)
cluster.generation++
this.cluster.generation++
this.cluster = cluster
return true
} else {
@ -34,12 +36,16 @@ private class ClusterValue<V : Comparable<V>>(val value: V, var cluster: Mutable
}
}
private class MutableCluster<V : Comparable<V>>(var center: V, expectedSize: Int) {
val values = ObjectArrayList<ClusterValue<V>>(min(100, expectedSize))
private class MutableCluster<V : Comparable<V>>(var center: V) {
val values = ObjectArrayList<ClusterValue<V>>()
var generation = 0
var centerCalculatedAtGeneration = -1
inline fun calculateCenter(identity: V, plus: (V, V) -> V, minus: (V, V) -> V, divInt: (V, Int) -> V, abs: (V) -> V): Boolean {
if (values.isEmpty()) return false
fun calculateCenter(identity: V, plus: (V, V) -> V, minus: (V, V) -> V, divInt: (V, Int) -> V, abs: (V) -> V): Boolean {
if (values.isEmpty) return false
if (centerCalculatedAtGeneration == generation) return true
centerCalculatedAtGeneration = generation
var value = identity
values.forEach { value = plus(value, it.value) }
val old = center
@ -51,9 +57,37 @@ private class MutableCluster<V : Comparable<V>>(var center: V, expectedSize: Int
return true
}
fun pullFrom(clusters: MutableList<MutableCluster<V>>, identity: V): Boolean {
var candidate: ClusterValue<V>? = null
for (eCluser in clusters) {
if (eCluser.values.size > 1) {
for (value in eCluser.values) {
if (candidate == null || candidate.error < value.error) {
candidate = value
}
}
}
}
if (candidate != null) {
values.add(candidate)
candidate.cluster.values.remove(candidate)
this.generation++
candidate.cluster.generation++
candidate.cluster = this
center = candidate.value
candidate.error = identity
clusters.add(this)
return true
} else {
return false
}
}
}
private inline fun <V : Comparable<V>> Iterable<V>.clusterize(
private fun <V : Comparable<V>> Iterable<V>.clusterize(
random: RandomGenerator,
initialClusters: Int = 1,
identity: V,
@ -63,49 +97,43 @@ private inline fun <V : Comparable<V>> Iterable<V>.clusterize(
abs: (V) -> V,
heuristics: (min: V, max: V, error: V) -> Boolean,
): List<Cluster<V>> {
require(initialClusters > 0) { "Invalid amount of initial clusters: $initialClusters" }
val itr = iterator()
if (!itr.hasNext())
return listOf()
var expectedSize = 1
val clusters = ObjectArrayList<MutableCluster<V>>(initialClusters)
for (i in 0 until initialClusters) {
clusters.add(MutableCluster(identity))
}
val values = ObjectArrayList<ClusterValue<V>>()
var min = itr.next()
var max = min
while (itr.hasNext()) {
val value = itr.next()
values.add(ClusterValue(min, clusters.random(random), min))
values[0].cluster.values.add(values[0])
for (value in itr) {
min = minOf(min, value)
max = maxOf(max, value)
expectedSize++
val cluster = clusters.random(random)
val wrapped = ClusterValue(value, cluster, value)
values.add(wrapped)
cluster.values.add(wrapped)
}
if (min == max) {
return listOf(Cluster.Impl(listOf(min), min))
}
var targetClusters = initialClusters
val clusters = ObjectArrayList<MutableCluster<V>>(initialClusters)
val values = ObjectArrayList<ClusterValue<V>>(expectedSize)
while (true) {
clusters.clear()
values.clear()
var converged = false
var oversaturation = false
for (i in 0 until targetClusters) {
clusters.add(MutableCluster(identity, expectedSize))
}
for (value in this) {
val cluster = clusters.random(random)
val wrapped = ClusterValue(value, cluster, value)
values.add(wrapped)
cluster.values.add(wrapped)
}
clusters.forEach { it.calculateCenter(identity, plus, minus, divInt, abs) }
while (!converged) {
@ -127,26 +155,7 @@ private inline fun <V : Comparable<V>> Iterable<V>.clusterize(
}
for (cluster in emptyClusters) {
var candidate: ClusterValue<V>? = null
for (eCluser in clusters) {
if (eCluser.values.size > 1) {
for (value in eCluser.values) {
if (candidate == null || candidate.error < value.error) {
candidate = value
}
}
}
}
if (candidate != null) {
cluster.values.add(candidate)
candidate.cluster.values.remove(candidate)
candidate.cluster = cluster
cluster.center = candidate.value
candidate.error = identity
clusters.add(cluster)
} else {
if (!cluster.pullFrom(clusters, identity)) {
oversaturation = true
break
}
@ -156,8 +165,10 @@ private inline fun <V : Comparable<V>> Iterable<V>.clusterize(
val maxError = values.maxOf { it.error }
if (!oversaturation && targetClusters < values.size && heuristics(min, max, maxError)) {
targetClusters++
if (!oversaturation && clusters.size < values.size && heuristics(min, max, maxError)) {
val cluster = MutableCluster(identity)
check(cluster.pullFrom(clusters, identity)) { "Newly created cluster couldn't pull first value from other clusters (expected at least one cluster with 2 values in it)" }
clusters.add(cluster)
} else {
return clusters.stream()
.filter { it.values.isNotEmpty() }