Last active
September 28, 2018 03:08
-
-
Save nbness2/2a9d2076e69a56a91b4df5f8e035a140 to your computer and use it in GitHub Desktop.
Kotlin RandomUtil
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
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 | |
} |
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
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) | |
} |
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
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) | |
} |
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
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") | |
} |
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
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