package com.seanshubin.kotlin.tryme.domain.dice

object SpheresOfInfluenceDiceStatisticsApp {
    fun List<Int>.incrementDieRoll(faces: Int): List<Int> {
        val newValues = mutableListOf<Int>()
        var carry = true
        for (currentIndex in size - 1 downTo 0) {
            val value = get(currentIndex)
            if (carry) {
                if (value == faces) {
                    newValues.add(1)
                } else {
                    newValues.add(value + 1)
                    carry = false
                }
            } else {
                newValues.add(value)
            }
        }
        newValues.reverse()
        return newValues
    }

    fun allRolls(quantity: Int, faces: Int): List<List<Int>> {
        val startingValue = (1..quantity).map { 1 }
        var currentValue = startingValue
        val results = mutableListOf(currentValue)
        currentValue = currentValue.incrementDieRoll(faces)
        while (currentValue != startingValue) {
            results.add(currentValue)
            currentValue = currentValue.incrementDieRoll(faces)
        }
        return results
    }

    fun List<Int>.extractAtIndex(index: Int): Pair<Int, List<Int>> {
        val value = get(index)
        val before = subList(0, index)
        val after = subList(index + 1, size)
        val remain = before + after
        return Pair(value, remain)
    }

    fun List<Int>.permutations(): List<List<Int>> {
        if (size < 2) return listOf(this)
        val allPermutations = mutableListOf<List<Int>>()
        for (index in this.indices) {
            val (atIndex, remain) = extractAtIndex(index)
            remain.permutations().forEach {
                val permutation = listOf(atIndex) + it
                allPermutations.add(permutation)
            }
        }
        return allPermutations
    }

    fun List<Int>.hitsForRoll(): Int {
        var hits = 0
        var accumulator = 0
        for (index in indices) {
            val current = get(index)
            accumulator += current
            if (accumulator >= 6) {
                hits++
                accumulator = 0
            }
        }
        return hits
    }

    fun List<Int>.maxHitsForRoll(): Int {
        val scores: List<Int> = permutations().map { it.hitsForRoll() }
        return scores.max() ?: throw RuntimeException("unable to compute maximum of $scores")
    }

    fun List<List<Int>>.scoreHistogram(): Map<Int, Int> {
        val histogram = mutableMapOf<Int, Int>()
        forEach {
            val score = it.maxHitsForRoll()
            histogram[score] = (histogram[score] ?: 0) + 1
        }
        return histogram
    }

    fun Int.scoreHistogram(): Map<Int, Int> {
        val allRolls = allRolls(this, 6)
        return allRolls.scoreHistogram()
    }

    fun pluralize(quantity: Int, singular: String, plural: String): String {
        return if (quantity == 1) singular else plural
    }

    fun <T> List<List<T>>.flattenWithSpacer(spacer: T): List<T> =
            map { listOf(spacer) + it }.flatten().drop(1)

    data class HistogramForQuantity(val quantity: Int, val histogram: Map<Int, Int>) {
        fun summary(): List<String> {
            val total = histogram.values.sum()
            val keys = histogram.keys.sorted()
            val diceString = pluralize(quantity, "die", "dice")
            val caption = "$quantity $diceString"
            val summaries = keys.map {
                val possible = histogram.getValue(it)
                val percent = possible.toDouble() / total.toDouble() * 100.0
                val hitString = pluralize(it, "hit", "hits")
                val summary = "$possible/$total (%.2f%%) chance of $it $hitString".format(percent)
                summary
            }
            return listOf(caption) + summaries
        }
    }

    @JvmStatic
    fun main(args: Array<String>) {
        val header = listOf(
                "1 hit per grouping of dice that sums to 6 or greater",
                ""
        )
        val body = (1..5).map {
            HistogramForQuantity(it, it.scoreHistogram())
        }.map {
            it.summary()
        }.flattenWithSpacer("")
        val lines = header + body
        lines.forEach(::println)
    }
}
/*
1 hit per grouping of dice that sums to 6 or greater

1 die
5/6 (83.33%) chance of 0 hits
1/6 (16.67%) chance of 1 hit

2 dice
10/36 (27.78%) chance of 0 hits
25/36 (69.44%) chance of 1 hit
1/36 (2.78%) chance of 2 hits

3 dice
10/216 (4.63%) chance of 0 hits
145/216 (67.13%) chance of 1 hit
60/216 (27.78%) chance of 2 hits
1/216 (0.46%) chance of 3 hits

4 dice
5/1296 (0.39%) chance of 0 hits
333/1296 (25.69%) chance of 1 hit
847/1296 (65.35%) chance of 2 hits
110/1296 (8.49%) chance of 3 hits
1/1296 (0.08%) chance of 4 hits

5 dice
1/7776 (0.01%) chance of 0 hits
456/7776 (5.86%) chance of 1 hit
4258/7776 (54.76%) chance of 2 hits
2885/7776 (37.10%) chance of 3 hits
175/7776 (2.25%) chance of 4 hits
1/7776 (0.01%) chance of 5 hits
*/

Compiled from "SpheresOfInfluenceDiceStatisticsApp.kt"
public final class com.seanshubin.kotlin.tryme.domain.dice.SpheresOfInfluenceDiceStatisticsApp {
  public static final com.seanshubin.kotlin.tryme.domain.dice.SpheresOfInfluenceDiceStatisticsApp INSTANCE;

  private com.seanshubin.kotlin.tryme.domain.dice.SpheresOfInfluenceDiceStatisticsApp();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public final java.util.List<java.lang.Integer> incrementDieRoll(java.util.List<java.lang.Integer>, int);
    Code:
       0: aload_1
       1: ldc           #16                 // String <this>
       3: invokestatic  #22                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: new           #24                 // class java/util/ArrayList
       9: dup
      10: invokespecial #25                 // Method java/util/ArrayList."<init>":()V
      13: checkcast     #27                 // class java/util/List
      16: astore_3
      17: iconst_1
      18: istore        4
      20: aload_1
      21: invokeinterface #31,  1           // InterfaceMethod java/util/List.size:()I
      26: iconst_1
      27: isub
      28: istore        5
      30: iconst_m1
      31: iload         5
      33: if_icmpge     115
      36: aload_1
      37: iload         5
      39: invokeinterface #35,  2           // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
      44: checkcast     #37                 // class java/lang/Number
      47: invokevirtual #40                 // Method java/lang/Number.intValue:()I
      50: istore        6
      52: iload         4
      54: ifeq          97
      57: iload         6
      59: iload_2
      60: if_icmpne     77
      63: aload_3
      64: iconst_1
      65: invokestatic  #46                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      68: invokeinterface #50,  2           // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      73: pop
      74: goto          109
      77: aload_3
      78: iload         6
      80: iconst_1
      81: iadd
      82: invokestatic  #46                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      85: invokeinterface #50,  2           // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      90: pop
      91: iconst_0
      92: istore        4
      94: goto          109
      97: aload_3
      98: iload         6
     100: invokestatic  #46                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     103: invokeinterface #50,  2           // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
     108: pop
     109: iinc          5, -1
     112: goto          30
     115: aload_3
     116: invokestatic  #56                 // Method kotlin/collections/CollectionsKt.reverse:(Ljava/util/List;)V
     119: aload_3
     120: areturn

  public final java.util.List<java.util.List<java.lang.Integer>> allRolls(int, int);
    Code:
       0: new           #70                 // class kotlin/ranges/IntRange
       3: dup
       4: iconst_1
       5: iload_1
       6: invokespecial #73                 // Method kotlin/ranges/IntRange."<init>":(II)V
       9: checkcast     #75                 // class java/lang/Iterable
      12: astore        4
      14: iconst_0
      15: istore        5
      17: aload         4
      19: astore        6
      21: new           #24                 // class java/util/ArrayList
      24: dup
      25: aload         4
      27: bipush        10
      29: invokestatic  #79                 // Method kotlin/collections/CollectionsKt.collectionSizeOrDefault:(Ljava/lang/Iterable;I)I
      32: invokespecial #82                 // Method java/util/ArrayList."<init>":(I)V
      35: checkcast     #84                 // class java/util/Collection
      38: astore        7
      40: iconst_0
      41: istore        8
      43: aload         6
      45: invokeinterface #88,  1           // InterfaceMethod java/lang/Iterable.iterator:()Ljava/util/Iterator;
      50: astore        9
      52: aload         9
      54: invokeinterface #94,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z
      59: ifeq          99
      62: aload         9
      64: checkcast     #96                 // class kotlin/collections/IntIterator
      67: invokevirtual #99                 // Method kotlin/collections/IntIterator.nextInt:()I
      70: istore        10
      72: aload         7
      74: iload         10
      76: istore        11
      78: astore        13
      80: iconst_0
      81: istore        12
      83: iconst_1
      84: invokestatic  #46                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      87: aload         13
      89: swap
      90: invokeinterface #100,  2          // InterfaceMethod java/util/Collection.add:(Ljava/lang/Object;)Z
      95: pop
      96: goto          52
      99: aload         7
     101: checkcast     #27                 // class java/util/List
     104: nop
     105: astore_3
     106: aload_3
     107: astore        4
     109: iconst_1
     110: anewarray     #27                 // class java/util/List
     113: astore        6
     115: aload         6
     117: iconst_0
     118: aload         4
     120: aastore
     121: aload         6
     123: invokestatic  #104                // Method kotlin/collections/CollectionsKt.mutableListOf:([Ljava/lang/Object;)Ljava/util/List;
     126: astore        5
     128: aload_0
     129: aload         4
     131: iload_2
     132: invokevirtual #106                // Method incrementDieRoll:(Ljava/util/List;I)Ljava/util/List;
     135: astore        4
     137: aload         4
     139: aload_3
     140: invokestatic  #110                // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z
     143: ifne          168
     146: aload         5
     148: aload         4
     150: invokeinterface #50,  2           // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
     155: pop
     156: aload_0
     157: aload         4
     159: iload_2
     160: invokevirtual #106                // Method incrementDieRoll:(Ljava/util/List;I)Ljava/util/List;
     163: astore        4
     165: goto          137
     168: aload         5
     170: areturn

  public final kotlin.Pair<java.lang.Integer, java.util.List<java.lang.Integer>> extractAtIndex(java.util.List<java.lang.Integer>, int);
    Code:
       0: aload_1
       1: ldc           #16                 // String <this>
       3: invokestatic  #22                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_1
       7: iload_2
       8: invokeinterface #35,  2           // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
      13: checkcast     #37                 // class java/lang/Number
      16: invokevirtual #40                 // Method java/lang/Number.intValue:()I
      19: istore_3
      20: aload_1
      21: iconst_0
      22: iload_2
      23: invokeinterface #132,  3          // InterfaceMethod java/util/List.subList:(II)Ljava/util/List;
      28: astore        4
      30: aload_1
      31: iload_2
      32: iconst_1
      33: iadd
      34: aload_1
      35: invokeinterface #31,  1           // InterfaceMethod java/util/List.size:()I
      40: invokeinterface #132,  3          // InterfaceMethod java/util/List.subList:(II)Ljava/util/List;
      45: astore        5
      47: aload         4
      49: checkcast     #84                 // class java/util/Collection
      52: aload         5
      54: checkcast     #75                 // class java/lang/Iterable
      57: invokestatic  #136                // Method kotlin/collections/CollectionsKt.plus:(Ljava/util/Collection;Ljava/lang/Iterable;)Ljava/util/List;
      60: astore        6
      62: new           #138                // class kotlin/Pair
      65: dup
      66: iload_3
      67: invokestatic  #46                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      70: aload         6
      72: invokespecial #141                // Method kotlin/Pair."<init>":(Ljava/lang/Object;Ljava/lang/Object;)V
      75: areturn

  public final java.util.List<java.util.List<java.lang.Integer>> permutations(java.util.List<java.lang.Integer>);
    Code:
       0: aload_1
       1: ldc           #16                 // String <this>
       3: invokestatic  #22                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_1
       7: invokeinterface #31,  1           // InterfaceMethod java/util/List.size:()I
      12: iconst_2
      13: if_icmpge     21
      16: aload_1
      17: invokestatic  #153                // Method kotlin/collections/CollectionsKt.listOf:(Ljava/lang/Object;)Ljava/util/List;
      20: areturn
      21: new           #24                 // class java/util/ArrayList
      24: dup
      25: invokespecial #25                 // Method java/util/ArrayList."<init>":()V
      28: checkcast     #27                 // class java/util/List
      31: astore_2
      32: iconst_0
      33: istore_3
      34: aload_1
      35: checkcast     #84                 // class java/util/Collection
      38: invokeinterface #154,  1          // InterfaceMethod java/util/Collection.size:()I
      43: istore        4
      45: iload_3
      46: iload         4
      48: if_icmpge     176
      51: aload_0
      52: aload_1
      53: iload_3
      54: invokevirtual #156                // Method extractAtIndex:(Ljava/util/List;I)Lkotlin/Pair;
      57: astore        5
      59: aload         5
      61: invokevirtual #160                // Method kotlin/Pair.component1:()Ljava/lang/Object;
      64: checkcast     #37                 // class java/lang/Number
      67: invokevirtual #40                 // Method java/lang/Number.intValue:()I
      70: istore        6
      72: aload         5
      74: invokevirtual #163                // Method kotlin/Pair.component2:()Ljava/lang/Object;
      77: checkcast     #27                 // class java/util/List
      80: astore        7
      82: aload_0
      83: aload         7
      85: invokevirtual #165                // Method permutations:(Ljava/util/List;)Ljava/util/List;
      88: checkcast     #75                 // class java/lang/Iterable
      91: astore        8
      93: iconst_0
      94: istore        9
      96: aload         8
      98: invokeinterface #88,  1           // InterfaceMethod java/lang/Iterable.iterator:()Ljava/util/Iterator;
     103: astore        10
     105: aload         10
     107: invokeinterface #94,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z
     112: ifeq          169
     115: aload         10
     117: invokeinterface #168,  1          // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
     122: astore        11
     124: aload         11
     126: checkcast     #27                 // class java/util/List
     129: astore        12
     131: iconst_0
     132: istore        13
     134: iload         6
     136: invokestatic  #46                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     139: invokestatic  #153                // Method kotlin/collections/CollectionsKt.listOf:(Ljava/lang/Object;)Ljava/util/List;
     142: checkcast     #84                 // class java/util/Collection
     145: aload         12
     147: checkcast     #75                 // class java/lang/Iterable
     150: invokestatic  #136                // Method kotlin/collections/CollectionsKt.plus:(Ljava/util/Collection;Ljava/lang/Iterable;)Ljava/util/List;
     153: astore        14
     155: aload_2
     156: aload         14
     158: invokeinterface #50,  2           // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
     163: pop
     164: nop
     165: nop
     166: goto          105
     169: nop
     170: iinc          3, 1
     173: goto          45
     176: aload_2
     177: areturn

  public final int hitsForRoll(java.util.List<java.lang.Integer>);
    Code:
       0: aload_1
       1: ldc           #16                 // String <this>
       3: invokestatic  #22                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: iconst_0
       7: istore_2
       8: iconst_0
       9: istore_3
      10: iconst_0
      11: istore        4
      13: aload_1
      14: checkcast     #84                 // class java/util/Collection
      17: invokeinterface #154,  1          // InterfaceMethod java/util/Collection.size:()I
      22: istore        5
      24: iload         4
      26: iload         5
      28: if_icmpge     69
      31: aload_1
      32: iload         4
      34: invokeinterface #35,  2           // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
      39: checkcast     #37                 // class java/lang/Number
      42: invokevirtual #40                 // Method java/lang/Number.intValue:()I
      45: istore        6
      47: iload_3
      48: iload         6
      50: iadd
      51: istore_3
      52: iload_3
      53: bipush        6
      55: if_icmplt     63
      58: iinc          2, 1
      61: iconst_0
      62: istore_3
      63: iinc          4, 1
      66: goto          24
      69: iload_2
      70: ireturn

  public final int maxHitsForRoll(java.util.List<java.lang.Integer>);
    Code:
       0: aload_1
       1: ldc           #16                 // String <this>
       3: invokestatic  #22                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_0
       7: aload_1
       8: invokevirtual #165                // Method permutations:(Ljava/util/List;)Ljava/util/List;
      11: checkcast     #75                 // class java/lang/Iterable
      14: astore_3
      15: iconst_0
      16: istore        4
      18: aload_3
      19: astore        5
      21: new           #24                 // class java/util/ArrayList
      24: dup
      25: aload_3
      26: bipush        10
      28: invokestatic  #79                 // Method kotlin/collections/CollectionsKt.collectionSizeOrDefault:(Ljava/lang/Iterable;I)I
      31: invokespecial #82                 // Method java/util/ArrayList."<init>":(I)V
      34: checkcast     #84                 // class java/util/Collection
      37: astore        6
      39: iconst_0
      40: istore        7
      42: aload         5
      44: invokeinterface #88,  1           // InterfaceMethod java/lang/Iterable.iterator:()Ljava/util/Iterator;
      49: astore        8
      51: aload         8
      53: invokeinterface #94,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z
      58: ifeq          107
      61: aload         8
      63: invokeinterface #168,  1          // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
      68: astore        9
      70: aload         6
      72: aload         9
      74: checkcast     #27                 // class java/util/List
      77: astore        10
      79: astore        12
      81: iconst_0
      82: istore        11
      84: getstatic     #188                // Field INSTANCE:Lcom/seanshubin/kotlin/tryme/domain/dice/SpheresOfInfluenceDiceStatisticsApp;
      87: aload         10
      89: invokevirtual #190                // Method hitsForRoll:(Ljava/util/List;)I
      92: invokestatic  #46                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      95: aload         12
      97: swap
      98: invokeinterface #100,  2          // InterfaceMethod java/util/Collection.add:(Ljava/lang/Object;)Z
     103: pop
     104: goto          51
     107: aload         6
     109: checkcast     #27                 // class java/util/List
     112: nop
     113: astore_2
     114: aload_2
     115: checkcast     #75                 // class java/lang/Iterable
     118: invokestatic  #194                // Method kotlin/collections/CollectionsKt.maxOrThrow:(Ljava/lang/Iterable;)Ljava/lang/Comparable;
     121: checkcast     #37                 // class java/lang/Number
     124: invokevirtual #40                 // Method java/lang/Number.intValue:()I
     127: ireturn

  public final java.util.Map<java.lang.Integer, java.lang.Integer> scoreHistogram(java.util.List<? extends java.util.List<java.lang.Integer>>);
    Code:
       0: aload_1
       1: ldc           #16                 // String <this>
       3: invokestatic  #22                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: new           #202                // class java/util/LinkedHashMap
       9: dup
      10: invokespecial #203                // Method java/util/LinkedHashMap."<init>":()V
      13: checkcast     #205                // class java/util/Map
      16: astore_2
      17: aload_1
      18: checkcast     #75                 // class java/lang/Iterable
      21: astore_3
      22: iconst_0
      23: istore        4
      25: aload_3
      26: invokeinterface #88,  1           // InterfaceMethod java/lang/Iterable.iterator:()Ljava/util/Iterator;
      31: astore        5
      33: aload         5
      35: invokeinterface #94,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z
      40: ifeq          132
      43: aload         5
      45: invokeinterface #168,  1          // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
      50: astore        6
      52: aload         6
      54: checkcast     #27                 // class java/util/List
      57: astore        7
      59: iconst_0
      60: istore        8
      62: getstatic     #188                // Field INSTANCE:Lcom/seanshubin/kotlin/tryme/domain/dice/SpheresOfInfluenceDiceStatisticsApp;
      65: aload         7
      67: invokevirtual #207                // Method maxHitsForRoll:(Ljava/util/List;)I
      70: istore        9
      72: aload_2
      73: astore        10
      75: iload         9
      77: invokestatic  #46                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      80: astore        11
      82: aload_2
      83: iload         9
      85: invokestatic  #46                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      88: invokeinterface #210,  2          // InterfaceMethod java/util/Map.get:(Ljava/lang/Object;)Ljava/lang/Object;
      93: checkcast     #42                 // class java/lang/Integer
      96: dup
      97: ifnull        106
     100: invokevirtual #211                // Method java/lang/Integer.intValue:()I
     103: goto          108
     106: pop
     107: iconst_0
     108: iconst_1
     109: iadd
     110: invokestatic  #46                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     113: astore        12
     115: aload         10
     117: aload         11
     119: aload         12
     121: invokeinterface #215,  3          // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
     126: pop
     127: nop
     128: nop
     129: goto          33
     132: nop
     133: aload_2
     134: areturn

  public final java.util.Map<java.lang.Integer, java.lang.Integer> scoreHistogram(int);
    Code:
       0: aload_0
       1: iload_1
       2: bipush        6
       4: invokevirtual #224                // Method allRolls:(II)Ljava/util/List;
       7: astore_2
       8: aload_0
       9: aload_2
      10: invokevirtual #226                // Method scoreHistogram:(Ljava/util/List;)Ljava/util/Map;
      13: areturn

  public final java.lang.String pluralize(int, java.lang.String, java.lang.String);
    Code:
       0: aload_2
       1: ldc           #230                // String singular
       3: invokestatic  #22                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_3
       7: ldc           #232                // String plural
       9: invokestatic  #22                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
      12: iload_1
      13: iconst_1
      14: if_icmpne     21
      17: aload_2
      18: goto          22
      21: aload_3
      22: areturn

  public final <T> java.util.List<T> flattenWithSpacer(java.util.List<? extends java.util.List<? extends T>>, T);
    Code:
       0: aload_1
       1: ldc           #16                 // String <this>
       3: invokestatic  #22                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_1
       7: checkcast     #75                 // class java/lang/Iterable
      10: astore_3
      11: iconst_0
      12: istore        4
      14: aload_3
      15: astore        5
      17: new           #24                 // class java/util/ArrayList
      20: dup
      21: aload_3
      22: bipush        10
      24: invokestatic  #79                 // Method kotlin/collections/CollectionsKt.collectionSizeOrDefault:(Ljava/lang/Iterable;I)I
      27: invokespecial #82                 // Method java/util/ArrayList."<init>":(I)V
      30: checkcast     #84                 // class java/util/Collection
      33: astore        6
      35: iconst_0
      36: istore        7
      38: aload         5
      40: invokeinterface #88,  1           // InterfaceMethod java/lang/Iterable.iterator:()Ljava/util/Iterator;
      45: astore        8
      47: aload         8
      49: invokeinterface #94,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z
      54: ifeq          107
      57: aload         8
      59: invokeinterface #168,  1          // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
      64: astore        9
      66: aload         6
      68: aload         9
      70: checkcast     #27                 // class java/util/List
      73: astore        10
      75: astore        12
      77: iconst_0
      78: istore        11
      80: aload_2
      81: invokestatic  #153                // Method kotlin/collections/CollectionsKt.listOf:(Ljava/lang/Object;)Ljava/util/List;
      84: checkcast     #84                 // class java/util/Collection
      87: aload         10
      89: checkcast     #75                 // class java/lang/Iterable
      92: invokestatic  #136                // Method kotlin/collections/CollectionsKt.plus:(Ljava/util/Collection;Ljava/lang/Iterable;)Ljava/util/List;
      95: aload         12
      97: swap
      98: invokeinterface #100,  2          // InterfaceMethod java/util/Collection.add:(Ljava/lang/Object;)Z
     103: pop
     104: goto          47
     107: aload         6
     109: checkcast     #27                 // class java/util/List
     112: nop
     113: checkcast     #75                 // class java/lang/Iterable
     116: invokestatic  #242                // Method kotlin/collections/CollectionsKt.flatten:(Ljava/lang/Iterable;)Ljava/util/List;
     119: checkcast     #75                 // class java/lang/Iterable
     122: iconst_1
     123: invokestatic  #246                // Method kotlin/collections/CollectionsKt.drop:(Ljava/lang/Iterable;I)Ljava/util/List;
     126: areturn

  public static final void main(java.lang.String[]);
    Code:
       0: aload_0
       1: ldc           #254                // String args
       3: invokestatic  #22                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: iconst_2
       7: anewarray     #235                // class java/lang/String
      10: astore_2
      11: aload_2
      12: iconst_0
      13: ldc_w         #256                // String 1 hit per grouping of dice that sums to 6 or greater
      16: aastore
      17: aload_2
      18: iconst_1
      19: ldc_w         #258                // String
      22: aastore
      23: aload_2
      24: invokestatic  #260                // Method kotlin/collections/CollectionsKt.listOf:([Ljava/lang/Object;)Ljava/util/List;
      27: astore_1
      28: getstatic     #188                // Field INSTANCE:Lcom/seanshubin/kotlin/tryme/domain/dice/SpheresOfInfluenceDiceStatisticsApp;
      31: new           #70                 // class kotlin/ranges/IntRange
      34: dup
      35: iconst_1
      36: iconst_5
      37: invokespecial #73                 // Method kotlin/ranges/IntRange."<init>":(II)V
      40: checkcast     #75                 // class java/lang/Iterable
      43: astore_3
      44: astore        12
      46: iconst_0
      47: istore        4
      49: aload_3
      50: astore        5
      52: new           #24                 // class java/util/ArrayList
      55: dup
      56: aload_3
      57: bipush        10
      59: invokestatic  #79                 // Method kotlin/collections/CollectionsKt.collectionSizeOrDefault:(Ljava/lang/Iterable;I)I
      62: invokespecial #82                 // Method java/util/ArrayList."<init>":(I)V
      65: checkcast     #84                 // class java/util/Collection
      68: astore        6
      70: iconst_0
      71: istore        7
      73: aload         5
      75: invokeinterface #88,  1           // InterfaceMethod java/lang/Iterable.iterator:()Ljava/util/Iterator;
      80: astore        8
      82: aload         8
      84: invokeinterface #94,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z
      89: ifeq          142
      92: aload         8
      94: checkcast     #96                 // class kotlin/collections/IntIterator
      97: invokevirtual #99                 // Method kotlin/collections/IntIterator.nextInt:()I
     100: istore        9
     102: aload         6
     104: iload         9
     106: istore        10
     108: astore        13
     110: iconst_0
     111: istore        11
     113: new           #262                // class com/seanshubin/kotlin/tryme/domain/dice/SpheresOfInfluenceDiceStatisticsApp$HistogramForQuantity
     116: dup
     117: iload         10
     119: getstatic     #188                // Field INSTANCE:Lcom/seanshubin/kotlin/tryme/domain/dice/SpheresOfInfluenceDiceStatisticsApp;
     122: iload         10
     124: invokevirtual #264                // Method scoreHistogram:(I)Ljava/util/Map;
     127: invokespecial #267                // Method com/seanshubin/kotlin/tryme/domain/dice/SpheresOfInfluenceDiceStatisticsApp$HistogramForQuantity."<init>":(ILjava/util/Map;)V
     130: aload         13
     132: swap
     133: invokeinterface #100,  2          // InterfaceMethod java/util/Collection.add:(Ljava/lang/Object;)Z
     138: pop
     139: goto          82
     142: aload         6
     144: checkcast     #27                 // class java/util/List
     147: nop
     148: aload         12
     150: swap
     151: checkcast     #75                 // class java/lang/Iterable
     154: astore_3
     155: astore        12
     157: iconst_0
     158: istore        4
     160: aload_3
     161: astore        5
     163: new           #24                 // class java/util/ArrayList
     166: dup
     167: aload_3
     168: bipush        10
     170: invokestatic  #79                 // Method kotlin/collections/CollectionsKt.collectionSizeOrDefault:(Ljava/lang/Iterable;I)I
     173: invokespecial #82                 // Method java/util/ArrayList."<init>":(I)V
     176: checkcast     #84                 // class java/util/Collection
     179: astore        6
     181: iconst_0
     182: istore        7
     184: aload         5
     186: invokeinterface #88,  1           // InterfaceMethod java/lang/Iterable.iterator:()Ljava/util/Iterator;
     191: astore        8
     193: aload         8
     195: invokeinterface #94,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z
     200: ifeq          243
     203: aload         8
     205: invokeinterface #168,  1          // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
     210: astore        9
     212: aload         6
     214: aload         9
     216: checkcast     #262                // class com/seanshubin/kotlin/tryme/domain/dice/SpheresOfInfluenceDiceStatisticsApp$HistogramForQuantity
     219: astore        10
     221: astore        13
     223: iconst_0
     224: istore        11
     226: aload         10
     228: invokevirtual #271                // Method com/seanshubin/kotlin/tryme/domain/dice/SpheresOfInfluenceDiceStatisticsApp$HistogramForQuantity.summary:()Ljava/util/List;
     231: aload         13
     233: swap
     234: invokeinterface #100,  2          // InterfaceMethod java/util/Collection.add:(Ljava/lang/Object;)Z
     239: pop
     240: goto          193
     243: aload         6
     245: checkcast     #27                 // class java/util/List
     248: nop
     249: aload         12
     251: swap
     252: ldc_w         #258                // String
     255: invokevirtual #273                // Method flattenWithSpacer:(Ljava/util/List;Ljava/lang/Object;)Ljava/util/List;
     258: astore_2
     259: aload_1
     260: checkcast     #84                 // class java/util/Collection
     263: aload_2
     264: checkcast     #75                 // class java/lang/Iterable
     267: invokestatic  #136                // Method kotlin/collections/CollectionsKt.plus:(Ljava/util/Collection;Ljava/lang/Iterable;)Ljava/util/List;
     270: astore_3
     271: aload_3
     272: checkcast     #75                 // class java/lang/Iterable
     275: astore        4
     277: iconst_0
     278: istore        5
     280: aload         4
     282: invokeinterface #88,  1           // InterfaceMethod java/lang/Iterable.iterator:()Ljava/util/Iterator;
     287: astore        6
     289: aload         6
     291: invokeinterface #94,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z
     296: ifeq          328
     299: aload         6
     301: invokeinterface #168,  1          // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
     306: astore        7
     308: aload         7
     310: astore        8
     312: iconst_0
     313: istore        9
     315: getstatic     #279                // Field java/lang/System.out:Ljava/io/PrintStream;
     318: aload         8
     320: invokevirtual #285                // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
     323: nop
     324: nop
     325: goto          289
     328: nop
     329: return

  static {};
    Code:
       0: new           #2                  // class com/seanshubin/kotlin/tryme/domain/dice/SpheresOfInfluenceDiceStatisticsApp
       3: dup
       4: invokespecial #297                // Method "<init>":()V
       7: putstatic     #188                // Field INSTANCE:Lcom/seanshubin/kotlin/tryme/domain/dice/SpheresOfInfluenceDiceStatisticsApp;
      10: return
}

Compiled from "SpheresOfInfluenceDiceStatisticsApp.kt"
public final class com.seanshubin.kotlin.tryme.domain.dice.SpheresOfInfluenceDiceStatisticsApp$HistogramForQuantity {
  private final int quantity;

  private final java.util.Map<java.lang.Integer, java.lang.Integer> histogram;

  public com.seanshubin.kotlin.tryme.domain.dice.SpheresOfInfluenceDiceStatisticsApp$HistogramForQuantity(int, java.util.Map<java.lang.Integer, java.lang.Integer>);
    Code:
       0: aload_2
       1: ldc           #10                 // String histogram
       3: invokestatic  #16                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_0
       7: invokespecial #19                 // Method java/lang/Object."<init>":()V
      10: aload_0
      11: iload_1
      12: putfield      #23                 // Field quantity:I
      15: aload_0
      16: aload_2
      17: putfield      #26                 // Field histogram:Ljava/util/Map;
      20: return

  public final int getQuantity();
    Code:
       0: aload_0
       1: getfield      #23                 // Field quantity:I
       4: ireturn

  public final java.util.Map<java.lang.Integer, java.lang.Integer> getHistogram();
    Code:
       0: aload_0
       1: getfield      #26                 // Field histogram:Ljava/util/Map;
       4: areturn

  public final java.util.List<java.lang.String> summary();
    Code:
       0: aload_0
       1: getfield      #26                 // Field histogram:Ljava/util/Map;
       4: invokeinterface #42,  1           // InterfaceMethod java/util/Map.values:()Ljava/util/Collection;
       9: checkcast     #44                 // class java/lang/Iterable
      12: invokestatic  #50                 // Method kotlin/collections/CollectionsKt.sumOfInt:(Ljava/lang/Iterable;)I
      15: istore_1
      16: aload_0
      17: getfield      #26                 // Field histogram:Ljava/util/Map;
      20: invokeinterface #54,  1           // InterfaceMethod java/util/Map.keySet:()Ljava/util/Set;
      25: checkcast     #44                 // class java/lang/Iterable
      28: invokestatic  #58                 // Method kotlin/collections/CollectionsKt.sorted:(Ljava/lang/Iterable;)Ljava/util/List;
      31: astore_2
      32: getstatic     #64                 // Field com/seanshubin/kotlin/tryme/domain/dice/SpheresOfInfluenceDiceStatisticsApp.INSTANCE:Lcom/seanshubin/kotlin/tryme/domain/dice/SpheresOfInfluenceDiceStatisticsApp;
      35: aload_0
      36: getfield      #23                 // Field quantity:I
      39: ldc           #66                 // String die
      41: ldc           #68                 // String dice
      43: invokevirtual #72                 // Method com/seanshubin/kotlin/tryme/domain/dice/SpheresOfInfluenceDiceStatisticsApp.pluralize:(ILjava/lang/String;Ljava/lang/String;)Ljava/lang/String;
      46: astore_3
      47: new           #74                 // class java/lang/StringBuilder
      50: dup
      51: invokespecial #75                 // Method java/lang/StringBuilder."<init>":()V
      54: aload_0
      55: getfield      #23                 // Field quantity:I
      58: invokevirtual #79                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      61: bipush        32
      63: invokevirtual #82                 // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder;
      66: aload_3
      67: invokevirtual #85                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      70: invokevirtual #89                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      73: astore        4
      75: aload_2
      76: checkcast     #44                 // class java/lang/Iterable
      79: astore        6
      81: iconst_0
      82: istore        7
      84: aload         6
      86: astore        8
      88: new           #91                 // class java/util/ArrayList
      91: dup
      92: aload         6
      94: bipush        10
      96: invokestatic  #95                 // Method kotlin/collections/CollectionsKt.collectionSizeOrDefault:(Ljava/lang/Iterable;I)I
      99: invokespecial #98                 // Method java/util/ArrayList."<init>":(I)V
     102: checkcast     #100                // class java/util/Collection
     105: astore        9
     107: iconst_0
     108: istore        10
     110: aload         8
     112: invokeinterface #104,  1          // InterfaceMethod java/lang/Iterable.iterator:()Ljava/util/Iterator;
     117: astore        11
     119: aload         11
     121: invokeinterface #110,  1          // InterfaceMethod java/util/Iterator.hasNext:()Z
     126: ifeq          301
     129: aload         11
     131: invokeinterface #114,  1          // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
     136: astore        12
     138: aload         9
     140: aload         12
     142: checkcast     #116                // class java/lang/Number
     145: invokevirtual #119                // Method java/lang/Number.intValue:()I
     148: istore        13
     150: astore        22
     152: iconst_0
     153: istore        14
     155: aload_0
     156: getfield      #26                 // Field histogram:Ljava/util/Map;
     159: iload         13
     161: invokestatic  #125                // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     164: invokestatic  #131                // Method kotlin/collections/MapsKt.getValue:(Ljava/util/Map;Ljava/lang/Object;)Ljava/lang/Object;
     167: checkcast     #116                // class java/lang/Number
     170: invokevirtual #119                // Method java/lang/Number.intValue:()I
     173: istore        15
     175: iload         15
     177: i2d
     178: iload_1
     179: i2d
     180: ddiv
     181: ldc2_w        #132                // double 100.0d
     184: dmul
     185: dstore        16
     187: getstatic     #64                 // Field com/seanshubin/kotlin/tryme/domain/dice/SpheresOfInfluenceDiceStatisticsApp.INSTANCE:Lcom/seanshubin/kotlin/tryme/domain/dice/SpheresOfInfluenceDiceStatisticsApp;
     190: iload         13
     192: ldc           #135                // String hit
     194: ldc           #137                // String hits
     196: invokevirtual #72                 // Method com/seanshubin/kotlin/tryme/domain/dice/SpheresOfInfluenceDiceStatisticsApp.pluralize:(ILjava/lang/String;Ljava/lang/String;)Ljava/lang/String;
     199: astore        18
     201: new           #74                 // class java/lang/StringBuilder
     204: dup
     205: invokespecial #75                 // Method java/lang/StringBuilder."<init>":()V
     208: iload         15
     210: invokevirtual #79                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
     213: bipush        47
     215: invokevirtual #82                 // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder;
     218: iload_1
     219: invokevirtual #79                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
     222: ldc           #139                // String  (%.2f%%) chance of
     224: invokevirtual #85                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
     227: iload         13
     229: invokevirtual #79                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
     232: bipush        32
     234: invokevirtual #82                 // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder;
     237: aload         18
     239: invokevirtual #85                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
     242: invokevirtual #89                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
     245: astore        19
     247: iconst_1
     248: anewarray     #4                  // class java/lang/Object
     251: astore        20
     253: aload         20
     255: iconst_0
     256: dload         16
     258: invokestatic  #144                // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
     261: aastore
     262: aload         20
     264: astore        20
     266: aload         19
     268: aload         20
     270: aload         20
     272: arraylength
     273: invokestatic  #150                // Method java/util/Arrays.copyOf:([Ljava/lang/Object;I)[Ljava/lang/Object;
     276: invokestatic  #156                // Method java/lang/String.format:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
     279: dup
     280: ldc           #158                // String format(...)
     282: invokestatic  #161                // Method kotlin/jvm/internal/Intrinsics.checkNotNullExpressionValue:(Ljava/lang/Object;Ljava/lang/String;)V
     285: astore        21
     287: aload         21
     289: aload         22
     291: swap
     292: invokeinterface #165,  2          // InterfaceMethod java/util/Collection.add:(Ljava/lang/Object;)Z
     297: pop
     298: goto          119
     301: aload         9
     303: checkcast     #167                // class java/util/List
     306: nop
     307: astore        5
     309: aload         4
     311: invokestatic  #171                // Method kotlin/collections/CollectionsKt.listOf:(Ljava/lang/Object;)Ljava/util/List;
     314: checkcast     #100                // class java/util/Collection
     317: aload         5
     319: checkcast     #44                 // class java/lang/Iterable
     322: invokestatic  #175                // Method kotlin/collections/CollectionsKt.plus:(Ljava/util/Collection;Ljava/lang/Iterable;)Ljava/util/List;
     325: areturn

  public final int component1();
    Code:
       0: aload_0
       1: getfield      #23                 // Field quantity:I
       4: ireturn

  public final java.util.Map<java.lang.Integer, java.lang.Integer> component2();
    Code:
       0: aload_0
       1: getfield      #26                 // Field histogram:Ljava/util/Map;
       4: areturn

  public final com.seanshubin.kotlin.tryme.domain.dice.SpheresOfInfluenceDiceStatisticsApp$HistogramForQuantity copy(int, java.util.Map<java.lang.Integer, java.lang.Integer>);
    Code:
       0: aload_2
       1: ldc           #10                 // String histogram
       3: invokestatic  #16                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: new           #2                  // class com/seanshubin/kotlin/tryme/domain/dice/SpheresOfInfluenceDiceStatisticsApp$HistogramForQuantity
       9: dup
      10: iload_1
      11: aload_2
      12: invokespecial #204                // Method "<init>":(ILjava/util/Map;)V
      15: areturn

  public static com.seanshubin.kotlin.tryme.domain.dice.SpheresOfInfluenceDiceStatisticsApp$HistogramForQuantity copy$default(com.seanshubin.kotlin.tryme.domain.dice.SpheresOfInfluenceDiceStatisticsApp$HistogramForQuantity, int, java.util.Map, int, java.lang.Object);
    Code:
       0: iload_3
       1: iconst_1
       2: iand
       3: ifeq          11
       6: aload_0
       7: getfield      #23                 // Field quantity:I
      10: istore_1
      11: iload_3
      12: iconst_2
      13: iand
      14: ifeq          22
      17: aload_0
      18: getfield      #26                 // Field histogram:Ljava/util/Map;
      21: astore_2
      22: aload_0
      23: iload_1
      24: aload_2
      25: invokevirtual #208                // Method copy:(ILjava/util/Map;)Lcom/seanshubin/kotlin/tryme/domain/dice/SpheresOfInfluenceDiceStatisticsApp$HistogramForQuantity;
      28: areturn

  public java.lang.String toString();
    Code:
       0: new           #74                 // class java/lang/StringBuilder
       3: dup
       4: invokespecial #75                 // Method java/lang/StringBuilder."<init>":()V
       7: ldc           #210                // String HistogramForQuantity(quantity=
       9: invokevirtual #85                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      12: aload_0
      13: getfield      #23                 // Field quantity:I
      16: invokevirtual #79                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      19: ldc           #212                // String , histogram=
      21: invokevirtual #85                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      24: aload_0
      25: getfield      #26                 // Field histogram:Ljava/util/Map;
      28: invokevirtual #215                // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
      31: bipush        41
      33: invokevirtual #82                 // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder;
      36: invokevirtual #89                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      39: areturn

  public int hashCode();
    Code:
       0: aload_0
       1: getfield      #23                 // Field quantity:I
       4: invokestatic  #219                // Method java/lang/Integer.hashCode:(I)I
       7: istore_1
       8: iload_1
       9: bipush        31
      11: imul
      12: aload_0
      13: getfield      #26                 // Field histogram:Ljava/util/Map;
      16: invokevirtual #221                // Method java/lang/Object.hashCode:()I
      19: iadd
      20: istore_1
      21: iload_1
      22: ireturn

  public boolean equals(java.lang.Object);
    Code:
       0: aload_0
       1: aload_1
       2: if_acmpne     7
       5: iconst_1
       6: ireturn
       7: aload_1
       8: instanceof    #2                  // class com/seanshubin/kotlin/tryme/domain/dice/SpheresOfInfluenceDiceStatisticsApp$HistogramForQuantity
      11: ifne          16
      14: iconst_0
      15: ireturn
      16: aload_1
      17: checkcast     #2                  // class com/seanshubin/kotlin/tryme/domain/dice/SpheresOfInfluenceDiceStatisticsApp$HistogramForQuantity
      20: astore_2
      21: aload_0
      22: getfield      #23                 // Field quantity:I
      25: aload_2
      26: getfield      #23                 // Field quantity:I
      29: if_icmpeq     34
      32: iconst_0
      33: ireturn
      34: aload_0
      35: getfield      #26                 // Field histogram:Ljava/util/Map;
      38: aload_2
      39: getfield      #26                 // Field histogram:Ljava/util/Map;
      42: invokestatic  #228                // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z
      45: ifne          50
      48: iconst_0
      49: ireturn
      50: iconst_1
      51: ireturn
}


package com.seanshubin.kotlin.tryme.domain.ratio

data class Ratio(val numerator: Int, val denominator: Int) : Comparable<Ratio> {
  operator fun plus(that: Ratio): Ratio {
    val lcm = leastCommonMultiple(denominator, that.denominator)
    return Ratio(numerator * lcm / denominator + that.numerator * lcm / that.denominator, lcm).simplify()
  }

  operator fun minus(that: Ratio): Ratio = (this + -that).simplify()
  operator fun times(that: Ratio): Ratio = Ratio(numerator * that.numerator, denominator * that.denominator).simplify()
  operator fun div(that: Ratio): Ratio = (this * that.recriprocal()).simplify()
  operator fun unaryMinus(): Ratio = Ratio(-numerator, denominator).simplify()
  fun recriprocal(): Ratio = Ratio(denominator, numerator).simplify()
  fun withDenominator(newDenominator: Int): Ratio = Ratio(numerator * newDenominator / denominator, newDenominator)
  override fun compareTo(that: Ratio): Int {
    val lcm = leastCommonMultiple(denominator, that.denominator)
    return (numerator * lcm / denominator).compareTo(that.numerator * lcm / that.denominator)
  }

  override fun toString(): String = "$numerator/$denominator"

  fun simplify(): Ratio = simplifyFactor().simplifySign()
  private fun simplifySign(): Ratio =
      if (denominator < 0) Ratio(-numerator, -denominator)
      else this

  private fun simplifyFactor(): Ratio {
    val gcf = greatestCommonFactor(numerator, denominator)
    return Ratio(numerator / gcf, denominator / gcf)
  }

  companion object {
    fun greatestCommonFactor(a: Int, b: Int): Int =
        if (b == 0) a
        else greatestCommonFactor(b, a % b)

    fun leastCommonMultiple(a: Int, b: Int): Int =
        if (a == 0 && b == 0) 0
        else a * b / greatestCommonFactor(a, b)

    val regex = Regex("""(-?\d+)/(-?\d+)""")

    fun parse(s: String): Ratio {
      val matchResult = regex.matchEntire(s)
      if (matchResult == null) throw RuntimeException("Value '$s' could did not match expression $regex")
      val numerator = matchResult.groupValues[1].toInt()
      val denominator = matchResult.groupValues[2].toInt()
      return Ratio(numerator, denominator).simplify()
    }
  }

  val toDouble: Double get() = numerator.toDouble() / denominator.toDouble()
}

Compiled from "Ratio.kt"
public final class com.seanshubin.kotlin.tryme.domain.ratio.Ratio implements java.lang.Comparable<com.seanshubin.kotlin.tryme.domain.ratio.Ratio> {
  public static final com.seanshubin.kotlin.tryme.domain.ratio.Ratio$Companion Companion;

  private final int numerator;

  private final int denominator;

  private static final kotlin.text.Regex regex;

  public com.seanshubin.kotlin.tryme.domain.ratio.Ratio(int, int);
    Code:
       0: aload_0
       1: invokespecial #12                 // Method java/lang/Object."<init>":()V
       4: aload_0
       5: iload_1
       6: putfield      #16                 // Field numerator:I
       9: aload_0
      10: iload_2
      11: putfield      #19                 // Field denominator:I
      14: return

  public final int getNumerator();
    Code:
       0: aload_0
       1: getfield      #16                 // Field numerator:I
       4: ireturn

  public final int getDenominator();
    Code:
       0: aload_0
       1: getfield      #19                 // Field denominator:I
       4: ireturn

  public final com.seanshubin.kotlin.tryme.domain.ratio.Ratio plus(com.seanshubin.kotlin.tryme.domain.ratio.Ratio);
    Code:
       0: aload_1
       1: ldc           #29                 // String that
       3: invokestatic  #35                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: getstatic     #39                 // Field Companion:Lcom/seanshubin/kotlin/tryme/domain/ratio/Ratio$Companion;
       9: aload_0
      10: getfield      #19                 // Field denominator:I
      13: aload_1
      14: getfield      #19                 // Field denominator:I
      17: invokevirtual #45                 // Method com/seanshubin/kotlin/tryme/domain/ratio/Ratio$Companion.leastCommonMultiple:(II)I
      20: istore_2
      21: new           #2                  // class com/seanshubin/kotlin/tryme/domain/ratio/Ratio
      24: dup
      25: aload_0
      26: getfield      #16                 // Field numerator:I
      29: iload_2
      30: imul
      31: aload_0
      32: getfield      #19                 // Field denominator:I
      35: idiv
      36: aload_1
      37: getfield      #16                 // Field numerator:I
      40: iload_2
      41: imul
      42: aload_1
      43: getfield      #19                 // Field denominator:I
      46: idiv
      47: iadd
      48: iload_2
      49: invokespecial #47                 // Method "<init>":(II)V
      52: invokevirtual #51                 // Method simplify:()Lcom/seanshubin/kotlin/tryme/domain/ratio/Ratio;
      55: areturn

  public final com.seanshubin.kotlin.tryme.domain.ratio.Ratio minus(com.seanshubin.kotlin.tryme.domain.ratio.Ratio);
    Code:
       0: aload_1
       1: ldc           #29                 // String that
       3: invokestatic  #35                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_0
       7: aload_1
       8: invokevirtual #56                 // Method unaryMinus:()Lcom/seanshubin/kotlin/tryme/domain/ratio/Ratio;
      11: invokevirtual #58                 // Method plus:(Lcom/seanshubin/kotlin/tryme/domain/ratio/Ratio;)Lcom/seanshubin/kotlin/tryme/domain/ratio/Ratio;
      14: invokevirtual #51                 // Method simplify:()Lcom/seanshubin/kotlin/tryme/domain/ratio/Ratio;
      17: areturn

  public final com.seanshubin.kotlin.tryme.domain.ratio.Ratio times(com.seanshubin.kotlin.tryme.domain.ratio.Ratio);
    Code:
       0: aload_1
       1: ldc           #29                 // String that
       3: invokestatic  #35                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: new           #2                  // class com/seanshubin/kotlin/tryme/domain/ratio/Ratio
       9: dup
      10: aload_0
      11: getfield      #16                 // Field numerator:I
      14: aload_1
      15: getfield      #16                 // Field numerator:I
      18: imul
      19: aload_0
      20: getfield      #19                 // Field denominator:I
      23: aload_1
      24: getfield      #19                 // Field denominator:I
      27: imul
      28: invokespecial #47                 // Method "<init>":(II)V
      31: invokevirtual #51                 // Method simplify:()Lcom/seanshubin/kotlin/tryme/domain/ratio/Ratio;
      34: areturn

  public final com.seanshubin.kotlin.tryme.domain.ratio.Ratio div(com.seanshubin.kotlin.tryme.domain.ratio.Ratio);
    Code:
       0: aload_1
       1: ldc           #29                 // String that
       3: invokestatic  #35                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_0
       7: aload_1
       8: invokevirtual #63                 // Method recriprocal:()Lcom/seanshubin/kotlin/tryme/domain/ratio/Ratio;
      11: invokevirtual #65                 // Method times:(Lcom/seanshubin/kotlin/tryme/domain/ratio/Ratio;)Lcom/seanshubin/kotlin/tryme/domain/ratio/Ratio;
      14: invokevirtual #51                 // Method simplify:()Lcom/seanshubin/kotlin/tryme/domain/ratio/Ratio;
      17: areturn

  public final com.seanshubin.kotlin.tryme.domain.ratio.Ratio unaryMinus();
    Code:
       0: new           #2                  // class com/seanshubin/kotlin/tryme/domain/ratio/Ratio
       3: dup
       4: aload_0
       5: getfield      #16                 // Field numerator:I
       8: ineg
       9: aload_0
      10: getfield      #19                 // Field denominator:I
      13: invokespecial #47                 // Method "<init>":(II)V
      16: invokevirtual #51                 // Method simplify:()Lcom/seanshubin/kotlin/tryme/domain/ratio/Ratio;
      19: areturn

  public final com.seanshubin.kotlin.tryme.domain.ratio.Ratio recriprocal();
    Code:
       0: new           #2                  // class com/seanshubin/kotlin/tryme/domain/ratio/Ratio
       3: dup
       4: aload_0
       5: getfield      #19                 // Field denominator:I
       8: aload_0
       9: getfield      #16                 // Field numerator:I
      12: invokespecial #47                 // Method "<init>":(II)V
      15: invokevirtual #51                 // Method simplify:()Lcom/seanshubin/kotlin/tryme/domain/ratio/Ratio;
      18: areturn

  public final com.seanshubin.kotlin.tryme.domain.ratio.Ratio withDenominator(int);
    Code:
       0: new           #2                  // class com/seanshubin/kotlin/tryme/domain/ratio/Ratio
       3: dup
       4: aload_0
       5: getfield      #16                 // Field numerator:I
       8: iload_1
       9: imul
      10: aload_0
      11: getfield      #19                 // Field denominator:I
      14: idiv
      15: iload_1
      16: invokespecial #47                 // Method "<init>":(II)V
      19: areturn

  public int compareTo(com.seanshubin.kotlin.tryme.domain.ratio.Ratio);
    Code:
       0: aload_1
       1: ldc           #29                 // String that
       3: invokestatic  #35                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: getstatic     #39                 // Field Companion:Lcom/seanshubin/kotlin/tryme/domain/ratio/Ratio$Companion;
       9: aload_0
      10: getfield      #19                 // Field denominator:I
      13: aload_1
      14: getfield      #19                 // Field denominator:I
      17: invokevirtual #45                 // Method com/seanshubin/kotlin/tryme/domain/ratio/Ratio$Companion.leastCommonMultiple:(II)I
      20: istore_2
      21: aload_0
      22: getfield      #16                 // Field numerator:I
      25: iload_2
      26: imul
      27: aload_0
      28: getfield      #19                 // Field denominator:I
      31: idiv
      32: aload_1
      33: getfield      #16                 // Field numerator:I
      36: iload_2
      37: imul
      38: aload_1
      39: getfield      #19                 // Field denominator:I
      42: idiv
      43: invokestatic  #73                 // Method kotlin/jvm/internal/Intrinsics.compare:(II)I
      46: ireturn

  public java.lang.String toString();
    Code:
       0: new           #77                 // class java/lang/StringBuilder
       3: dup
       4: invokespecial #78                 // Method java/lang/StringBuilder."<init>":()V
       7: aload_0
       8: getfield      #16                 // Field numerator:I
      11: invokevirtual #82                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      14: bipush        47
      16: invokevirtual #85                 // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder;
      19: aload_0
      20: getfield      #19                 // Field denominator:I
      23: invokevirtual #82                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      26: invokevirtual #87                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      29: areturn

  public final com.seanshubin.kotlin.tryme.domain.ratio.Ratio simplify();
    Code:
       0: aload_0
       1: invokespecial #90                 // Method simplifyFactor:()Lcom/seanshubin/kotlin/tryme/domain/ratio/Ratio;
       4: invokespecial #93                 // Method simplifySign:()Lcom/seanshubin/kotlin/tryme/domain/ratio/Ratio;
       7: areturn

  private final com.seanshubin.kotlin.tryme.domain.ratio.Ratio simplifySign();
    Code:
       0: aload_0
       1: getfield      #19                 // Field denominator:I
       4: ifge          27
       7: new           #2                  // class com/seanshubin/kotlin/tryme/domain/ratio/Ratio
      10: dup
      11: aload_0
      12: getfield      #16                 // Field numerator:I
      15: ineg
      16: aload_0
      17: getfield      #19                 // Field denominator:I
      20: ineg
      21: invokespecial #47                 // Method "<init>":(II)V
      24: goto          28
      27: aload_0
      28: areturn

  private final com.seanshubin.kotlin.tryme.domain.ratio.Ratio simplifyFactor();
    Code:
       0: getstatic     #39                 // Field Companion:Lcom/seanshubin/kotlin/tryme/domain/ratio/Ratio$Companion;
       3: aload_0
       4: getfield      #16                 // Field numerator:I
       7: aload_0
       8: getfield      #19                 // Field denominator:I
      11: invokevirtual #96                 // Method com/seanshubin/kotlin/tryme/domain/ratio/Ratio$Companion.greatestCommonFactor:(II)I
      14: istore_1
      15: new           #2                  // class com/seanshubin/kotlin/tryme/domain/ratio/Ratio
      18: dup
      19: aload_0
      20: getfield      #16                 // Field numerator:I
      23: iload_1
      24: idiv
      25: aload_0
      26: getfield      #19                 // Field denominator:I
      29: iload_1
      30: idiv
      31: invokespecial #47                 // Method "<init>":(II)V
      34: areturn

  public final double getToDouble();
    Code:
       0: aload_0
       1: getfield      #16                 // Field numerator:I
       4: i2d
       5: aload_0
       6: getfield      #19                 // Field denominator:I
       9: i2d
      10: ddiv
      11: dreturn

  public final int component1();
    Code:
       0: aload_0
       1: getfield      #16                 // Field numerator:I
       4: ireturn

  public final int component2();
    Code:
       0: aload_0
       1: getfield      #19                 // Field denominator:I
       4: ireturn

  public final com.seanshubin.kotlin.tryme.domain.ratio.Ratio copy(int, int);
    Code:
       0: new           #2                  // class com/seanshubin/kotlin/tryme/domain/ratio/Ratio
       3: dup
       4: iload_1
       5: iload_2
       6: invokespecial #47                 // Method "<init>":(II)V
       9: areturn

  public static com.seanshubin.kotlin.tryme.domain.ratio.Ratio copy$default(com.seanshubin.kotlin.tryme.domain.ratio.Ratio, int, int, int, java.lang.Object);
    Code:
       0: iload_3
       1: iconst_1
       2: iand
       3: ifeq          11
       6: aload_0
       7: getfield      #16                 // Field numerator:I
      10: istore_1
      11: iload_3
      12: iconst_2
      13: iand
      14: ifeq          22
      17: aload_0
      18: getfield      #19                 // Field denominator:I
      21: istore_2
      22: aload_0
      23: iload_1
      24: iload_2
      25: invokevirtual #107                // Method copy:(II)Lcom/seanshubin/kotlin/tryme/domain/ratio/Ratio;
      28: areturn

  public int hashCode();
    Code:
       0: aload_0
       1: getfield      #16                 // Field numerator:I
       4: invokestatic  #113                // Method java/lang/Integer.hashCode:(I)I
       7: istore_1
       8: iload_1
       9: bipush        31
      11: imul
      12: aload_0
      13: getfield      #19                 // Field denominator:I
      16: invokestatic  #113                // Method java/lang/Integer.hashCode:(I)I
      19: iadd
      20: istore_1
      21: iload_1
      22: ireturn

  public boolean equals(java.lang.Object);
    Code:
       0: aload_0
       1: aload_1
       2: if_acmpne     7
       5: iconst_1
       6: ireturn
       7: aload_1
       8: instanceof    #2                  // class com/seanshubin/kotlin/tryme/domain/ratio/Ratio
      11: ifne          16
      14: iconst_0
      15: ireturn
      16: aload_1
      17: checkcast     #2                  // class com/seanshubin/kotlin/tryme/domain/ratio/Ratio
      20: astore_2
      21: aload_0
      22: getfield      #16                 // Field numerator:I
      25: aload_2
      26: getfield      #16                 // Field numerator:I
      29: if_icmpeq     34
      32: iconst_0
      33: ireturn
      34: aload_0
      35: getfield      #19                 // Field denominator:I
      38: aload_2
      39: getfield      #19                 // Field denominator:I
      42: if_icmpeq     47
      45: iconst_0
      46: ireturn
      47: iconst_1
      48: ireturn

  public int compareTo(java.lang.Object);
    Code:
       0: aload_0
       1: aload_1
       2: checkcast     #2                  // class com/seanshubin/kotlin/tryme/domain/ratio/Ratio
       5: invokevirtual #122                // Method compareTo:(Lcom/seanshubin/kotlin/tryme/domain/ratio/Ratio;)I
       8: ireturn

  public static final kotlin.text.Regex access$getRegex$cp();
    Code:
       0: getstatic     #128                // Field regex:Lkotlin/text/Regex;
       3: areturn

  static {};
    Code:
       0: new           #41                 // class com/seanshubin/kotlin/tryme/domain/ratio/Ratio$Companion
       3: dup
       4: aconst_null
       5: invokespecial #132                // Method com/seanshubin/kotlin/tryme/domain/ratio/Ratio$Companion."<init>":(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
       8: putstatic     #39                 // Field Companion:Lcom/seanshubin/kotlin/tryme/domain/ratio/Ratio$Companion;
      11: new           #134                // class kotlin/text/Regex
      14: dup
      15: ldc           #136                // String (-?\\d+)/(-?\\d+)
      17: invokespecial #139                // Method kotlin/text/Regex."<init>":(Ljava/lang/String;)V
      20: putstatic     #128                // Field regex:Lkotlin/text/Regex;
      23: return
}

Compiled from "Ratio.kt"
public final class com.seanshubin.kotlin.tryme.domain.ratio.Ratio$Companion {
  private com.seanshubin.kotlin.tryme.domain.ratio.Ratio$Companion();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public final int greatestCommonFactor(int, int);
    Code:
       0: iload_2
       1: ifne          8
       4: iload_1
       5: goto          16
       8: aload_0
       9: iload_2
      10: iload_1
      11: iload_2
      12: irem
      13: invokevirtual #14                 // Method greatestCommonFactor:(II)I
      16: ireturn

  public final int leastCommonMultiple(int, int);
    Code:
       0: iload_1
       1: ifne          12
       4: iload_2
       5: ifne          12
       8: iconst_0
       9: goto          22
      12: iload_1
      13: iload_2
      14: imul
      15: aload_0
      16: iload_1
      17: iload_2
      18: invokevirtual #14                 // Method greatestCommonFactor:(II)I
      21: idiv
      22: ireturn

  public final kotlin.text.Regex getRegex();
    Code:
       0: invokestatic  #26                 // Method com/seanshubin/kotlin/tryme/domain/ratio/Ratio.access$getRegex$cp:()Lkotlin/text/Regex;
       3: areturn

  public final com.seanshubin.kotlin.tryme.domain.ratio.Ratio parse(java.lang.String);
    Code:
       0: aload_1
       1: ldc           #30                 // String s
       3: invokestatic  #36                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_0
       7: invokevirtual #38                 // Method getRegex:()Lkotlin/text/Regex;
      10: aload_1
      11: checkcast     #40                 // class java/lang/CharSequence
      14: invokevirtual #46                 // Method kotlin/text/Regex.matchEntire:(Ljava/lang/CharSequence;)Lkotlin/text/MatchResult;
      17: astore_2
      18: aload_2
      19: ifnonnull     61
      22: new           #48                 // class java/lang/RuntimeException
      25: dup
      26: new           #50                 // class java/lang/StringBuilder
      29: dup
      30: invokespecial #51                 // Method java/lang/StringBuilder."<init>":()V
      33: ldc           #53                 // String Value \'
      35: invokevirtual #57                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      38: aload_1
      39: invokevirtual #57                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      42: ldc           #59                 // String \' could did not match expression
      44: invokevirtual #57                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      47: aload_0
      48: invokevirtual #38                 // Method getRegex:()Lkotlin/text/Regex;
      51: invokevirtual #62                 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
      54: invokevirtual #66                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      57: invokespecial #69                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
      60: athrow
      61: aload_2
      62: invokeinterface #75,  1           // InterfaceMethod kotlin/text/MatchResult.getGroupValues:()Ljava/util/List;
      67: iconst_1
      68: invokeinterface #81,  2           // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
      73: checkcast     #83                 // class java/lang/String
      76: invokestatic  #89                 // Method java/lang/Integer.parseInt:(Ljava/lang/String;)I
      79: istore_3
      80: aload_2
      81: invokeinterface #75,  1           // InterfaceMethod kotlin/text/MatchResult.getGroupValues:()Ljava/util/List;
      86: iconst_2
      87: invokeinterface #81,  2           // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
      92: checkcast     #83                 // class java/lang/String
      95: invokestatic  #89                 // Method java/lang/Integer.parseInt:(Ljava/lang/String;)I
      98: istore        4
     100: new           #23                 // class com/seanshubin/kotlin/tryme/domain/ratio/Ratio
     103: dup
     104: iload_3
     105: iload         4
     107: invokespecial #92                 // Method com/seanshubin/kotlin/tryme/domain/ratio/Ratio."<init>":(II)V
     110: invokevirtual #96                 // Method com/seanshubin/kotlin/tryme/domain/ratio/Ratio.simplify:()Lcom/seanshubin/kotlin/tryme/domain/ratio/Ratio;
     113: areturn

  public com.seanshubin.kotlin.tryme.domain.ratio.Ratio$Companion(kotlin.jvm.internal.DefaultConstructorMarker);
    Code:
       0: aload_0
       1: invokespecial #103                // Method "<init>":()V
       4: return
}