package moe.nea.firmament.util.math import kotlin.math.min /** * Algorithm for (sort of) cheap reconciliation of two cycles with missing frames. */ object GChainReconciliation { // Step one: Find the most common element and shift the arrays until it is at the start in both (this could be just rotating until minimal levenshtein distance or smth. that would be way better for cycles with duplicates, but i do not want to implement levenshtein as well) // Step two: Find the first different element. // Step three: Find the next index of both of the elements. // Step four: Insert the element that is further away. fun Iterable.frequencies(): Map { val acc = mutableMapOf() for (t in this) { acc.compute(t, { _, old -> (old ?: 0) + 1 }) } return acc } fun findMostCommonlySharedElement( leftChain: List, rightChain: List, ): T { val lf = leftChain.frequencies() val rf = rightChain.frequencies() val mostCommonlySharedElement = lf.maxByOrNull { min(it.value, rf[it.key] ?: 0) }?.key if (mostCommonlySharedElement == null || mostCommonlySharedElement !in rf) error("Could not find a shared element") return mostCommonlySharedElement } fun List.getMod(index: Int): T { return this[index.mod(size)] } fun List.rotated(offset: Int): List { val newList = mutableListOf() for (index in indices) { newList.add(getMod(index - offset)) } return newList } fun shiftToFront(list: List, element: T): List { val shiftDistance = list.indexOf(element) require(shiftDistance >= 0) return list.rotated(-shiftDistance) } fun List.indexOfOrMaxInt(element: T): Int = indexOf(element).takeUnless { it < 0 } ?: Int.MAX_VALUE fun reconcileCycles( leftChain: List, rightChain: List, ): List { val mostCommonElement = findMostCommonlySharedElement(leftChain, rightChain) val left = shiftToFront(leftChain, mostCommonElement).toMutableList() val right = shiftToFront(rightChain, mostCommonElement).toMutableList() var index = 0 while (index < left.size && index < right.size) { val leftEl = left[index] val rightEl = right[index] if (leftEl == rightEl) { index++ continue } val nextLeftInRight = right.subList(index, right.size) .indexOfOrMaxInt(leftEl) val nextRightInLeft = left.subList(index, left.size) .indexOfOrMaxInt(rightEl) if (nextLeftInRight < nextRightInLeft) { left.add(index, rightEl) } else if (nextRightInLeft < nextLeftInRight) { right.add(index, leftEl) } else { index++ } } return if (left.size < right.size) right else left } fun isValidCycle(longList: List, cycle: List): Boolean { for ((i, value) in longList.withIndex()) { if (cycle.getMod(i) != value) return false } return true } fun List.shortenCycle(): List { for (i in (1..