KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/util/random/RandomUtils.kt

252 lines
6.9 KiB
Kotlin

package ru.dbotthepony.kstarbound.util.random
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import it.unimi.dsi.fastutil.bytes.ByteConsumer
import org.classdump.luna.ByteString
import ru.dbotthepony.kommons.util.IStruct2d
import ru.dbotthepony.kommons.util.IStruct2i
import ru.dbotthepony.kommons.util.XXHash32
import ru.dbotthepony.kommons.util.XXHash64
import java.util.*
import java.util.random.RandomGenerator
import java.util.stream.IntStream
import kotlin.math.ceil
import kotlin.math.floor
import kotlin.math.ln
import kotlin.math.sqrt
/**
* Decouples implementation from semantics.
* Code should construct random generator using this function.
* Replacing generator returned here will affect all random generation code.
*/
fun random(seed: Long = System.nanoTime()): RandomGenerator {
return MWCRandom(seed.toULong())
}
private fun toBytes(accept: ByteConsumer, value: Short) {
accept.accept(value.toByte())
accept.accept((value.toInt() ushr 8).toByte())
}
private fun toBytes(accept: ByteConsumer, value: Int) {
accept.accept(value.toByte())
accept.accept((value ushr 8).toByte())
accept.accept((value ushr 16).toByte())
accept.accept((value ushr 24).toByte())
}
private fun toBytes(accept: ByteConsumer, value: Long) {
accept.accept(value.toByte())
accept.accept((value ushr 8).toByte())
accept.accept((value ushr 16).toByte())
accept.accept((value ushr 24).toByte())
accept.accept((value ushr 32).toByte())
accept.accept((value ushr 40).toByte())
accept.accept((value ushr 48).toByte())
accept.accept((value ushr 56).toByte())
}
private fun toBytes(accept: ByteConsumer, value: Double) {
toBytes(accept, value.toBits())
}
private fun toBytes(accept: ByteConsumer, value: Float) {
toBytes(accept, value.toBits())
}
fun staticRandom32(vararg values: Any?): Int {
val digest = XXHash32(2938728349.toInt())
for (value in values) {
when (value) {
is String -> digest.update(value.toByteArray())
is ByteString -> digest.update(value.bytes)
is Byte -> digest.update(value)
is Boolean -> digest.update(if (value) 1 else 0)
is Short -> toBytes(digest::update, value)
is Int -> toBytes(digest::update, value)
is Long -> toBytes(digest::update, value)
is Double -> toBytes(digest::update, value)
is Float -> toBytes(digest::update, value)
null -> {} // do nothing?
else -> throw IllegalArgumentException("Can't hash value of type ${value::class.qualifiedName}")
}
}
return digest.digestAsInt()
}
fun staticRandomFloat(vararg values: Any?): Float {
return staticRandom32(*values).ushr(8) * 5.9604645E-8f
}
fun staticRandomDouble(vararg values: Any?): Double {
return staticRandom64(*values).ushr(11) * 1.1102230246251565E-16
}
fun staticRandomInt(min: Int, max: Int, vararg values: Any?): Int {
val hash = staticRandomDouble(*values)
return (min + (max - min + 1) * hash).toInt()
}
fun staticRandomLong(min: Long, max: Long, vararg values: Any?): Long {
val hash = staticRandomDouble(*values)
return (min + (max - min + 1L) * hash).toLong()
}
fun staticRandom64(vararg values: Any?): Long {
val digest = XXHash64(1997293021376312589L)
for (value in values) {
when (value) {
is String -> digest.update(value.toByteArray())
is ByteString -> digest.update(value.bytes)
is Byte -> digest.update(value)
is Boolean -> digest.update(if (value) 1 else 0)
is Short -> toBytes(digest::update, value)
is Int -> toBytes(digest::update, value)
is Long -> toBytes(digest::update, value)
is Double -> toBytes(digest::update, value)
is Float -> toBytes(digest::update, value)
null -> {} // do nothing?
else -> throw IllegalArgumentException("Can't hash value of type ${value::class.qualifiedName}")
}
}
return digest.digestAsLong()
}
// normal distribution via Box-Muller
fun RandomGenerator.nextNormalFloat(stddev: Float, mean: Float): Float {
var rand1: Float
var rand2: Float
var distSqr: Float
do {
rand1 = 2f * nextFloat() - 1f
rand2 = 2f * nextFloat() - 1f
distSqr = rand1 * rand1 + rand2 * rand2
} while (distSqr >= 1)
val mapping = sqrt(-2f * ln(distSqr) / distSqr)
return rand1 * mapping * stddev + mean
}
// normal distribution via Box-Muller
fun RandomGenerator.nextNormalDouble(stddev: Double, mean: Double): Double {
var rand1: Double
var rand2: Double
var distSqr: Double
do {
rand1 = 2.0 * nextDouble() - 1.0
rand2 = 2.0 * nextDouble() - 1.0
distSqr = rand1 * rand1 + rand2 * rand2
} while (distSqr >= 1)
val mapping = sqrt(-2.0 * ln(distSqr) / distSqr)
return rand1 * mapping * stddev + mean
}
fun RandomGenerator.stochasticRound(value: Double): Long {
val part = value - floor(value)
if (nextDouble() < part)
return ceil(value).toLong()
else
return floor(value).toLong()
}
fun RandomGenerator.bytes(): IntStream {
// avoid flatMap since that would create lots of heap garbage
val data = IntArray(4)
var index = 0
var populated = false
return IntStream.generate {
if (!populated || index == 4) {
val value = nextInt()
data[0] = value.and(0xFF)
data[1] = value.ushr(8).and(0xFF)
data[2] = value.ushr(16).and(0xFF)
data[3] = value.ushr(24).and(0xFF)
populated = true
index = 0
}
data[index++]
}
}
fun RandomGenerator.nextBytes(buf: ByteArray, offset: Int, length: Int) {
Objects.checkFromIndexSize(offset, buf.size, length)
if (length == 0) return
var offset = offset
bytes().limit(length.toLong()).forEach {
buf[offset++] = it.toByte()
}
}
fun RandomGenerator.nextBytes(length: Int): ByteArray {
if (length == 0) return ByteArray(0)
require(length > 0) { "Invalid length: $length" }
val data = ByteArray(length)
var index = 0
bytes().limit(length.toLong()).forEach {
data[index++] = it.toByte()
}
return data
}
fun <T> Collection<T>.random(random: RandomGenerator): T {
if (isEmpty())
throw NoSuchElementException("List is empty")
return elementAt(random.nextInt(size))
}
fun JsonArray.random(random: RandomGenerator): JsonElement {
if (isEmpty())
throw NoSuchElementException("List is empty")
return elementAt(random.nextInt(size()))
}
fun <T> Collection<T>.random(random: RandomGenerator, default: () -> T): T {
if (isEmpty())
return default.invoke()
return elementAt(random.nextInt(size))
}
fun JsonArray.random(random: RandomGenerator, default: () -> JsonElement): JsonElement {
if (isEmpty())
return default.invoke()
return elementAt(random.nextInt(size()))
}
fun RandomGenerator.nextRange(range: IStruct2i): Int {
return if (range.component1() == range.component2()) return range.component1() else nextInt(range.component1(), range.component2())
}
fun RandomGenerator.nextRange(range: IStruct2d): Double {
return if (range.component1() == range.component2()) return range.component1() else nextDouble(range.component1(), range.component2())
}
fun <T> MutableList<T>.shuffle(random: RandomGenerator) {
for (i in 0 until size) {
val rand = random.nextInt(size)
val a = this[i]
val b = this[rand]
this[i] = b
this[rand] = a
}
}