Day 18 solution thread
# advent-of-code
k
Day 18 solution thread
m
I like how it required two different approaches for me, for both parts. For the first one I could think of an character walker. The second finds all closed substring and solves that until no substrings exists.
Copy code
object Day18 : AdventOfCode() {
    val parsed = input.lines().map { it.replace(" ", "") }

    override fun part1() = parsed.map(::solveEquation).sum()

    data class Index(var value: Int)
    private fun solveEquation(input: String, index: Index = Index(0)) : Long {
        var sum = 0L
        var opIsPlus = true
        while (index.value < input.length) {
            when (val c = input[index.value++]) {
                in '0'..'9' -> sum = applyOp(sum, opIsPlus, c - '0' - 0L)
                '*' -> opIsPlus = false
                '+' -> opIsPlus = true
                '(' -> sum = applyOp(sum, opIsPlus, solveEquation(input, index))
                ')' -> return sum
            }
        }
        return sum
    }

    private fun applyOp(sum: Long, opIsPlus: Boolean, other: Long) = if(opIsPlus) sum + other else sum * other
    
    override fun part2() = parsed.map(::solvePart2).sum()

    private fun solvePart2(input: String) : Long {
        var s = input
        while(true){
            val (o, c) = findOpenClosed(s) ?: return solvePart(s)
            s = s.substring(0, o) + solvePart(s.substring(o + 1, c)) + s.substring(c + 1)
        }
    }

    private fun findOpenClosed(input: String) : Pair<Int, Int>? {
        var openIndex = 0
        for(i in input.indices){
            when(input[i]){
                '(' -> openIndex = i
                ')' -> return openIndex to i
            }
        }
        return null
    }

    private fun solvePart(input: String) = input.split('*')
            .map { it.split('+').sumOf(String::toLong) }
            .reduce { acc, i -> acc * i }
}
e
https://github.com/ephemient/aoc2020/blob/main/kt/src/main/kotlin/io/github/ephemient/aoc2020/Day18.kt part 1 is just consuming input (recursing on parens) in order, part 2 is looping over additions inside of a loop over multiplications
n
This one for me was pretty rough. I also realized my approach for part 1 wasn't nice for part 2, dithered a bit between different approaches, made tons of stupid mistakes... Anyway,
I also had a bit of a headache with the tokenizing logic for integers and then shamelessly exploited the fact that all the numbers are one digit... 🤷
I should probably fix that properly
e
exploiting the structure of the input is part of AoC 🙂 it may still be interesting to do, of course
I didn't realize that they were all single-digit numbers and made it general…
I did write a custom tokenizer in Rust, but didn't bother for Kotlin since my solution is just YOLO on error
n
I try not to exploit things not specified in the input explanation
But I know what you mean
I figured it out, was just a couple of extra annoying lines
Overall I'm pretty happy with the solution finally, the data structure I parse into in particular
e
in past years there have been days that are literally unsolvable (as in unsolved P=NP type problems) going by the problem text alone, unless you exploit the structure of the inputs
but nothing like that seems necessary this year so far :)
n
yeah it seems not
t
e
hmm. now that I look, we already have
Iterable<T>.sumOf(selector: (T) -> Long)
in the standard library
new in Kotlin 1.4, I must have missed it
yeah, the Number-like Char.toInt()/.toLong() is unfortunate. Java has Character.digit() but Kotlin doesn't have its own. there's a recent KEEP proposal to fix this: https://github.com/Kotlin/KEEP/blob/int-char-conversions/proposals/stdlib/char-int-conversions.md
m
I always just do 'character - '0''
Although today I needed a long so it became 'character - '0' - 0L'
Lol
n
What's the difference between sumBy and sumOf
I have to remember all these map + something shortcuts. map + sum -> SumOf/By, withIndex + map -> mapWithIndex
e
.sumBy()/.sumByDouble() only has Int and Double versions, with different names. the new .sumOf() has overloads for all numeric types under the same name (using @JvmName to differentiate them on the JVM, otherwise generic type erasure makes things unhappy)
n
should sumBy just be considered deprecated then
e
it's not marked deprecated, but probably
also there's withIndex versions for almost everything, e.g. filterIndexed, flatMapIndexed, forEachIndexed, reduceIndexed, foldIndexed, runningFoldIndexed, etc.
k
Aah, nice to know about sumOf
I always thought sumBy was a weird name anyway
e
yeah I only noticed minOf/maxOf in the Kotlin 1.4 release notes, missed sumOf somehow. I think either names works but it's better if it's consistent
t
Huh. Didn’t remember sumOf. Nice!
Thanks for the tip @ephemient! Edited and gave you credit for the tip! 🙂
l
I did the work by using regular expressions to find and reduce operations until only a number remains
t
Oh that is clever
A bit like the N dimensional yesterday, I made a pretty generic solution (same code for part1 and part2, just different rules given to the function)
I'm sure I'm missing something around that while
Ah! do
Not sure if that do version is better
n
I understand what you did, I thought about doing an "N passes" approach
that's probably really the most robust way to do it, to be honest
imagine you start having exponentnation as well with higher precedence, etc
b
yeah because the real story is
I misread the text again
so I wrote a normal parser
n
Hah
I kinda cheated a little for part 2, I realized I could do it in one pass by keeping a running product and a running sum
since part 2 will get evaluated as a product of sums
it is impressively concise, mine is a fair bit longer, I parse things into nice sealed classes first
b
and it was pretty fast
clearly not 3 minutes like the top of the leaderboard 😄
but kotlin is less flexible (there are libs that allow writing parsers though)
I could have just thrown antlr in there