package rationals

import java.lang.IllegalArgumentException
import java.lang.NumberFormatException
import java.math.BigInteger

class Rational {
    private val numerator: BigInteger
    private val denominator: BigInteger

    constructor(numerator: BigInteger) : this(numerator, BigInteger.ONE)

    constructor(numerator: Number): this(numerator, 1)

    constructor(numerator: Number, denominator: Number) :
            this(BigInteger.valueOf(numerator.toLong()), BigInteger.valueOf(denominator.toLong()))

    constructor(numerator: BigInteger, denominator: BigInteger) {
        if (denominator == BigInteger.ZERO) throw IllegalArgumentException("Denominator cannot be zero")

        val sign = denominator.signum().toBigInteger()
        val signedNumerator = sign * numerator
        val signedDenominator = sign * denominator

        val gcd = signedNumerator.gcd(signedDenominator)
        this.numerator = signedNumerator.divide(gcd)
        this.denominator = signedDenominator.divide(gcd)
    }

    operator fun plus(that: Rational) = Rational(
            (this.numerator * that.denominator) + (that.numerator * this.denominator),
            this.denominator * that.denominator)

    operator fun minus(that: Rational) = plus(-that)

    operator fun times(that: Rational) = Rational(this.numerator * that.numerator, this.denominator * that.denominator)

    operator fun div(that: Rational) = Rational(this.numerator * that.denominator, this.denominator * that.numerator)

    operator fun unaryMinus() = Rational(-numerator, denominator)

    operator fun compareTo(that: Rational) : Int {
        return ((this.numerator * that.denominator) - (that.numerator * this.denominator)).signum()
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is Rational) return false
        return this.numerator == other.numerator && this.denominator == other.denominator
    }

    override fun toString() = if (denominator == BigInteger.ONE) "$numerator" else "$numerator/$denominator"

    operator fun rangeTo(that: Rational): RationalRange {
        return RationalRange(this, that)
    }

    override fun hashCode(): Int {
        var result = numerator.hashCode()
        result = 31 * result + denominator.hashCode()
        return result
    }

    class RationalRange(val startRational: Rational, val endRational: Rational) {
        operator fun contains(rational: Rational) = rational >= startRational && rational <= endRational
    }
}

infix fun <T : Number> T.divBy(denominator: T) = Rational(this, denominator)

fun String.toRational() : Rational {
    fun String.toBigIntegerOrFail() = toBigIntegerOrNull() ?:
    throw IllegalArgumentException("${this@toRational} cannot be converted to a Rational")

    if (!contains("/")) return Rational(toBigIntegerOrFail())
    val (elem1, elem2) = split("/")
    return Rational(elem1.toBigIntegerOrFail(), elem2.toBigIntegerOrFail())
}

fun Int.toRational() = Rational(this)

fun main() {
    "a/f".toRational()
    val half = 1 divBy 2
    val third = 1 divBy 3

    val sum: Rational = half + third
    println(5 divBy 6 == sum)

    val difference: Rational = half - third
    println(1 divBy 6 == difference)

    val product: Rational = half * third
    println(1 divBy 6 == product)

    val quotient: Rational = half / third
    println(3 divBy 2 == quotient)

    val negation: Rational = -half
    println(-1 divBy 2 == negation)

    println((2 divBy 1).toString() == "2")
    println((-2 divBy 4).toString() == "-1/2")
    println("117/1098".toRational().toString() == "13/122")

    val twoThirds = 2 divBy 3
    println(half < twoThirds)

    println(half in third..twoThirds)

    println(3.toRational().toString() == "3")

    println(2000000000L divBy 4000000000L == 1 divBy 2)

    println("912016490186296920119201192141970416029".toBigInteger() divBy
            "1824032980372593840238402384283940832058".toBigInteger() == 1 divBy 2)
}

Compiled from "Rational.kt"
public final class rationals.Rational$RationalRange {
  private final rationals.Rational startRational;

  private final rationals.Rational endRational;

  public rationals.Rational$RationalRange(rationals.Rational, rationals.Rational);
    Code:
       0: aload_1
       1: ldc           #9                  // String startRational
       3: invokestatic  #15                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_2
       7: ldc           #17                 // String endRational
       9: invokestatic  #15                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
      12: aload_0
      13: invokespecial #20                 // Method java/lang/Object."<init>":()V
      16: aload_0
      17: aload_1
      18: putfield      #23                 // Field startRational:Lrationals/Rational;
      21: aload_0
      22: aload_2
      23: putfield      #25                 // Field endRational:Lrationals/Rational;
      26: return

  public final rationals.Rational getStartRational();
    Code:
       0: aload_0
       1: getfield      #23                 // Field startRational:Lrationals/Rational;
       4: areturn

  public final rationals.Rational getEndRational();
    Code:
       0: aload_0
       1: getfield      #25                 // Field endRational:Lrationals/Rational;
       4: areturn

  public final boolean contains(rationals.Rational);
    Code:
       0: aload_1
       1: ldc           #34                 // String rational
       3: invokestatic  #15                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_1
       7: aload_0
       8: getfield      #23                 // Field startRational:Lrationals/Rational;
      11: invokevirtual #40                 // Method rationals/Rational.compareTo:(Lrationals/Rational;)I
      14: iflt          32
      17: aload_1
      18: aload_0
      19: getfield      #25                 // Field endRational:Lrationals/Rational;
      22: invokevirtual #40                 // Method rationals/Rational.compareTo:(Lrationals/Rational;)I
      25: ifgt          32
      28: iconst_1
      29: goto          33
      32: iconst_0
      33: ireturn
}

Compiled from "Rational.kt"
public final class rationals.RationalKt {
  public static final <T extends java.lang.Number> rationals.Rational divBy(T, T);
    Code:
       0: aload_0
       1: ldc           #10                 // String <this>
       3: invokestatic  #16                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_1
       7: ldc           #18                 // String denominator
       9: invokestatic  #16                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
      12: new           #20                 // class rationals/Rational
      15: dup
      16: aload_0
      17: aload_1
      18: invokespecial #24                 // Method rationals/Rational."<init>":(Ljava/lang/Number;Ljava/lang/Number;)V
      21: areturn

  public static final rationals.Rational toRational(java.lang.String);
    Code:
       0: aload_0
       1: ldc           #10                 // String <this>
       3: invokestatic  #16                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_0
       7: checkcast     #30                 // class java/lang/CharSequence
      10: ldc           #32                 // String /
      12: checkcast     #30                 // class java/lang/CharSequence
      15: iconst_0
      16: iconst_2
      17: aconst_null
      18: invokestatic  #38                 // Method kotlin/text/StringsKt.contains$default:(Ljava/lang/CharSequence;Ljava/lang/CharSequence;ZILjava/lang/Object;)Z
      21: ifne          37
      24: new           #20                 // class rationals/Rational
      27: dup
      28: aload_0
      29: aload_0
      30: invokestatic  #42                 // Method toRational$toBigIntegerOrFail:(Ljava/lang/String;Ljava/lang/String;)Ljava/math/BigInteger;
      33: invokespecial #45                 // Method rationals/Rational."<init>":(Ljava/math/BigInteger;)V
      36: areturn
      37: aload_0
      38: checkcast     #30                 // class java/lang/CharSequence
      41: iconst_1
      42: anewarray     #47                 // class java/lang/String
      45: astore_2
      46: aload_2
      47: iconst_0
      48: ldc           #32                 // String /
      50: aastore
      51: aload_2
      52: iconst_0
      53: iconst_0
      54: bipush        6
      56: aconst_null
      57: invokestatic  #51                 // Method kotlin/text/StringsKt.split$default:(Ljava/lang/CharSequence;[Ljava/lang/String;ZIILjava/lang/Object;)Ljava/util/List;
      60: astore_1
      61: aload_1
      62: iconst_0
      63: invokeinterface #57,  2           // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
      68: checkcast     #47                 // class java/lang/String
      71: astore_2
      72: aload_1
      73: iconst_1
      74: invokeinterface #57,  2           // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
      79: checkcast     #47                 // class java/lang/String
      82: astore_3
      83: new           #20                 // class rationals/Rational
      86: dup
      87: aload_2
      88: aload_0
      89: invokestatic  #42                 // Method toRational$toBigIntegerOrFail:(Ljava/lang/String;Ljava/lang/String;)Ljava/math/BigInteger;
      92: aload_3
      93: aload_0
      94: invokestatic  #42                 // Method toRational$toBigIntegerOrFail:(Ljava/lang/String;Ljava/lang/String;)Ljava/math/BigInteger;
      97: invokespecial #60                 // Method rationals/Rational."<init>":(Ljava/math/BigInteger;Ljava/math/BigInteger;)V
     100: areturn

  public static final rationals.Rational toRational(int);
    Code:
       0: new           #20                 // class rationals/Rational
       3: dup
       4: iload_0
       5: invokestatic  #71                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       8: checkcast     #73                 // class java/lang/Number
      11: invokespecial #76                 // Method rationals/Rational."<init>":(Ljava/lang/Number;)V
      14: areturn

  public static final void main();
    Code:
       0: ldc           #81                 // String a/f
       2: invokestatic  #83                 // Method toRational:(Ljava/lang/String;)Lrationals/Rational;
       5: pop
       6: iconst_1
       7: invokestatic  #71                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      10: checkcast     #73                 // class java/lang/Number
      13: iconst_2
      14: invokestatic  #71                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      17: checkcast     #73                 // class java/lang/Number
      20: invokestatic  #85                 // Method divBy:(Ljava/lang/Number;Ljava/lang/Number;)Lrationals/Rational;
      23: astore_0
      24: iconst_1
      25: invokestatic  #71                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      28: checkcast     #73                 // class java/lang/Number
      31: iconst_3
      32: invokestatic  #71                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      35: checkcast     #73                 // class java/lang/Number
      38: invokestatic  #85                 // Method divBy:(Ljava/lang/Number;Ljava/lang/Number;)Lrationals/Rational;
      41: astore_1
      42: aload_0
      43: aload_1
      44: invokevirtual #89                 // Method rationals/Rational.plus:(Lrationals/Rational;)Lrationals/Rational;
      47: astore_2
      48: iconst_5
      49: invokestatic  #71                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      52: checkcast     #73                 // class java/lang/Number
      55: bipush        6
      57: invokestatic  #71                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      60: checkcast     #73                 // class java/lang/Number
      63: invokestatic  #85                 // Method divBy:(Ljava/lang/Number;Ljava/lang/Number;)Lrationals/Rational;
      66: aload_2
      67: invokestatic  #93                 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z
      70: istore_3
      71: getstatic     #99                 // Field java/lang/System.out:Ljava/io/PrintStream;
      74: iload_3
      75: invokevirtual #105                // Method java/io/PrintStream.println:(Z)V
      78: aload_0
      79: aload_1
      80: invokevirtual #108                // Method rationals/Rational.minus:(Lrationals/Rational;)Lrationals/Rational;
      83: astore_3
      84: iconst_1
      85: invokestatic  #71                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      88: checkcast     #73                 // class java/lang/Number
      91: bipush        6
      93: invokestatic  #71                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      96: checkcast     #73                 // class java/lang/Number
      99: invokestatic  #85                 // Method divBy:(Ljava/lang/Number;Ljava/lang/Number;)Lrationals/Rational;
     102: aload_3
     103: invokestatic  #93                 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z
     106: istore        4
     108: getstatic     #99                 // Field java/lang/System.out:Ljava/io/PrintStream;
     111: iload         4
     113: invokevirtual #105                // Method java/io/PrintStream.println:(Z)V
     116: aload_0
     117: aload_1
     118: invokevirtual #111                // Method rationals/Rational.times:(Lrationals/Rational;)Lrationals/Rational;
     121: astore        4
     123: iconst_1
     124: invokestatic  #71                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     127: checkcast     #73                 // class java/lang/Number
     130: bipush        6
     132: invokestatic  #71                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     135: checkcast     #73                 // class java/lang/Number
     138: invokestatic  #85                 // Method divBy:(Ljava/lang/Number;Ljava/lang/Number;)Lrationals/Rational;
     141: aload         4
     143: invokestatic  #93                 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z
     146: istore        5
     148: getstatic     #99                 // Field java/lang/System.out:Ljava/io/PrintStream;
     151: iload         5
     153: invokevirtual #105                // Method java/io/PrintStream.println:(Z)V
     156: aload_0
     157: aload_1
     158: invokevirtual #114                // Method rationals/Rational.div:(Lrationals/Rational;)Lrationals/Rational;
     161: astore        5
     163: iconst_3
     164: invokestatic  #71                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     167: checkcast     #73                 // class java/lang/Number
     170: iconst_2
     171: invokestatic  #71                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     174: checkcast     #73                 // class java/lang/Number
     177: invokestatic  #85                 // Method divBy:(Ljava/lang/Number;Ljava/lang/Number;)Lrationals/Rational;
     180: aload         5
     182: invokestatic  #93                 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z
     185: istore        6
     187: getstatic     #99                 // Field java/lang/System.out:Ljava/io/PrintStream;
     190: iload         6
     192: invokevirtual #105                // Method java/io/PrintStream.println:(Z)V
     195: aload_0
     196: invokevirtual #118                // Method rationals/Rational.unaryMinus:()Lrationals/Rational;
     199: astore        6
     201: iconst_m1
     202: invokestatic  #71                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     205: checkcast     #73                 // class java/lang/Number
     208: iconst_2
     209: invokestatic  #71                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     212: checkcast     #73                 // class java/lang/Number
     215: invokestatic  #85                 // Method divBy:(Ljava/lang/Number;Ljava/lang/Number;)Lrationals/Rational;
     218: aload         6
     220: invokestatic  #93                 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z
     223: istore        7
     225: getstatic     #99                 // Field java/lang/System.out:Ljava/io/PrintStream;
     228: iload         7
     230: invokevirtual #105                // Method java/io/PrintStream.println:(Z)V
     233: iconst_2
     234: invokestatic  #71                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     237: checkcast     #73                 // class java/lang/Number
     240: iconst_1
     241: invokestatic  #71                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     244: checkcast     #73                 // class java/lang/Number
     247: invokestatic  #85                 // Method divBy:(Ljava/lang/Number;Ljava/lang/Number;)Lrationals/Rational;
     250: invokevirtual #122                // Method rationals/Rational.toString:()Ljava/lang/String;
     253: ldc           #124                // String 2
     255: invokestatic  #93                 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z
     258: istore        7
     260: getstatic     #99                 // Field java/lang/System.out:Ljava/io/PrintStream;
     263: iload         7
     265: invokevirtual #105                // Method java/io/PrintStream.println:(Z)V
     268: bipush        -2
     270: invokestatic  #71                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     273: checkcast     #73                 // class java/lang/Number
     276: iconst_4
     277: invokestatic  #71                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     280: checkcast     #73                 // class java/lang/Number
     283: invokestatic  #85                 // Method divBy:(Ljava/lang/Number;Ljava/lang/Number;)Lrationals/Rational;
     286: invokevirtual #122                // Method rationals/Rational.toString:()Ljava/lang/String;
     289: ldc           #126                // String -1/2
     291: invokestatic  #93                 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z
     294: istore        7
     296: getstatic     #99                 // Field java/lang/System.out:Ljava/io/PrintStream;
     299: iload         7
     301: invokevirtual #105                // Method java/io/PrintStream.println:(Z)V
     304: ldc           #128                // String 117/1098
     306: invokestatic  #83                 // Method toRational:(Ljava/lang/String;)Lrationals/Rational;
     309: invokevirtual #122                // Method rationals/Rational.toString:()Ljava/lang/String;
     312: ldc           #130                // String 13/122
     314: invokestatic  #93                 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z
     317: istore        7
     319: getstatic     #99                 // Field java/lang/System.out:Ljava/io/PrintStream;
     322: iload         7
     324: invokevirtual #105                // Method java/io/PrintStream.println:(Z)V
     327: iconst_2
     328: invokestatic  #71                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     331: checkcast     #73                 // class java/lang/Number
     334: iconst_3
     335: invokestatic  #71                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     338: checkcast     #73                 // class java/lang/Number
     341: invokestatic  #85                 // Method divBy:(Ljava/lang/Number;Ljava/lang/Number;)Lrationals/Rational;
     344: astore        7
     346: aload_0
     347: aload         7
     349: invokevirtual #134                // Method rationals/Rational.compareTo:(Lrationals/Rational;)I
     352: ifge          359
     355: iconst_1
     356: goto          360
     359: iconst_0
     360: istore        8
     362: getstatic     #99                 // Field java/lang/System.out:Ljava/io/PrintStream;
     365: iload         8
     367: invokevirtual #105                // Method java/io/PrintStream.println:(Z)V
     370: aload_1
     371: aload         7
     373: invokevirtual #138                // Method rationals/Rational.rangeTo:(Lrationals/Rational;)Lrationals/Rational$RationalRange;
     376: aload_0
     377: invokevirtual #144                // Method rationals/Rational$RationalRange.contains:(Lrationals/Rational;)Z
     380: istore        8
     382: getstatic     #99                 // Field java/lang/System.out:Ljava/io/PrintStream;
     385: iload         8
     387: invokevirtual #105                // Method java/io/PrintStream.println:(Z)V
     390: iconst_3
     391: invokestatic  #146                // Method toRational:(I)Lrationals/Rational;
     394: invokevirtual #122                // Method rationals/Rational.toString:()Ljava/lang/String;
     397: ldc           #148                // String 3
     399: invokestatic  #93                 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z
     402: istore        8
     404: getstatic     #99                 // Field java/lang/System.out:Ljava/io/PrintStream;
     407: iload         8
     409: invokevirtual #105                // Method java/io/PrintStream.println:(Z)V
     412: ldc2_w        #149                // long 2000000000l
     415: invokestatic  #155                // Method java/lang/Long.valueOf:(J)Ljava/lang/Long;
     418: checkcast     #73                 // class java/lang/Number
     421: ldc2_w        #156                // long 4000000000l
     424: invokestatic  #155                // Method java/lang/Long.valueOf:(J)Ljava/lang/Long;
     427: checkcast     #73                 // class java/lang/Number
     430: invokestatic  #85                 // Method divBy:(Ljava/lang/Number;Ljava/lang/Number;)Lrationals/Rational;
     433: iconst_1
     434: invokestatic  #71                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     437: checkcast     #73                 // class java/lang/Number
     440: iconst_2
     441: invokestatic  #71                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     444: checkcast     #73                 // class java/lang/Number
     447: invokestatic  #85                 // Method divBy:(Ljava/lang/Number;Ljava/lang/Number;)Lrationals/Rational;
     450: invokestatic  #93                 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z
     453: istore        8
     455: getstatic     #99                 // Field java/lang/System.out:Ljava/io/PrintStream;
     458: iload         8
     460: invokevirtual #105                // Method java/io/PrintStream.println:(Z)V
     463: new           #159                // class java/math/BigInteger
     466: dup
     467: ldc           #161                // String 912016490186296920119201192141970416029
     469: invokespecial #164                // Method java/math/BigInteger."<init>":(Ljava/lang/String;)V
     472: checkcast     #73                 // class java/lang/Number
     475: new           #159                // class java/math/BigInteger
     478: dup
     479: ldc           #166                // String 1824032980372593840238402384283940832058
     481: invokespecial #164                // Method java/math/BigInteger."<init>":(Ljava/lang/String;)V
     484: checkcast     #73                 // class java/lang/Number
     487: invokestatic  #85                 // Method divBy:(Ljava/lang/Number;Ljava/lang/Number;)Lrationals/Rational;
     490: iconst_1
     491: invokestatic  #71                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     494: checkcast     #73                 // class java/lang/Number
     497: iconst_2
     498: invokestatic  #71                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     501: checkcast     #73                 // class java/lang/Number
     504: invokestatic  #85                 // Method divBy:(Ljava/lang/Number;Ljava/lang/Number;)Lrationals/Rational;
     507: invokestatic  #93                 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z
     510: istore        8
     512: getstatic     #99                 // Field java/lang/System.out:Ljava/io/PrintStream;
     515: iload         8
     517: invokevirtual #105                // Method java/io/PrintStream.println:(Z)V
     520: return

  public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #178                // Method main:()V
       3: return

  private static final java.math.BigInteger toRational$toBigIntegerOrFail(java.lang.String, java.lang.String);
    Code:
       0: aload_0
       1: invokestatic  #184                // Method kotlin/text/StringsKt.toBigIntegerOrNull:(Ljava/lang/String;)Ljava/math/BigInteger;
       4: dup
       5: ifnonnull     36
       8: pop
       9: new           #186                // class java/lang/IllegalArgumentException
      12: dup
      13: new           #188                // class java/lang/StringBuilder
      16: dup
      17: invokespecial #190                // Method java/lang/StringBuilder."<init>":()V
      20: aload_1
      21: invokevirtual #194                // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      24: ldc           #196                // String  cannot be converted to a Rational
      26: invokevirtual #194                // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      29: invokevirtual #197                // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      32: invokespecial #198                // Method java/lang/IllegalArgumentException."<init>":(Ljava/lang/String;)V
      35: athrow
      36: areturn
}

Compiled from "Rational.kt"
public final class rationals.Rational {
  private final java.math.BigInteger numerator;

  private final java.math.BigInteger denominator;

  public rationals.Rational(java.math.BigInteger);
    Code:
       0: aload_1
       1: ldc           #9                  // String numerator
       3: invokestatic  #15                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_0
       7: aload_1
       8: getstatic     #21                 // Field java/math/BigInteger.ONE:Ljava/math/BigInteger;
      11: dup
      12: ldc           #22                 // String ONE
      14: invokestatic  #25                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullExpressionValue:(Ljava/lang/Object;Ljava/lang/String;)V
      17: invokespecial #28                 // Method "<init>":(Ljava/math/BigInteger;Ljava/math/BigInteger;)V
      20: return

  public rationals.Rational(java.lang.Number);
    Code:
       0: aload_1
       1: ldc           #9                  // String numerator
       3: invokestatic  #15                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_0
       7: aload_1
       8: iconst_1
       9: invokestatic  #37                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      12: checkcast     #39                 // class java/lang/Number
      15: invokespecial #42                 // Method "<init>":(Ljava/lang/Number;Ljava/lang/Number;)V
      18: return

  public rationals.Rational(java.lang.Number, java.lang.Number);
    Code:
       0: aload_1
       1: ldc           #9                  // String numerator
       3: invokestatic  #15                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_2
       7: ldc           #45                 // String denominator
       9: invokestatic  #15                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
      12: aload_0
      13: aload_1
      14: invokevirtual #49                 // Method java/lang/Number.longValue:()J
      17: invokestatic  #52                 // Method java/math/BigInteger.valueOf:(J)Ljava/math/BigInteger;
      20: dup
      21: ldc           #54                 // String valueOf(...)
      23: invokestatic  #25                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullExpressionValue:(Ljava/lang/Object;Ljava/lang/String;)V
      26: aload_2
      27: invokevirtual #49                 // Method java/lang/Number.longValue:()J
      30: invokestatic  #52                 // Method java/math/BigInteger.valueOf:(J)Ljava/math/BigInteger;
      33: dup
      34: ldc           #54                 // String valueOf(...)
      36: invokestatic  #25                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullExpressionValue:(Ljava/lang/Object;Ljava/lang/String;)V
      39: invokespecial #28                 // Method "<init>":(Ljava/math/BigInteger;Ljava/math/BigInteger;)V
      42: return

  public rationals.Rational(java.math.BigInteger, java.math.BigInteger);
    Code:
       0: aload_1
       1: ldc           #9                  // String numerator
       3: invokestatic  #15                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_2
       7: ldc           #45                 // String denominator
       9: invokestatic  #15                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
      12: aload_0
      13: invokespecial #57                 // Method java/lang/Object."<init>":()V
      16: aload_2
      17: getstatic     #60                 // Field java/math/BigInteger.ZERO:Ljava/math/BigInteger;
      20: invokestatic  #64                 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z
      23: ifeq          36
      26: new           #66                 // class java/lang/IllegalArgumentException
      29: dup
      30: ldc           #68                 // String Denominator cannot be zero
      32: invokespecial #71                 // Method java/lang/IllegalArgumentException."<init>":(Ljava/lang/String;)V
      35: athrow
      36: aload_2
      37: invokevirtual #75                 // Method java/math/BigInteger.signum:()I
      40: i2l
      41: invokestatic  #52                 // Method java/math/BigInteger.valueOf:(J)Ljava/math/BigInteger;
      44: dup
      45: ldc           #54                 // String valueOf(...)
      47: invokestatic  #25                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullExpressionValue:(Ljava/lang/Object;Ljava/lang/String;)V
      50: astore_3
      51: aload_3
      52: aload_1
      53: invokevirtual #79                 // Method java/math/BigInteger.multiply:(Ljava/math/BigInteger;)Ljava/math/BigInteger;
      56: dup
      57: ldc           #81                 // String multiply(...)
      59: invokestatic  #25                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullExpressionValue:(Ljava/lang/Object;Ljava/lang/String;)V
      62: astore        4
      64: aload_3
      65: aload_2
      66: invokevirtual #79                 // Method java/math/BigInteger.multiply:(Ljava/math/BigInteger;)Ljava/math/BigInteger;
      69: dup
      70: ldc           #81                 // String multiply(...)
      72: invokestatic  #25                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullExpressionValue:(Ljava/lang/Object;Ljava/lang/String;)V
      75: astore        5
      77: aload         4
      79: aload         5
      81: invokevirtual #84                 // Method java/math/BigInteger.gcd:(Ljava/math/BigInteger;)Ljava/math/BigInteger;
      84: astore        6
      86: aload_0
      87: aload         4
      89: aload         6
      91: invokevirtual #87                 // Method java/math/BigInteger.divide:(Ljava/math/BigInteger;)Ljava/math/BigInteger;
      94: dup
      95: ldc           #89                 // String divide(...)
      97: invokestatic  #25                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullExpressionValue:(Ljava/lang/Object;Ljava/lang/String;)V
     100: putfield      #91                 // Field numerator:Ljava/math/BigInteger;
     103: aload_0
     104: aload         5
     106: aload         6
     108: invokevirtual #87                 // Method java/math/BigInteger.divide:(Ljava/math/BigInteger;)Ljava/math/BigInteger;
     111: dup
     112: ldc           #89                 // String divide(...)
     114: invokestatic  #25                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullExpressionValue:(Ljava/lang/Object;Ljava/lang/String;)V
     117: putfield      #93                 // Field denominator:Ljava/math/BigInteger;
     120: return

  public final rationals.Rational plus(rationals.Rational);
    Code:
       0: aload_1
       1: ldc           #100                // String that
       3: invokestatic  #15                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: new           #2                  // class rationals/Rational
       9: dup
      10: nop
      11: aload_0
      12: getfield      #91                 // Field numerator:Ljava/math/BigInteger;
      15: aload_1
      16: getfield      #93                 // Field denominator:Ljava/math/BigInteger;
      19: invokevirtual #79                 // Method java/math/BigInteger.multiply:(Ljava/math/BigInteger;)Ljava/math/BigInteger;
      22: dup
      23: ldc           #81                 // String multiply(...)
      25: invokestatic  #25                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullExpressionValue:(Ljava/lang/Object;Ljava/lang/String;)V
      28: aload_1
      29: getfield      #91                 // Field numerator:Ljava/math/BigInteger;
      32: aload_0
      33: getfield      #93                 // Field denominator:Ljava/math/BigInteger;
      36: invokevirtual #79                 // Method java/math/BigInteger.multiply:(Ljava/math/BigInteger;)Ljava/math/BigInteger;
      39: dup
      40: ldc           #81                 // String multiply(...)
      42: invokestatic  #25                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullExpressionValue:(Ljava/lang/Object;Ljava/lang/String;)V
      45: invokevirtual #103                // Method java/math/BigInteger.add:(Ljava/math/BigInteger;)Ljava/math/BigInteger;
      48: dup
      49: ldc           #105                // String add(...)
      51: invokestatic  #25                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullExpressionValue:(Ljava/lang/Object;Ljava/lang/String;)V
      54: aload_0
      55: getfield      #93                 // Field denominator:Ljava/math/BigInteger;
      58: aload_1
      59: getfield      #93                 // Field denominator:Ljava/math/BigInteger;
      62: invokevirtual #79                 // Method java/math/BigInteger.multiply:(Ljava/math/BigInteger;)Ljava/math/BigInteger;
      65: dup
      66: ldc           #81                 // String multiply(...)
      68: invokestatic  #25                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullExpressionValue:(Ljava/lang/Object;Ljava/lang/String;)V
      71: invokespecial #28                 // Method "<init>":(Ljava/math/BigInteger;Ljava/math/BigInteger;)V
      74: areturn

  public final rationals.Rational minus(rationals.Rational);
    Code:
       0: aload_1
       1: ldc           #100                // String that
       3: invokestatic  #15                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_0
       7: aload_1
       8: invokevirtual #110                // Method unaryMinus:()Lrationals/Rational;
      11: invokevirtual #112                // Method plus:(Lrationals/Rational;)Lrationals/Rational;
      14: areturn

  public final rationals.Rational times(rationals.Rational);
    Code:
       0: aload_1
       1: ldc           #100                // String that
       3: invokestatic  #15                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: new           #2                  // class rationals/Rational
       9: dup
      10: aload_0
      11: getfield      #91                 // Field numerator:Ljava/math/BigInteger;
      14: aload_1
      15: getfield      #91                 // Field numerator:Ljava/math/BigInteger;
      18: invokevirtual #79                 // Method java/math/BigInteger.multiply:(Ljava/math/BigInteger;)Ljava/math/BigInteger;
      21: dup
      22: ldc           #81                 // String multiply(...)
      24: invokestatic  #25                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullExpressionValue:(Ljava/lang/Object;Ljava/lang/String;)V
      27: aload_0
      28: getfield      #93                 // Field denominator:Ljava/math/BigInteger;
      31: aload_1
      32: getfield      #93                 // Field denominator:Ljava/math/BigInteger;
      35: invokevirtual #79                 // Method java/math/BigInteger.multiply:(Ljava/math/BigInteger;)Ljava/math/BigInteger;
      38: dup
      39: ldc           #81                 // String multiply(...)
      41: invokestatic  #25                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullExpressionValue:(Ljava/lang/Object;Ljava/lang/String;)V
      44: invokespecial #28                 // Method "<init>":(Ljava/math/BigInteger;Ljava/math/BigInteger;)V
      47: areturn

  public final rationals.Rational div(rationals.Rational);
    Code:
       0: aload_1
       1: ldc           #100                // String that
       3: invokestatic  #15                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: new           #2                  // class rationals/Rational
       9: dup
      10: aload_0
      11: getfield      #91                 // Field numerator:Ljava/math/BigInteger;
      14: aload_1
      15: getfield      #93                 // Field denominator:Ljava/math/BigInteger;
      18: invokevirtual #79                 // Method java/math/BigInteger.multiply:(Ljava/math/BigInteger;)Ljava/math/BigInteger;
      21: dup
      22: ldc           #81                 // String multiply(...)
      24: invokestatic  #25                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullExpressionValue:(Ljava/lang/Object;Ljava/lang/String;)V
      27: aload_0
      28: getfield      #93                 // Field denominator:Ljava/math/BigInteger;
      31: aload_1
      32: getfield      #91                 // Field numerator:Ljava/math/BigInteger;
      35: invokevirtual #79                 // Method java/math/BigInteger.multiply:(Ljava/math/BigInteger;)Ljava/math/BigInteger;
      38: dup
      39: ldc           #81                 // String multiply(...)
      41: invokestatic  #25                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullExpressionValue:(Ljava/lang/Object;Ljava/lang/String;)V
      44: invokespecial #28                 // Method "<init>":(Ljava/math/BigInteger;Ljava/math/BigInteger;)V
      47: areturn

  public final rationals.Rational unaryMinus();
    Code:
       0: new           #2                  // class rationals/Rational
       3: dup
       4: aload_0
       5: getfield      #91                 // Field numerator:Ljava/math/BigInteger;
       8: invokevirtual #118                // Method java/math/BigInteger.negate:()Ljava/math/BigInteger;
      11: dup
      12: ldc           #120                // String negate(...)
      14: invokestatic  #25                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullExpressionValue:(Ljava/lang/Object;Ljava/lang/String;)V
      17: aload_0
      18: getfield      #93                 // Field denominator:Ljava/math/BigInteger;
      21: invokespecial #28                 // Method "<init>":(Ljava/math/BigInteger;Ljava/math/BigInteger;)V
      24: areturn

  public final int compareTo(rationals.Rational);
    Code:
       0: aload_1
       1: ldc           #100                // String that
       3: invokestatic  #15                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: nop
       7: aload_0
       8: getfield      #91                 // Field numerator:Ljava/math/BigInteger;
      11: aload_1
      12: getfield      #93                 // Field denominator:Ljava/math/BigInteger;
      15: invokevirtual #79                 // Method java/math/BigInteger.multiply:(Ljava/math/BigInteger;)Ljava/math/BigInteger;
      18: dup
      19: ldc           #81                 // String multiply(...)
      21: invokestatic  #25                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullExpressionValue:(Ljava/lang/Object;Ljava/lang/String;)V
      24: aload_1
      25: getfield      #91                 // Field numerator:Ljava/math/BigInteger;
      28: aload_0
      29: getfield      #93                 // Field denominator:Ljava/math/BigInteger;
      32: invokevirtual #79                 // Method java/math/BigInteger.multiply:(Ljava/math/BigInteger;)Ljava/math/BigInteger;
      35: dup
      36: ldc           #81                 // String multiply(...)
      38: invokestatic  #25                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullExpressionValue:(Ljava/lang/Object;Ljava/lang/String;)V
      41: invokevirtual #125                // Method java/math/BigInteger.subtract:(Ljava/math/BigInteger;)Ljava/math/BigInteger;
      44: dup
      45: ldc           #127                // String subtract(...)
      47: invokestatic  #25                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullExpressionValue:(Ljava/lang/Object;Ljava/lang/String;)V
      50: invokevirtual #75                 // Method java/math/BigInteger.signum:()I
      53: 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 rationals/Rational
      11: ifne          16
      14: iconst_0
      15: ireturn
      16: aload_0
      17: getfield      #91                 // Field numerator:Ljava/math/BigInteger;
      20: aload_1
      21: checkcast     #2                  // class rationals/Rational
      24: getfield      #91                 // Field numerator:Ljava/math/BigInteger;
      27: invokestatic  #64                 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z
      30: ifeq          54
      33: aload_0
      34: getfield      #93                 // Field denominator:Ljava/math/BigInteger;
      37: aload_1
      38: checkcast     #2                  // class rationals/Rational
      41: getfield      #93                 // Field denominator:Ljava/math/BigInteger;
      44: invokestatic  #64                 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z
      47: ifeq          54
      50: iconst_1
      51: goto          55
      54: iconst_0
      55: ireturn

  public java.lang.String toString();
    Code:
       0: aload_0
       1: getfield      #93                 // Field denominator:Ljava/math/BigInteger;
       4: getstatic     #21                 // Field java/math/BigInteger.ONE:Ljava/math/BigInteger;
       7: invokestatic  #64                 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z
      10: ifeq          23
      13: aload_0
      14: getfield      #91                 // Field numerator:Ljava/math/BigInteger;
      17: invokestatic  #139                // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
      20: goto          52
      23: new           #141                // class java/lang/StringBuilder
      26: dup
      27: invokespecial #142                // Method java/lang/StringBuilder."<init>":()V
      30: aload_0
      31: getfield      #91                 // Field numerator:Ljava/math/BigInteger;
      34: invokevirtual #146                // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
      37: bipush        47
      39: invokevirtual #149                // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder;
      42: aload_0
      43: getfield      #93                 // Field denominator:Ljava/math/BigInteger;
      46: invokevirtual #146                // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
      49: invokevirtual #151                // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      52: areturn

  public final rationals.Rational$RationalRange rangeTo(rationals.Rational);
    Code:
       0: aload_1
       1: ldc           #100                // String that
       3: invokestatic  #15                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: new           #155                // class rationals/Rational$RationalRange
       9: dup
      10: aload_0
      11: aload_1
      12: invokespecial #158                // Method rationals/Rational$RationalRange."<init>":(Lrationals/Rational;Lrationals/Rational;)V
      15: areturn

  public int hashCode();
    Code:
       0: aload_0
       1: getfield      #91                 // Field numerator:Ljava/math/BigInteger;
       4: invokevirtual #161                // Method java/math/BigInteger.hashCode:()I
       7: istore_1
       8: bipush        31
      10: iload_1
      11: imul
      12: aload_0
      13: getfield      #93                 // Field denominator:Ljava/math/BigInteger;
      16: invokevirtual #161                // Method java/math/BigInteger.hashCode:()I
      19: iadd
      20: istore_1
      21: iload_1
      22: ireturn
}


package org.example.e3fxgaming.wortproblem

fun main() {
    val fileContent = InputReader::class.java.getResource("/inputChomskyNF.txt")?.readText() ?: throw Exception("No rules found")

    val inputReader = InputReader(fileContent)

    val firstTestWord = "abaaba"
    val secondTestWord = "abbbba"
    val thirdTestWord = "bbabbbaabbaabbbabb"
    val falseWord = "aaabbbb"

    val solver = Solver(inputReader)
    println("$firstTestWord: ${solver.solve(firstTestWord)}")
    println("$secondTestWord: ${solver.solve(secondTestWord)}")
    println("$thirdTestWord: ${solver.solve(thirdTestWord)}")
    println("$falseWord: ${solver.solve(falseWord)}")

    println(inputReader)
}

/**
 * Class for reading the Chomsky NF input. Also holds the cache.
 */
class InputReader(fileContent: String) {
    //Maps second part of rule to all possible first parts
    private val rules = fileContent.split(System.getProperty("line.separator"))
        .map { it.split("->") }
        .map {
            Rule(it[0], it[1])
        }

    val reversedRules = rules.groupBy {
        it.productionOutput
    }

    //caching rule wrappers to speed up subsequent same-word-evaluations
    val cache: MutableMap<String, DerivationWrapper> = mutableMapOf()

    override fun toString(): String {
        return "Read ${rules.size } rules. Current cache size: ${cache.size} entries."
    }
}

/**
 * An element of the production system, that can transform a [variable] into a [productionOutput]
 */
data class Rule(val variable: String, val productionOutput: String) {
    override fun toString(): String {
        return "$variable->$productionOutput"
    }
}

/**
 * Since the same [word] may be derived in multiple ways, this DerivationWrapper bundles the possible [derivations].
 * Also stores information about its direct [left] and [right] DerivationWrapper descendants.
 *
 */
data class DerivationWrapper(val word: String, val derivations: List<DerivationOperation>, val left: DerivationWrapper?, val right: DerivationWrapper?) {
    fun getChild(childSelector: (DerivationWrapper) -> DerivationWrapper?, collectionList: MutableList<DerivationWrapper>): List<DerivationWrapper> {
        collectionList.add(this)
        childSelector(this)?.getChild(childSelector, collectionList)
        return collectionList
    }

    override fun toString(): String {
        return derivations.joinToString("\n")
    }
}

/**
 * Represents a single derivation A -> A B with [rule], where A ends at [firstSegmentEndsAtIndex].
 */
data class DerivationOperation(val rule: Rule, val firstSegmentEndsAtIndex: Int) {
    override fun toString(): String {
        return "$rule ($firstSegmentEndsAtIndex)"
    }
}

/**
 * Wrapper class for solving word problems.
 * [inputReader] provides the grammar information.
 */
class Solver(private val inputReader: InputReader) {

    /**
     * Tests whether a [word] string belongs to the language of the provided grammar.
     */
    fun solve(word: String): Boolean {
        val solutionPyramid: MutableList<List<DerivationWrapper>> = mutableListOf()

        //create foundation row of solutionPyramid
        word.map { wordChar ->
            val substring = wordChar.toString()

            inputReader.cache[substring]?.let {
                return@map it
            }

            val derivations = inputReader.reversedRules[substring]?.map { rule ->
                DerivationOperation(rule, 0)
            }.orEmpty()

            DerivationWrapper(substring, derivations, null, null).also {
                inputReader.cache[it.word] = it
            }
        }.let { firstRow ->
            solutionPyramid.add(firstRow)
        }

        //create top rows of solutionPyramid
        do {
            val lastRow = solutionPyramid.last()
            val combinationPairs = (0 until lastRow.lastIndex).map { indexFirstElement ->
                val leftDerivationWrapper = lastRow[indexFirstElement]
                val rightDerivationWrapper = lastRow[indexFirstElement + 1]
                val substring = word.substring(indexFirstElement, indexFirstElement + solutionPyramid.size + 1)
                combinePairs(leftDerivationWrapper, rightDerivationWrapper, substring)
            }
            solutionPyramid.add(combinationPairs)
        } while (solutionPyramid.last().size != 1)

        return solutionPyramid.last().first().derivations.any { it.rule.variable == "S" }
    }

    /**
     * Combines a [leftDerivationWrapper] and [rightDerivationWrapper] into a superior [DerivationWrapper] for a given [word]
     */
    private fun combinePairs(leftDerivationWrapper: DerivationWrapper, rightDerivationWrapper: DerivationWrapper, word: String): DerivationWrapper {
        inputReader.cache[word]?.let {
            return it
        }

        val leftDerivationWrappers = leftDerivationWrapper.getChild({it.left}, mutableListOf())
        val rightDerivationWrappers = rightDerivationWrapper.getChild({it.right}, mutableListOf())

        if(leftDerivationWrappers.size != rightDerivationWrappers.size) throw Exception("DerivationWrapper not balanced")

        val pairs = leftDerivationWrappers.zip(rightDerivationWrappers.reversed()).filterNot {
            it.first.derivations.isEmpty() || it.second.derivations.isEmpty()
        }

        val derivationOperations = pairs.map { derivationWrappers ->
            val firstProductionInputs = derivationWrappers.first.derivations.map { it.rule.variable }
            val secondProductionInputs = derivationWrappers.second.derivations.map { it.rule.variable }

            val productionPairs = firstProductionInputs.toPairList(secondProductionInputs).map { it.joinToString(" ") }

            val applicableRules = productionPairs.mapNotNull { productionPair ->
                inputReader.reversedRules[productionPair]
            }.flatten()

            applicableRules.map { DerivationOperation(it, derivationWrappers.first.word.length) }
        }.flatten()

        return DerivationWrapper(word, derivationOperations, leftDerivationWrapper, rightDerivationWrapper).also {
            inputReader.cache[it.word] = it
        }
    }
}

/**
 * Creates the Cartesian product of the elements of this List and a different [otherList] List.
 */
private fun List<String>.toPairList(otherList: List<String>) = flatMap { aItem ->
    otherList.map { bItem ->
        listOf(aItem, bItem) }
}

Compiled from "ExtendedCYKAlgorithm.kt"
public final class org.example.e3fxgaming.wortproblem.ExtendedCYKAlgorithmKt {
  public static final void main();
    Code:
       0: ldc           #8                  // class org/example/e3fxgaming/wortproblem/InputReader
       2: ldc           #10                 // String /inputChomskyNF.txt
       4: invokevirtual #16                 // Method java/lang/Class.getResource:(Ljava/lang/String;)Ljava/net/URL;
       7: dup
       8: ifnull        37
      11: astore_3
      12: getstatic     #22                 // Field kotlin/text/Charsets.UTF_8:Ljava/nio/charset/Charset;
      15: astore        4
      17: aload_3
      18: invokestatic  #28                 // Method kotlin/io/TextStreamsKt.readBytes:(Ljava/net/URL;)[B
      21: astore        5
      23: new           #30                 // class java/lang/String
      26: dup
      27: aload         5
      29: aload         4
      31: invokespecial #34                 // Method java/lang/String."<init>":([BLjava/nio/charset/Charset;)V
      34: goto          48
      37: pop
      38: new           #36                 // class java/lang/Exception
      41: dup
      42: ldc           #38                 // String No rules found
      44: invokespecial #41                 // Method java/lang/Exception."<init>":(Ljava/lang/String;)V
      47: athrow
      48: astore_0
      49: new           #8                  // class org/example/e3fxgaming/wortproblem/InputReader
      52: dup
      53: aload_0
      54: invokespecial #42                 // Method org/example/e3fxgaming/wortproblem/InputReader."<init>":(Ljava/lang/String;)V
      57: astore_1
      58: ldc           #44                 // String abaaba
      60: astore_2
      61: ldc           #46                 // String abbbba
      63: astore_3
      64: ldc           #48                 // String bbabbbaabbaabbbabb
      66: astore        4
      68: ldc           #50                 // String aaabbbb
      70: astore        5
      72: new           #52                 // class org/example/e3fxgaming/wortproblem/Solver
      75: dup
      76: aload_1
      77: invokespecial #55                 // Method org/example/e3fxgaming/wortproblem/Solver."<init>":(Lorg/example/e3fxgaming/wortproblem/InputReader;)V
      80: astore        6
      82: new           #57                 // class java/lang/StringBuilder
      85: dup
      86: invokespecial #59                 // Method java/lang/StringBuilder."<init>":()V
      89: aload_2
      90: invokevirtual #63                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      93: ldc           #65                 // String :
      95: invokevirtual #63                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      98: aload         6
     100: aload_2
     101: invokevirtual #69                 // Method org/example/e3fxgaming/wortproblem/Solver.solve:(Ljava/lang/String;)Z
     104: invokevirtual #72                 // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
     107: invokevirtual #76                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
     110: getstatic     #82                 // Field java/lang/System.out:Ljava/io/PrintStream;
     113: swap
     114: invokevirtual #88                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
     117: new           #57                 // class java/lang/StringBuilder
     120: dup
     121: invokespecial #59                 // Method java/lang/StringBuilder."<init>":()V
     124: aload_3
     125: invokevirtual #63                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
     128: ldc           #65                 // String :
     130: invokevirtual #63                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
     133: aload         6
     135: aload_3
     136: invokevirtual #69                 // Method org/example/e3fxgaming/wortproblem/Solver.solve:(Ljava/lang/String;)Z
     139: invokevirtual #72                 // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
     142: invokevirtual #76                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
     145: getstatic     #82                 // Field java/lang/System.out:Ljava/io/PrintStream;
     148: swap
     149: invokevirtual #88                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
     152: new           #57                 // class java/lang/StringBuilder
     155: dup
     156: invokespecial #59                 // Method java/lang/StringBuilder."<init>":()V
     159: aload         4
     161: invokevirtual #63                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
     164: ldc           #65                 // String :
     166: invokevirtual #63                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
     169: aload         6
     171: aload         4
     173: invokevirtual #69                 // Method org/example/e3fxgaming/wortproblem/Solver.solve:(Ljava/lang/String;)Z
     176: invokevirtual #72                 // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
     179: invokevirtual #76                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
     182: getstatic     #82                 // Field java/lang/System.out:Ljava/io/PrintStream;
     185: swap
     186: invokevirtual #88                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
     189: new           #57                 // class java/lang/StringBuilder
     192: dup
     193: invokespecial #59                 // Method java/lang/StringBuilder."<init>":()V
     196: aload         5
     198: invokevirtual #63                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
     201: ldc           #65                 // String :
     203: invokevirtual #63                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
     206: aload         6
     208: aload         5
     210: invokevirtual #69                 // Method org/example/e3fxgaming/wortproblem/Solver.solve:(Ljava/lang/String;)Z
     213: invokevirtual #72                 // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
     216: invokevirtual #76                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
     219: getstatic     #82                 // Field java/lang/System.out:Ljava/io/PrintStream;
     222: swap
     223: invokevirtual #88                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
     226: getstatic     #82                 // Field java/lang/System.out:Ljava/io/PrintStream;
     229: aload_1
     230: invokevirtual #88                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
     233: return

  private static final java.util.List<java.util.List<java.lang.String>> toPairList(java.util.List<java.lang.String>, java.util.List<java.lang.String>);
    Code:
       0: aload_0
       1: checkcast     #109                // class java/lang/Iterable
       4: astore_2
       5: iconst_0
       6: istore_3
       7: aload_2
       8: astore        4
      10: new           #111                // class java/util/ArrayList
      13: dup
      14: invokespecial #112                // Method java/util/ArrayList."<init>":()V
      17: checkcast     #114                // class java/util/Collection
      20: astore        5
      22: iconst_0
      23: istore        6
      25: aload         4
      27: invokeinterface #118,  1          // InterfaceMethod java/lang/Iterable.iterator:()Ljava/util/Iterator;
      32: astore        7
      34: aload         7
      36: invokeinterface #124,  1          // InterfaceMethod java/util/Iterator.hasNext:()Z
      41: ifeq          198
      44: aload         7
      46: invokeinterface #128,  1          // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
      51: astore        8
      53: aload         8
      55: checkcast     #30                 // class java/lang/String
      58: astore        9
      60: iconst_0
      61: istore        10
      63: aload_1
      64: checkcast     #109                // class java/lang/Iterable
      67: astore        11
      69: iconst_0
      70: istore        12
      72: aload         11
      74: astore        13
      76: new           #111                // class java/util/ArrayList
      79: dup
      80: aload         11
      82: bipush        10
      84: invokestatic  #134                // Method kotlin/collections/CollectionsKt.collectionSizeOrDefault:(Ljava/lang/Iterable;I)I
      87: invokespecial #137                // Method java/util/ArrayList."<init>":(I)V
      90: checkcast     #114                // class java/util/Collection
      93: astore        14
      95: iconst_0
      96: istore        15
      98: aload         13
     100: invokeinterface #118,  1          // InterfaceMethod java/lang/Iterable.iterator:()Ljava/util/Iterator;
     105: astore        16
     107: aload         16
     109: invokeinterface #124,  1          // InterfaceMethod java/util/Iterator.hasNext:()Z
     114: ifeq          175
     117: aload         16
     119: invokeinterface #128,  1          // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
     124: astore        17
     126: aload         14
     128: aload         17
     130: checkcast     #30                 // class java/lang/String
     133: astore        18
     135: astore        19
     137: iconst_0
     138: istore        20
     140: iconst_2
     141: anewarray     #30                 // class java/lang/String
     144: astore        21
     146: aload         21
     148: iconst_0
     149: aload         9
     151: aastore
     152: aload         21
     154: iconst_1
     155: aload         18
     157: aastore
     158: aload         21
     160: invokestatic  #141                // Method kotlin/collections/CollectionsKt.listOf:([Ljava/lang/Object;)Ljava/util/List;
     163: aload         19
     165: swap
     166: invokeinterface #145,  2          // InterfaceMethod java/util/Collection.add:(Ljava/lang/Object;)Z
     171: pop
     172: goto          107
     175: aload         14
     177: checkcast     #147                // class java/util/List
     180: nop
     181: checkcast     #109                // class java/lang/Iterable
     184: nop
     185: astore        9
     187: aload         5
     189: aload         9
     191: invokestatic  #151                // Method kotlin/collections/CollectionsKt.addAll:(Ljava/util/Collection;Ljava/lang/Iterable;)Z
     194: pop
     195: goto          34
     198: aload         5
     200: checkcast     #147                // class java/util/List
     203: nop
     204: areturn

  public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #177                // Method main:()V
       3: return

  public static final java.util.List access$toPairList(java.util.List, java.util.List);
    Code:
       0: aload_0
       1: aload_1
       2: invokestatic  #182                // Method toPairList:(Ljava/util/List;Ljava/util/List;)Ljava/util/List;
       5: areturn
}