Skip to content

Instantly share code, notes, and snippets.

@nbness2
Last active September 28, 2018 03:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nbness2/2a9d2076e69a56a91b4df5f8e035a140 to your computer and use it in GitHub Desktop.
Save nbness2/2a9d2076e69a56a91b4df5f8e035a140 to your computer and use it in GitHub Desktop.
Kotlin RandomUtil
package KotlinSrc
import java.util.*
import kotlin.reflect.KCallable
import kotlin.reflect.KType
import kotlin.reflect.full.createType
abstract class BaseRandom {
private val getNextByType: HashMap<KType, KCallable<*>> = HashMap()
private val _random: Random
private fun Boolean.toInt(): Int = if (this) 1 else 0
internal fun nextByte(random: Random, lowerBound: Byte, upperBound: Byte, inclusive: Boolean): Byte {
return ((lowerBound-1) + (random.nextDouble() * (upperBound - (lowerBound-1) + inclusive.toInt()))).toByte()
}
internal fun nextShort(random: Random, lowerBound: Short, upperBound: Short, inclusive: Boolean): Short {
return ((lowerBound-1) + (random.nextDouble() * (upperBound - (lowerBound-1) + inclusive.toInt()))).toShort()
}
internal fun nextInt(random: Random, lowerBound: Int, upperBound: Int, inclusive: Boolean): Int {
return lowerBound + (random.nextDouble() * (upperBound - lowerBound + inclusive.toInt())).toInt()
}
internal fun nextLong(random: Random, lowerBound: Long, upperBound: Long, inclusive: Boolean): Long {
return (lowerBound + (random.nextDouble() * (upperBound - lowerBound + inclusive.toInt()))).toLong()
}
internal fun nextFloat(random: Random, lowerBound: Float, upperBound: Float, inclusive: Boolean): Float {
return lowerBound + (random.nextDouble() * (upperBound - lowerBound + (inclusive.toInt() * Float.MIN_VALUE))).toFloat()
}
internal fun nextDouble(random: Random, lowerBound: Double, upperBound: Double, inclusive: Boolean): Double {
return lowerBound + ((random.nextDouble() + Double.MIN_VALUE) * (upperBound - lowerBound + (inclusive.toInt() * Double.MIN_VALUE)))
}
constructor(): this(Random())
constructor(random: Random){
this._random = random
for (callable in this::class.members)
if (callable.name.contains("next"))
this.getNextByType[callable.returnType] = callable
}
public fun <T> random(lowerBound: T, upperBound: T, inclusive: Boolean, random: Random?=null): T where T: Number, T: Comparable<T> {
val selectedRandom = random ?: this._random
val selectedCallable = this.getNextByType[lowerBound::class.createType()] ?: throw NullPointerException("next function does not exist for ${lowerBound::class.qualifiedName}")
return selectedCallable.call(this, selectedRandom, minOf(lowerBound, upperBound), maxOf(lowerBound, upperBound), inclusive) as T
}
abstract fun copyOf(): BaseRandom
}
package KotlinSrc
class RandomRange <T>: BaseRandom where T: Number, T: Comparable<T>{
private val inclusive: Boolean
private val minimum: T
private val maximum: T
fun pick(inclusive: Boolean): T = this.random(this.minimum, this.maximum, inclusive)
fun pick(): T = this.pick(this.inclusive)
constructor(minimum: T, maximum: T, inclusive: Boolean=false): super() {
this.minimum = minOf(minimum, maximum)
this.maximum = maxOf(minimum, maximum)
this.inclusive = inclusive
}
override fun copyOf(): RandomRange<T> = RandomRange(this.minimum, this.maximum, this.inclusive)
}
package KotlinSrc
open class RandomTable <T>: BaseRandom {
private val items: Array<T>
private val resultsMap: HashMap<T, Int> = HashMap()
private val resultsList: MutableList<T> = mutableListOf()
operator fun get(index: Int): T { //pythony indexing
var finalIndex = index
if (finalIndex > this.items.size-1 || finalIndex < -this.items.size)
throw ArrayIndexOutOfBoundsException("Index ($finalIndex) out of range for length: ${this.items.size}")
finalIndex += if (finalIndex < 0) this.items.size else 0
return this.items[finalIndex]
}
operator fun get(item: T): Int {
if (item !in this.items)
throw ArrayStoreException("Item $item not contained.")
return this.items.indexOf(item)
}
constructor(items: Array<T>) { this.items = items.copyOf() }
protected fun sortItemsBy(other: IntArray) {
fun IntArray.sortedByOther(other: IntArray): Pair<IntArray, IntArray> {
if (this.size != other.size)
throw ArrayStoreException("sortedByOther: this and other must be same size")
val sortedArray: Array<Pair<Int, Int>> =
Array(this.size) { idx -> this[idx] to other[idx]}
sortedArray.sortBy{it -> it.second}
return sortedArray.map { it -> it.first }.toIntArray() to sortedArray.map{ it -> it.second }.toIntArray()
}
val prev = this.items.clone()
val indices = IntArray(other.size){i -> i}
for ((newIndex, oldIndex) in indices.sortedByOther(other).first.withIndex())
this.items[newIndex] = prev[oldIndex]
other.sort()
}
fun pick(modifier: Int=0): T = this.pickInternal(modifier)
fun pickMap(pickAmount: Int, modifier: Int=0): HashMap<T, Int> {
this.resultsMap.clear()
var result: T
var previousCount: Int
for (run in 0 until pickAmount) {
result = this.pickInternal(modifier)
this.resultsMap.putIfAbsent(result, 0)
previousCount = this.resultsMap[result] as Int
this.resultsMap[result] = previousCount + 1
}
return this.resultsMap.clone() as HashMap<T, Int>
}
fun pickOrdered(pickAmount: Int, modifier: Int=0): List<T> {
this.resultsList.clear()
for (run in 0 until pickAmount)
this.resultsList.add(this.pickInternal(modifier))
return this.resultsList.toList()
}
internal open fun pickInternal(modifier: Int=0): T = this.items[this.random(0, this.items.size, false)]
override fun copyOf(): RandomTable<T> = RandomTable(this.items)
}
package KotlinSrc
class TestBR: BaseRandom() { override fun copyOf(): TestBR = TestBR() }
fun main(args: Array<String>) {
val min = -250_000
val max = 250_000
val inclusive = false
val pickAmount = 100_000
val items = arrayOf(
"String1", "String2", "String3", "String4", "String5", "String6", "String7", "String8"
)
val weights = intArrayOf(
5, 6, 1, 4, 25, 8, 10, 2
)
BRTest(min, max, inclusive)
println()
RRTest(min, max, !inclusive)
println()
RTTest(items, pickAmount)
println()
WTTest(items, weights, pickAmount)
}
private fun BRTest(min: Int, max: Int, inclusive: Boolean) {
val br = TestBR()
var result: Int
var counter = 0
var isMin = false
var isMax = false
val inclusiveMax: Int = (max - (if (inclusive) 0 else 1))
println("Starting BaseRandom test from $min to $max ${if (inclusiveMax == max ) "" else "not"} inclusive")
while (!isMin || !isMax) {
result = br.random(min, max, inclusive)
counter++
if (!isMin && result == min) {
println("BaseRandom min ($min) picked at $counter picks")
isMin = true
} else if (!isMax && result == inclusiveMax) {
println("BaseRandom max ($inclusiveMax) picked at $counter picks")
isMax = true
} else if (result > inclusiveMax || result < min) {
println("Result: $result out of range")
}
}
println("BaseRandom finished at $counter picks")
}
private fun RRTest(min: Int, max: Int, inclusive: Boolean) {
val rr = RandomRange(min, max, inclusive)
var result: Int
var counter = 0
var isMin = false
var isMax = false
val inclusiveMax: Int = (max - (if (inclusive) 0 else 1))
println("Starting RandomRange test from $min to $max ${if (inclusiveMax == max ) "" else "not"} inclusive")
while (!isMin || !isMax) {
result = rr.pick()
counter++
if (!isMin && result == min) {
println("RandomRange min ($min) picked at $counter picks")
isMin = true
} else if (!isMax && result == inclusiveMax) {
println("RandomRange max ($inclusiveMax) picked at $counter picks")
isMax = true
} else if (result > inclusiveMax || result < min) {
println("Result: $result out of range")
}
}
println("RandomRange finished at $counter picks")
}
private fun <T> RTTest(items: Array<T>, pickAmount: Int) {
val rt = RandomTable(items)
println("Starting RandomTable test with items:\n\t${items.contentDeepToString()}")
println("Each item has a ${100.0 / items.size}% chance to be picked")
val results = rt.pickMap(pickAmount)
println("RandomTable finished, results over $pickAmount picks\n\t$results")
}
private fun <T> WTTest(items: Array<T>, weights: IntArray, pickAmount: Int) {
val wt = WeightedTable(items, weights)
println("Starting WeightedTable test with items:\n\t${items.contentDeepToString()}\n\tand weights:\n\t${weights.contentToString()}\n\ttotal weight: ${wt.totalWeight}")
val results = wt.pickMap(pickAmount)
val expected = HashMap<T, Double>()
for (key in results.keys)
expected.putIfAbsent(key, wt.chanceOf(key))
println("Expected chances\n\t$expected")
println("Actual results out of $pickAmount picks\n\t$results")
}
package KotlinSrc
class WeightedTable<T>: RandomTable<T> {
private val weights: IntArray
val totalWeight: Long
private val weightRange: RandomRange<Long>
constructor(items: Array<T>, weights: IntArray): super(items) {
weights.forEach{ weight ->
if (weights.count { it == weight} > 1)
throw ArrayStoreException("Cannot have more than 1 item per weight in ${this::class.simpleName}")
else if (weight < 1)
throw ArrayStoreException("Weight cannot be less than 1 in ${this::class.simpleName}")
}
this.weights = weights
this.sortItemsBy(weights)
var finalWeight: Long = 0
for (weight in weights) {
finalWeight += weight
}
this.totalWeight = finalWeight
this.weightRange = RandomRange(1L, this.totalWeight, true)
}
fun chanceOf(item: T): Double {
val selectedWeight = this.weights[this[item]]
return (selectedWeight.toDouble() * 100) / this.totalWeight
}
override fun pickInternal(modifier: Int): T {
var pickedWeight = this.weightRange.pick() * (1.0 / (1 + (modifier.toDouble() / 100)))
for ((weightIndex, weightValue) in this.weights.withIndex()) {
pickedWeight -= weightValue
if (pickedWeight <= 0) return this[weightIndex]
}
return this[-1]
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment