Kotlin. Beats 100% (139 ms). OOP approach + gcd
\n\n\n# Code\nkotlin []\nclass Solution {\n    fun fractionAddition(expression: String): String {\n        FractionalNumberReader(expression).use { reader ->\n 
![image.png](https://assets.leetcode.com/users/images/7e58cb37-ddfc-4aeb-8ed4-3b048f17855d_1724436487.6424947.png)\n\n\n# Code\n```kotlin []\nclass Solution {\n    fun fractionAddition(expression: String): String {\n        FractionalNumberReader(expression).use { reader ->\n            var number = reader.parseFractionNumber()\n            while (reader.ready()) {\n                number = number.add(reader.parseFractionNumber())\n            }\n            return number.toString()\n        }\n    }\n\n    class FractionalNumber(private val m: Int, private val n: Int) {\n\n        fun add(other: FractionalNumber): FractionalNumber {\n            val newM = m * other.n + other.m * n\n            val newN = n * other.n\n            val gcd = gcd(newM, newN)\n            return FractionalNumber(\n                newM / gcd, \n                newN / gcd\n            )\n        }\n\n        private fun gcd(a: Int, b: Int): Int {\n            var a = a\n            var b = b\n            while (b != 0) {\n                val tmp = a % b\n                a = b\n                b = tmp\n            }\n            return a\n        }\n\n        override fun toString(): String {\n            return if (n < 0) "${-m}/${-n}" else "$m/$n"\n        }\n    }\n\n    class FractionalNumberReader(expression: String): AutoCloseable {\n\n        val stream = PushbackReader(InputStreamReader(expression.byteInputStream()))\n\n        fun ready(): Boolean {\n            return stream.ready()\n        }\n\n        fun parseFractionNumber(): FractionalNumber {\n            val m = if (parsePlus()) parseNumber() else -parseNumber()\n            parseSeparator()\n            val n = parseNumber()\n            return FractionalNumber(m, n)\n        }\n\n        private fun parsePlus(): Boolean = when (val char = stream.read()) {\n            \'-\'.code -> false\n            \'+\'.code -> true\n            else -> { stream.unread(char); true }\n        }\n\n        private fun parseSeparator() {\n            val char = stream.read()\n            if (char != \'/\'.code) throw java.lang.IllegalStateException("Unknown separator: ${char.toChar()}")\n        }\n\n        private fun parseNumber(): Int {\n            var number = 0\n            while (stream.ready()) {\n                val code = stream.read()\n                if (code >= \'0\'.code && code <= \'9\'.code) {\n                    number = number * 10 + (code - \'0\'.code)\n                } else {\n                    stream.unread(code)\n                    break\n                }\n            }\n            return number\n        }\n\n        override fun close() {\n            stream.close()\n        }\n    }\n}\n```\n\n# Approach \n\nThe main difficulty of this task is related to the large number of subtasks that arise at different levels.\n\nThe task requires:\n1) Parse each fraction\n2) Add the fractions\n3) Display correctly on the screen\n\n1. Parse each fraction\nFor parsing, I decided to use the standard mechanism - PushbackReader, which allows you to return the last read character. This is very convenient for situations when we expect a number during parsing, but we receive something else from the stream.\n\n2. Add fractions\nThere is nothing complicated here - we apply the standard rule of adding fractions, reducing the upper and lower parts of the fraction by the greatest common divisor. I find the greatest common divisor using the Euclidean algorithm.\n\n3. Display correctly on the screen\nIn the code, I did not bother with the signs that appear in the upper and lower parts of the fraction. But when returning the final result, a correction is necessary if the lower part of the fraction (n) turns out to be negative. If the lower part of the fraction turns out to be negative, simply move the "minus" from the lower part of the fraction to the upper part of the fraction.\n\n# "Growing divisor" approach\n\nAs an alternative, you can consider using a "constantly growing divisor" approach. This is used in some optimizations when you want to avoid unnecessary "heavy" division operations.\n\nWhat is the essence of this approach? It takes a long time to calculate the GCD each time a fraction is added. You can avoid simplifying the fraction after each addition, and do it only at the very end, when a string representation of the fraction is required.\n\nThus, we can simply add fractions to each other without reducing common divisors. This will be a large fraction with an impressive upper and lower parts.\n\nOnly when we reach the operation of obtaining the final result - only then will the GCD algorithm be called and the reduction will be performed.\n\n![image.png](https://assets.leetcode.com/users/images/e06c94d6-a365-4303-b511-b0279f614947_1724436704.8878262.png)\n\n```\nclass Solution {\n    fun fractionAddition(expression: String): String {\n        FractionalNumberReader(expression).use { reader ->\n            var number = reader.parseFractionNumber()\n            while (reader.ready()) {\n                number = number.add(reader.parseFractionNumber())\n            }\n            return number.toString()\n        }\n    }\n\n    class FractionalNumber(private val m: BigInteger, private val n: BigInteger) {\n\n        fun add(other: FractionalNumber): FractionalNumber {\n            return FractionalNumber(m * other.n + other.m * n, n * other.n)\n        }\n\n        override fun toString(): String {\n            val gcd = m.gcd(n)\n            var newM = m / gcd\n            var newN = n / gcd\n            if (newN.signum() < 0) { newM = -newM; newN = -newN }\n            return "$newM/$newN"\n        }\n    }\n\n    class FractionalNumberReader(expression: String): AutoCloseable {\n\n        val stream = PushbackReader(InputStreamReader(expression.byteInputStream()))\n\n        fun ready(): Boolean {\n            return stream.ready()\n        }\n\n        fun parseFractionNumber(): FractionalNumber {\n            val plus = parsePlus()\n            val m = parseNumber()\n            parseSeparator()\n            val n = parseNumber()\n            return FractionalNumber(\n                BigInteger(if (plus) 1 else -1, byteArrayOf(m.toByte())),\n                BigInteger(1, byteArrayOf(n.toByte()))\n            )\n        }\n\n        private fun parsePlus(): Boolean = when (val char = stream.read()) {\n            \'-\'.code -> false\n            \'+\'.code -> true\n            else -> { stream.unread(char); true }\n        }\n\n        private fun parseSeparator() {\n            val char = stream.read()\n            if (char != \'/\'.code) throw java.lang.IllegalStateException("Unknown separator: ${char.toChar()}")\n        }\n\n        private fun parseNumber(): Int {\n            var number = 0\n            while (stream.ready()) {\n                val code = stream.read()\n                if (code >= \'0\'.code && code <= \'9\'.code) {\n                    number = number * 10 + (code - \'0\'.code)\n                } else {\n                    stream.unread(code)\n                    break\n                }\n            }\n            return number\n        }\n\n        override fun close() {\n            stream.close()\n        }\n    }\n}\n```
['Kotlin']