Skip to content

Instantly share code, notes, and snippets.

@rasjonell
Last active October 6, 2023 11:05
Show Gist options
  • Save rasjonell/c11494888ab0cc660df7fb0bc27c3f82 to your computer and use it in GitHub Desktop.
Save rasjonell/c11494888ab0cc660df7fb0bc27c3f82 to your computer and use it in GitHub Desktop.
Collaborative Position Generator in Kotlin, based on: https://github.com/mweidner037/position-strings
const val LAST_INTERNAL = "~"
class PositionGenerator(private val ID: String) {
companion object {
const val FIRST: String = ""
const val LAST: String = LAST_INTERNAL
}
private val longName: String = ",$ID."
private val firstName: String = "$ID."
private val lastValueSeqs: MutableMap<String, Int> = mutableMapOf()
fun createBetween(left: String = FIRST, right: String = LAST): String {
require(left < right) { "left must be less than right: $left !< $right" }
require(right <= LAST) { "right must be less than or equal to LAST: $right !<= $LAST" }
val leftFixed = if (left == FIRST) null else left
val rightFixed = if (right == LAST) null else right
val ans: String = when {
rightFixed != null && (leftFixed == null || rightFixed.startsWith(leftFixed)) -> {
val ancestor = leftVersion(rightFixed)
appendWaypoint(ancestor)
}
leftFixed == null -> appendWaypoint("")
else -> {
val prefix = getPrefix(leftFixed)
val lastValueSeq = lastValueSeqs[prefix]
if (lastValueSeq != null && !(rightFixed != null && rightFixed.startsWith(prefix))) {
val valueSeq = nextOddValueSeq(lastValueSeq)
"$prefix${stringifyBase52(valueSeq)}".also { lastValueSeqs[prefix] = valueSeq }
} else {
appendWaypoint(leftFixed)
}
}
}
require(left < ans && ans < right) { "Bad position: $left $ans $right" }
return ans
}
private fun appendWaypoint(ancestor: String): String {
var waypointName = if (ancestor.isEmpty()) firstName else longName
val existing = if (ancestor.startsWith(firstName)) 0 else ancestor.lastIndexOf(longName)
if (existing != -1) {
val index = ancestor.substring(existing).count { it == '.' }
waypointName = stringifyShortName(index)
}
val prefix = "$ancestor$waypointName"
val lastValueSeq = lastValueSeqs[prefix]
val valueSeq = lastValueSeq?.let { nextOddValueSeq(it) } ?: 1
lastValueSeqs[prefix] = valueSeq
return "$prefix${stringifyBase52(valueSeq)}"
}
}
fun precond(statement: Boolean, message: String, vararg optionalParams: Any) {
require(statement) {
if (optionalParams.isEmpty()) message else "$message ${optionalParams.joinToString(" ") { it.toString() }}"
}
}
fun assert(statement: Boolean, message: String? = null, vararg optionalParams: Any) {
if (!statement) {
if (message == null) {
precond(statement, "Assertion failed", *optionalParams)
} else {
precond(statement, "Assertion failed: $message", *optionalParams)
}
}
}
fun validateID(ID: String) {
precond(ID < LAST_INTERNAL, "ID must be less than $LAST_INTERNAL: $ID")
precond(!ID.contains(','), "ID must not contain ',': $ID")
precond(!ID.contains('.'), "ID must not contain '.': $ID")
}
fun getPrefix(position: String): String {
for (i in position.length - 2 downTo 0) {
val char = position[i]
if (char == '.' || (char in '0'..'9')) {
return position.substring(0, i + 1)
}
}
assert(false, "No last waypoint char found (not a position?)", position)
return ""
}
fun stringifyBase52(n: Int): String {
if (n == 0) return "A"
val codes = mutableListOf<Int>()
var num = n
while (num > 0) {
val digit = num % 52
codes.add(0, if (digit >= 26) 71 + digit else 65 + digit)
num /= 52
}
return codes.joinToString("") { it.toChar().toString() }
}
fun parseBase52(s: Char): Int {
return s.code - if (s.code >= 97) 71 else 65
}
fun leftVersion(position: String): String {
val last = parseBase52(position[position.length - 1])
assert(last % 2 == 1, "Bad valueSeq (not a position?)", last, position)
return "${position.dropLast(1)}${stringifyBase52(last - 1)}"
}
fun stringifyShortName(n: Int): String {
return if (n < 10) (48 + n).toChar().toString()
else "${stringifyBase52(n / 10)}${(48 + (n % 10)).toChar()}"
}
fun nextOddValueSeq(n: Int): Int {
val d = if (n == 0) 1 else (Math.log(n.toDouble()) / Math.log(52.0)).toInt() + 1
return if (n.toDouble() == Math.pow(52.0, d.toDouble()) - Math.pow(26.0, d.toDouble()) - 1) {
(n + 1) * 52 + 1
} else {
n + 2
}
}
fun main() {
var gen = PositionGenerator("1")
val res = mutableListOf<String>(gen.createBetween())
for (i in 1..10000) {
var pos = gen.createBetween(res.last())
res.add(pos)
}
println(res)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment