Improve performance of clusterization used in chart rendering
This commit is contained in:
parent
ac2c387601
commit
4fa46505b6
@ -13,11 +13,11 @@ interface Cluster<V : Any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class ClusterValue<V : Comparable<V>>(val value: V, var cluster: MutableCluster<V>, var error: V) {
|
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))
|
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
|
if (cluster == this.cluster) return false
|
||||||
|
|
||||||
val newError = abs(minus(cluster.center, value))
|
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
|
error = newError
|
||||||
this.cluster.values.remove(this)
|
this.cluster.values.remove(this)
|
||||||
cluster.values.add(this)
|
cluster.values.add(this)
|
||||||
|
cluster.generation++
|
||||||
|
this.cluster.generation++
|
||||||
this.cluster = cluster
|
this.cluster = cluster
|
||||||
return true
|
return true
|
||||||
} else {
|
} 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) {
|
private class MutableCluster<V : Comparable<V>>(var center: V) {
|
||||||
val values = ObjectArrayList<ClusterValue<V>>(min(100, expectedSize))
|
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 {
|
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 (values.isEmpty) return false
|
||||||
|
if (centerCalculatedAtGeneration == generation) return true
|
||||||
|
|
||||||
|
centerCalculatedAtGeneration = generation
|
||||||
var value = identity
|
var value = identity
|
||||||
values.forEach { value = plus(value, it.value) }
|
values.forEach { value = plus(value, it.value) }
|
||||||
val old = center
|
val old = center
|
||||||
@ -51,9 +57,37 @@ private class MutableCluster<V : Comparable<V>>(var center: V, expectedSize: Int
|
|||||||
|
|
||||||
return true
|
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,
|
random: RandomGenerator,
|
||||||
initialClusters: Int = 1,
|
initialClusters: Int = 1,
|
||||||
identity: V,
|
identity: V,
|
||||||
@ -63,49 +97,43 @@ private inline fun <V : Comparable<V>> Iterable<V>.clusterize(
|
|||||||
abs: (V) -> V,
|
abs: (V) -> V,
|
||||||
heuristics: (min: V, max: V, error: V) -> Boolean,
|
heuristics: (min: V, max: V, error: V) -> Boolean,
|
||||||
): List<Cluster<V>> {
|
): List<Cluster<V>> {
|
||||||
|
require(initialClusters > 0) { "Invalid amount of initial clusters: $initialClusters" }
|
||||||
val itr = iterator()
|
val itr = iterator()
|
||||||
|
|
||||||
if (!itr.hasNext())
|
if (!itr.hasNext())
|
||||||
return listOf()
|
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 min = itr.next()
|
||||||
var max = min
|
var max = min
|
||||||
|
|
||||||
while (itr.hasNext()) {
|
values.add(ClusterValue(min, clusters.random(random), min))
|
||||||
val value = itr.next()
|
values[0].cluster.values.add(values[0])
|
||||||
|
|
||||||
|
for (value in itr) {
|
||||||
min = minOf(min, value)
|
min = minOf(min, value)
|
||||||
max = maxOf(max, 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) {
|
if (min == max) {
|
||||||
return listOf(Cluster.Impl(listOf(min), min))
|
return listOf(Cluster.Impl(listOf(min), min))
|
||||||
}
|
}
|
||||||
|
|
||||||
var targetClusters = initialClusters
|
|
||||||
val clusters = ObjectArrayList<MutableCluster<V>>(initialClusters)
|
|
||||||
val values = ObjectArrayList<ClusterValue<V>>(expectedSize)
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
clusters.clear()
|
|
||||||
values.clear()
|
|
||||||
|
|
||||||
var converged = false
|
var converged = false
|
||||||
var oversaturation = 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) }
|
clusters.forEach { it.calculateCenter(identity, plus, minus, divInt, abs) }
|
||||||
|
|
||||||
while (!converged) {
|
while (!converged) {
|
||||||
@ -127,26 +155,7 @@ private inline fun <V : Comparable<V>> Iterable<V>.clusterize(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (cluster in emptyClusters) {
|
for (cluster in emptyClusters) {
|
||||||
var candidate: ClusterValue<V>? = null
|
if (!cluster.pullFrom(clusters, identity)) {
|
||||||
|
|
||||||
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 {
|
|
||||||
oversaturation = true
|
oversaturation = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -156,8 +165,10 @@ private inline fun <V : Comparable<V>> Iterable<V>.clusterize(
|
|||||||
|
|
||||||
val maxError = values.maxOf { it.error }
|
val maxError = values.maxOf { it.error }
|
||||||
|
|
||||||
if (!oversaturation && targetClusters < values.size && heuristics(min, max, maxError)) {
|
if (!oversaturation && clusters.size < values.size && heuristics(min, max, maxError)) {
|
||||||
targetClusters++
|
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 {
|
} else {
|
||||||
return clusters.stream()
|
return clusters.stream()
|
||||||
.filter { it.values.isNotEmpty() }
|
.filter { it.values.isNotEmpty() }
|
||||||
|
Loading…
Reference in New Issue
Block a user