Last active
October 6, 2023 11:05
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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