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