8.2 KiB
8.2 KiB
In [ ]:
%use plotly
API¶
In [ ]:
enum class Severity(val penalty: Double){ MINOR(1.0), MAJOR(2.0), CRITICAL(3.0) } enum class State{ OPEN, ASSIGNED, RESOLVED } data class Issue(val id: String, val dayCreated: Int, val severity: Severity, val complexity: Int, var state: State = State.OPEN, var dayAssigned: Int? = null, var dayResolved: Int? = null){ fun activate(day: Int){ state = State.ASSIGNED dayAssigned = day } fun resolve(day: Int){ state = State.RESOLVED dayResolved = day } internal fun tryResolve(day: Int){ if(state == State.ASSIGNED && day >= (dayAssigned ?: 0) + complexity ){ resolve(day) } } } class Worker(val name: String){ var currentIssue: Issue? = null private set fun isBusy(): Boolean = currentIssue != null fun update(day: Int){ currentIssue?.tryResolve(day) if(currentIssue?.state == State.RESOLVED){ currentIssue = null } } fun assign(day: Int, issue: Issue){ if(currentIssue != null) error("Can't assign work to a worker which is busy") issue.activate(day) currentIssue = issue } } interface IssueGenerator{ fun generate(day: Int): List<Issue> } interface Strategy{ fun selectIssue(day: Int, issues: List<Issue>): Issue? } class WorkResult(val issues: List<Issue>, val workers: Int, val days: Int) @OptIn(kotlin.ExperimentalStdlibApi::class) fun simulate(generator: IssueGenerator, strategy: Strategy, numWorkers: Int = 10, days: Int = 100): WorkResult{ val workers = (0 until numWorkers).map{Worker("worker $it")} val issues = buildList<Issue>{ for(day in 0 until days){ //update all workers workers.forEach { it.update(day) } //generate new issues val newIssues = generator.generate(day) addAll(newIssues) //Select all free workers workers.filter { !it.isBusy() }.forEach { worker-> val unasigned = filter { it.state == State.OPEN } val anIssue = strategy.selectIssue(day, unasigned) //select an issue to assign from all unassigned issues if(anIssue != null){ worker.assign(day, anIssue) } } } } return WorkResult(issues, numWorkers, days) } fun WorkResult.computeLoss(): Double = issues.sumByDouble { ((it.dayResolved ?: days) - it.dayCreated)*it.severity.penalty } / days / workers / issues.size
Implementations¶
In [ ]:
import kotlin.random.Random import kotlin.math.pow /** * Generate one random issue per day */ class RandomIssueGenerator(seed: Long, val issuesPerDay: Int = 4 ) : IssueGenerator{ private val random = Random(seed) override fun generate(day: Int): List<Issue>{ return List(issuesPerDay){ val severity = Severity.values()[random.nextInt(3)] val complexity = random.nextInt(15) Issue("${day}_${it}", day, severity, complexity) } } } object TakeOldest: Strategy{ override fun selectIssue(day: Int, issues: List<Issue>): Issue?{ return issues.minByOrNull { it.dayCreated } } } class TakeRandom(seed: Long): Strategy{ private val random = Random(seed) override fun selectIssue(day: Int, issues: List<Issue>): Issue?{ if(issues.isEmpty()) return null return issues.random(random) } } object TakeCritical: Strategy{ override fun selectIssue(day: Int, issues: List<Issue>): Issue?{ return issues.maxByOrNull { it.severity.penalty*(day - it.dayCreated) } } }
Simulate lossseverity¶
In [ ]:
val seed = 89L val days = 100 val workers = 10
Take oldest¶
In [ ]:
val result = simulate(RandomIssueGenerator(seed, workers),TakeOldest, days = days) //result.issues.forEach { println(it)} result.computeLoss()
Take random¶
In [ ]:
simulate(RandomIssueGenerator(seed, workers),TakeRandom(seed), days = days).computeLoss()
Take critical¶
In [ ]:
simulate(RandomIssueGenerator(seed, workers), TakeCritical, days = days).computeLoss()
In [ ]:
val seeds = List(1000){Random.nextLong()} Plotly.plot{ trace{ x.numbers = seeds.map{ seed -> simulate(RandomIssueGenerator(seed, workers), TakeOldest, days = days).computeLoss()} name = "oldest" type = TraceType.histogram } trace{ x.numbers = seeds.map{ seed -> simulate(RandomIssueGenerator(seed, workers), TakeRandom(seed), days = days).computeLoss()} name = "random" type = TraceType.histogram } trace{ x.numbers = seeds.map{ seed -> simulate(RandomIssueGenerator(seed, workers), TakeCritical, days = days).computeLoss()} name = "critical" type = TraceType.histogram } layout{ title = "Loss distribtution" xaxis { title = "Loss" } } }
In [ ]:
In [ ]: