<Advent of Code 2022 day 5> :thread:
# advent-of-code
a
d
I started to write a parser for the first part and then, about halfway through, just decided to type it in manually. Cheating? (No more than using AI!) I printed out the whole map and just typed in the top. Part 2 was just to move the last n chars.
m
I let out an audible "holy... f...'' when I read the input the first time 😂
m
Copy code
package aoc2022

fun main() = day05(String(System.`in`.readAllBytes())).forEach(::println)

fun day05(input: String): List<Any?> {
    val (stacksInput, commandsInput) = input.split("\n\n").map { it.split("\n") }

    fun loadStacks(stacksInput: List<String>): MutableList<MutableList<Char>> {
        val stacks = MutableList((stacksInput.last().length + 2) / 4) { mutableListOf<Char>() }
        stacksInput.take(stacksInput.size - 1).reversed().forEach { line ->
            val chunked = line.chunked(4).map(String::trim)
            chunked.forEachIndexed { i, token ->
                if (token.isNotBlank()) stacks[i] += token[1]
            }
        }
        return stacks
    }

    fun compute(part1: Boolean = false) = loadStacks(stacksInput).also { stacks ->
        for (line in commandsInput) {
            val (n, from, to) = line.split(" ").mapNotNull(String::toIntOrNull)
            if (part1) {
                repeat(n) { stacks[to - 1] += stacks[from - 1].removeLast() }
            } else {
                val newCrates = mutableListOf<Char>()
                repeat(n) { newCrates += stacks[from - 1].removeLast() }
                stacks[to - 1] += newCrates.reversed()
            }
        }
    }

    return listOf(compute(part1 = true), compute(part1 = false))
        .map { it.joinToString("") { it.last().toString() } }
}
c
same, I spent more time hardcoding the input than in the algorithm🤣
m
requesting a 'removeLast(count: Int)' function for a mutableList 👀
j
Finally a challenge (in parsing input, but still)!
d
Based on @Michael Böiers code, I amend my last line to:
Copy code
println(crates.values.map { it.last() }.joinToString(""))
@Jakub Gwóźdź - yeah, it even took the AI bots >2 minutes to solve!
j
message has been deleted
j
funny thing - I did part 2 first, kinda (I forgot a
.reversed()
when doing part 1 🙂
m
Not my cleanest work (edit: cleaned it up)
Copy code
object Day5 : Challenge() {
    val stacks: List<List<Char>>
    val moves: List<List<Int>>
    init {
        val regex = """move (\d+) from (\d+) to (\d+)""".toRegex()
        input.split(System.lineSeparator() + System.lineSeparator()).let { (a,b) ->
            stacks = List(9){ mutableListOf<Char>() }.apply {
                a.lines().forEach { line ->
                    for((index, value) in line.withIndex()){
                        if(value.isUpperCase()){
                            this[(index - 1) / 4].add(0, value)
                        }
                    }
                }
            }
            moves = b.lines().mapNotNull { line ->
                regex.matchEntire(line)?.destructured?.toList()?.map(String::toInt)
            }
        }
    }

    override fun part1() = craneGame(reverse = false)

    override fun part2() = craneGame(reverse = true)

    private fun craneGame(reverse: Boolean) = stacks.map(List<Char>::toMutableList).apply {
        for ((amount, from, to) in moves) {
            this[to - 1] += MutableList(amount) { this[from - 1].removeLast() }.apply { if (reverse) reverse() }
        }
    }.joinToString(separator = ""){ it.last().toString() }
}
m
My part 1:
c
I'm not going to put it on a frame
m
And part 2:
Christian coming in inbetween... 😄
v
I've cleaned some messy stuff, but its still really huge solution (80% parsing, 20% problem solving)
m
@Michael Böiers You can shortify your code
Copy code
val newCrates = mutableListOf<Char>()
repeat(n) { newCrates += stacks[from - 1].removeLast() }
stacks[to - 1] += newCrates.reversed()
to
Copy code
stacks[to - 1] += List(n){ stacks[from - 1].removeLast() }.reversed()
👀
s
This was fun to parse in Kotlin. Although I would love to have the ability to destructure a list of ints into the fields of a data class 😄
Copy code
class Crane(input: List<String>) {

    data class CrateMove(val num: Int, val from: Int, val to: Int)

    private val emptyLine = input.indexOfFirst { it.isEmpty() }

    private val stacks = input.parseStacks()

    private val regex = Regex("\\d+")

    private val moves: List<CrateMove> = input.drop(emptyLine + 1).map(::parseCrateMove)
    private fun List<String>.parseStacks() = MutableList<Stack<Char>>((first().length + 1) / 4) { Stack() }
        .also { stacks ->
            take(emptyLine - 1).reversed()
                .flatMap { it.chunked(4) }
                .map { it[1] }
                .forEachIndexed { idx, char ->
                    if (char != ' ') {
                        stacks[idx % stacks.size].push(char)
                    }
                }
        }

    private fun parseCrateMove(command: String): CrateMove {
        val (num, from, to) = regex.findAll(command).map { it.value.toInt() }.toList()
        return CrateMove(num, from - 1, to - 1)
    }

    private fun moveAll(mover: (move: CrateMove) -> Unit): String {
        moves.forEach(mover)
        return String(stacks.map { it.pop() }.toCharArray())
    }

    fun part1() = moveAll { move -> move.moveOneByOne() }
    fun part2() = moveAll { move -> move.moveAtOnce() }

    private fun CrateMove.moveOneByOne() = repeat(num) {
        stacks[to].push(stacks[from].pop())
    }

    private fun CrateMove.moveAtOnce() = Stack<Char>().apply {
        repeat(num) {
            push(stacks[from].pop())
        }
        repeat(num) {
            stacks[to].push(pop())
        }
    }

}
b
I think I fell pretty close in line to others, not golfing this. Trying to look at structure/reusability/testability. Definitely learning some new things reading the comments. The choice of
List<List<String>>
meant having to go from a map to a string, I couldn't figure out how to do it otherwise...
d
Parsing was the hard part. Initial version was pretty ugly, cleaned it up with typealiases and extension/member fns. https://github.com/dfings/advent-of-code/blob/main/src/2022/problem_5.main.kts
n
My parser is pretty short:
Copy code
val cr = Regex("""\[(.)\]""")
val mr = Regex("""move (\d+) from (\d+) to (\d+)""")
val stacks = mutableMapOf<Int, ArrayDeque<Char>>()
val moves = mutableListOf<Move>()
for (line in input) {
  cr.findAll(line).forEach { r -> stacks.getOrPut(r.range.first / 4 + 1) { ArrayDeque() }.addFirst(r.groupValues[1][0]) }
  mr.find(line)?.let { r -> moves += Move(r.groupValues[1].toInt(), r.groupValues[2].toInt(), r.groupValues[3].toInt()) }
}
c
Honestly shocked that Deque does not have a RemoveLast(n) method, nor does List. Parsing seems easiest to just magically grab the character from the expected position and then check if it's a whitespace or not:
Copy code
private fun processInput(){
        val divide = input.indexOfFirst { it == "\n\n" }
        // Deal with the crane state
        input.subList(0, divide).reversed().drop(1).forEach {
            for (i in 1..it.length step 4) {
                if (it[i] != ' ') {
                    val index = (i - 1) / 4
                    craneState1[index].push(it[i])
                    craneState2[index].add(it[i])
                }
            }
        }
        // Deal with the instructions
        input.subList(divide + 1, input.size)
            .map { it.toInstruction() }
            .forEach {
                craneState1.doInstruction1(it)
                craneState2.doInstruction2(it)
            }
    }
Also learned that hard way that .drop( ) doesn't actually 'drop' anything from the current List/Array...🙃
d
My parser is also pretty short, I mean reasoning about it was the hard part
n
Esp. proud of realizing that I can use
range.first
to compute the stack id.
x
mutableList.removeAll
strikes again! This made me question my existence 😅 😢
it was making my test to pass but generated the wrong answer
d
New screenshot, fixed unclear naming
c
There is an inbuilt java Stack<> data type...
d
Parsing uses addFirst (to bottom of stack), though I suppose I could process the filtered lines in reverse order. And — unlike
java.util.ArrayDeque
, the Kotlin version doesn’t have methods named
push
or
pop
Deque
is recommended when you need a stack (or a queue).
java.util.Stack
is based on
java.util.Vector
which was obsoleted by the collections framework in 1.2 (https://docs.oracle.com/javase/7/docs/api/java/util/Stack.html)
b
@Dan Fingal-Surma nice extensions on the Deque 💯
x
parser took longer than the actual problem
r
Damn!! I wasted first few minutes in parsing. I ended up hard-coding the input and then using
stack + hashmap
m
Refactored solution 🙂
Copy code
package aoc2022

fun main() = day05(String(System.`in`.readAllBytes())).forEach(::println)

fun day05(input: String): List<Any?> {
    val (stacksInput, commandsInput) = input.split("\n\n").map { it.split("\n") }

    val size = stacksInput.last().split(" ").mapNotNull(String::toIntOrNull).last()
    val stacksContents = with(stacksInput) { take(size - 1).reversed() }
    val initialStacks = List(size) { stack ->
        buildList {
            for (line in stacksContents) line.getOrNull(1 + (stack * 4))?.takeIf { it != ' ' }?.let { add(it) }
        }
    }

    val commands = commandsInput.map {
        it.split(" ").mapNotNull(String::toIntOrNull).mapIndexed { i, x -> if (i == 0) x else x - 1 }
    }

    fun compute(mod: ArrayDeque<Char>.(Char) -> Unit): String {
        val stacks = List(initialStacks.size) { initialStacks[it].toMutableList() }
        for ((n, from, to) in commands)
            stacks[to] += ArrayDeque<Char>().apply { repeat(n) { mod(stacks[from].removeLast()) } }
        return stacks.joinToString("") { it.last().toString() }
    }

    return listOf(compute { addLast(it) }, compute { addFirst(it) })
}
j
Today I started with
Deque<Char>
which worked fine but then decided to rewrite it to simple (...) string manipulations. The combination of
with/when
turned out to be quite readable for parsing
m
@Michael de Kaste requesting a
MutableList.remove(IntRange)
and an
IntRange.last(n)
and
IntRange.first(n)
. Then we could do something like
list.remove(list.indices.last(n))
or other cool stuff 🙂
m
Used Stack, but have to reverse it (I wonder why) .. Also first time using the map() .. newbie here
m
@Paul Woitaschek I am a big fan of immutability, but there’s a time and place for everything 😉
r
I used hashmap + stack - but yeah had to reverse stack in part2. I think
Dequeue
would be more beneficial here (stack reverse can be avoided in part 2)
k
Copy code
fun main() {
    fun part1(input: List<String>): String =
        loadInput(input, 8, 9).moveOne().second.topToString()



    fun part2(input: List<String>): String =
        loadInput(input, 8, 9).moveAll().second.topToString()

    val input = readInput("day05")
    println(part1(input))
    println(part2(input))
}

fun loadQueues(input: List<String>, stacks: Int): List<Stack<Char>> {
    val queues = mutableListOf<Stack<Char>>()
    (0 until stacks).forEach { _ -> queues.add(Stack()) }
    input.reversed().forEach {
        it.forEachIndexed { index, char ->
            if (char != ' ' && char != '[' && char != ']') queues[index / 4].add(char)
        }
    }

    return queues
}

fun loadInput(input: List<String>, stackHeight: Int, stacks: Int): Pair<List<String>, List<Stack<Char>>> =
    Pair(input.subList(stackHeight + 2, input.size), loadQueues(input.subList(0, stackHeight), stacks))

fun Pair<List<String>, List<Stack<Char>>>.moveOne(): Pair<List<String>, List<Stack<Char>>> {
    this.first.forEach {
        val moves = it.parseMoves()
        this.second.moveOne(moves.amount, moves.from, <http://moves.to|moves.to>)
    }
    return this
}

fun Pair<List<String>, List<Stack<Char>>>.moveAll(): Pair<List<String>, List<Stack<Char>>> {
    this.first.forEach {
        val moves = it.parseMoves()
        this.second.moveAll(moves.amount, moves.from, <http://moves.to|moves.to>)
    }
    return this
}

fun List<Stack<Char>>.topToString(): String =
    this.map { it.last() }.joinToString("")


fun List<Stack<Char>>.moveAll(amount: Int, from: Int, to: Int) {
    val toMove = (0 until (amount)).map { _ ->
        this[from - 1].pop()
    }

    toMove.reversed().forEach { this[to- 1].add(it)}
}

fun List<Stack<Char>>.moveOne(amount: Int, from: Int, to: Int) {
    (0 until (amount)).forEach { _ ->
        this[to - 1].add(this[from - 1].pop())
    }
}

fun String.parseMoves(): Moves {
    val nums = this.split(" ").filter { it.all(Char::isDigit) }.map { it.toInt() }
    return Moves(nums[0], nums[1], nums[2])
}

data class Moves(
    val amount: Int,
    val from: Int,
    val to: Int
)
Could probably be improved a lot. Solved using a stack. Ended up just reversing it for part 2, not really optimal...
z
As for everyone, parsing was most of the work... https://github.com/zsmb13/advent-of-code-2022/blob/main/src/Day05.kt My favourite part of the solution is this snippet - pretty quick and simple to write in Kotlin:
Copy code
fun <T> MutableList<T>.removeLast(count: Int): List<T> {
    val removeIndex = this.size - count
    return List(size = count) { this.removeAt(removeIndex) }
}
m
removeLast(count: Int) would stay in line with a return from removeLast() which will return a list of the things you removed, which sounds logical to me
m
Ok, I misread that. Still, how about this:
Copy code
fun <T> MutableList<T>.removeLastReversed(count: Int) = buildList { add(removeLast()) }
fun <T> MutableList<T>.removeLast(count: Int) = ArrayDeque<T>().apply { addFirst(removeLast()) } as List
m
or just
Copy code
fun <T> MutableList<T>.removeLast(count: Int) = List(count) { removeLast() }.asReversed()
m
Sure, was thinking performance
m
asReversed() is just as performant as normal, since its just a view on the original list
.reversed() is a different story 😛
d
@David Whittaker joinToString actually has a transform function so you don't need to map first, you can do it directly, in my case it becomes this:
Copy code
return stacks.joinToString(separator = "") { it.firstOrNull() ?: "" }
I just have a List of Lists with (single letter) Strings in them to represent the crates, in my case every first element is the top crate, I'm accommodating for stacks to be empty, could have done this with a filter as well
My day 5: https://github.com/Davio/advent-of-code/blob/main/src/main/kotlin/com/github/davio/aoc/y2022/Day5.kt Parsing the input took like 95% of solving the puzzle, executing the moves and calculting the final result was the easy part this time 😄
f
modelled a map (of lists) of the inventory, and used `take(n)`/`takeLast(n)` combined with
reversed()
to generate new
from
and
to
lists for the given indices - avoiding
repeat
. part 2 was then just a matter of not calling
reversed
Copy code
val oldFrom = inventory[cmd.from]!!
            val takeFrom = oldFrom.takeLast(cmd.num)
            val newFrom = oldFrom.take(oldFrom.size - cmd.num)
            val newTo = inventory[<http://cmd.to|cmd.to>]!! + (if (reversed) takeFrom.reversed() else takeFrom)
            inventory[cmd.from] = newFrom
            inventory[<http://cmd.to|cmd.to>] = newTo
For parsing I just split the input in 2 lists to simplify a bit. not happy with the over all structure though - I think it can be made more readable/simpler - not the least the parsing of input.
<https://github.com/fmmr/advent/blob/master/src/main/kotlin/no/rodland/advent_2022/Day05.kt>
m
Solved y2022d05 with modeling the Ship as a Map<Int,Stack<Crate>> Moving crates becomes popping and pushing with and intermediate stack for part two. https://github.com/mcbeelen/aoc/blob/%F0%9F%8C%B1/y2022/src/main/kotlin/y2022/day05/SupplyStacks.kt
k
Full universal solution of #30 lines 😃
Copy code
fun main() {
    fun solution(lines: List<String>, part: Int): String {
        val initLines = lines
            .takeWhile { it.isNotEmpty() }
            .dropLast(1)
            .map { it.chunked(4).mapIndexedNotNull { i, s -> s.trim(' ', '[', ']').takeIf(String::isNotEmpty)?.let { i + 1 to it[0] } } }

        val repo: MutableMap<Int, List<Char>> = initLines
            .flatten()
            .groupingBy { it.first }
            .fold(emptyList<Char>()) { acc, (i, c) -> listOf(c) + acc }
            .toMutableMap()

        lines
            .drop(initLines.size + 2)
            .mapNotNull { Regex("""move\s(\d+)\sfrom\s(\d+)\sto\s(\d+)""").find(it)?.groupValues?.drop(1)?.map(String::toInt) }
            .onEach { (count, fromRepo, toRepo) ->
                val cargo = repo[fromRepo]!!.takeLast(count)
                repo[fromRepo] = repo[fromRepo]!!.dropLast(count)
                repo[toRepo] = repo[toRepo]!! + if (part == 1) cargo.reversed() else cargo
            }
        return repo.toSortedMap().map { it.value.last() }.joinToString("")
    }

    check(solution(readInput("Day05_test"), part = 1) == "CMZ")
    check(solution(readInput("Day05_test"), part = 2) == "MCD")

    println(solution(readInput("Day05"), part = 1))
    println(solution(readInput("Day05"), part = 2))
}
https://github.com/karloti/advent-of-code-2022-kotlin/blob/main/src/Day05.kt
e
https://github.com/ephemient/aoc2022/blob/main/kt/src/commonMain/kotlin/com/github/ephemient/aoc2022/Day5.kt manual traversal of the header lines is not the most elegant solution, but that's what I did anyway
g
@Karloti I think you shouldn’t post solution on main channel.
w
Parsing took time as I wanted to use Sequence. I had to undo few times to get it correct. The algorithm is simple. Using ArrayDeque made things easier, however lists could work as well. Regex as always is a time waste!. It wasn't clear that the stack IDs are single digit. https://github.com/wellithy/aoc2022/blob/main/src/Day05.kt
k
@Grzegorz Baczek I have no experience with Slack and accidentally made a mistake. I removed the solution from the main channel!
o
Copy code
data class Instruction(val source: Int, val destination: Int, val amount: Int)
Copy code
fun parseInput(lines: List<String>): Pair<List<Stack<String>>, List<Instruction>> {
    val (stacks, instructionString) = lines.joinToString("\n").split("\n\n")
    return List(10) { LinkedList<String>() }.apply {
        stacks.split("\n").take(stacks.split("\n").size - 1).forEach { value ->
            value.chunked(4).forEachIndexed { index, s ->
                if (s.isNotBlank()) {
                    this[index].add(s)
                }
            }
        }
    }.mapIndexed { index, strings ->
        val stack = Stack<String>()
        strings.reversed().forEach {
            stack.push(it)
        }
        stack
    } to instructionString.split("\n").map {
        it.split(" ").mapNotNull {
            it.toIntOrNull()
        }
    }.map {
        Instruction(it[1], it[2], it[0])
    }
}

part1

fun main() = solve { lines ->
    parseInput(lines).apply {
        second.forEach { instruction ->
            (1..instruction.amount).forEach {
                first[instruction.destination - 1].push(first[instruction.source - 1].pop())
            }
        }
        first.forEach {
            if (it.isNotEmpty()) print(it.peek()[1])
        }
    }
}

part 2

fun main() = solve { lines ->
    parseInput(lines).apply {
        second.forEach { instruction ->
            val temp = mutableListOf<String>()
            (1..instruction.amount).forEach {
                temp.add(first[instruction.source - 1].pop())
            }
            temp.reversed().forEach {
                first[instruction.destination - 1].push(it)
            }
        }
        first.forEach {
            if (it.isNotEmpty()) print(it.peek()[1])
        }
    }
}
m
Thanks @Michael de Kaste for reminding me about asReversed(). I’d still rather use the deque than a normal list in that use case, seems more straight FORWARD to me 😉
d
To all the people using Stack, I thought it was deprecated in favor of Deque, I just ended up using a simple list instead
r
Day 5 solution - made it generic, had hard-coded the input initially 😄 https://github.com/ritesh-singh/aoc-2022-kotlin/blob/main/src/day05/Day05.kt
a
I have used Stack, although for Part2 it would have made more sense to use a List. https://github.com/Akhunzaada/advent-of-code-2022/blob/main/src/Day05.kt
k
CHALLENGE! Given the result of the solution of part one, who can write a recursive solution for the movements of a Crane(cargo=1)?
r
Challenges like these are on the way - it's 5th day only 😄 Enough for the day.
m
g
I had a lot of fun today! Nice puzzle
e
you can see how JVM consistently beats GraalVM native-image on throughput, and Kotlin/Native is not anywhere close to either https://ephemient.github.io/aoc2022/jmh-visualizer/index.html
j
I really like the way my parsing turned out
m
Interesting, I would have thought Kotlin/Native to be faster than JVM,
t
I ended up parsing the crates as vertical stripes from the first few input lines and that made my parser a lot simpler. • BlogCode
k
This one felt a lot like a parser/runtime problem, very tedious for me to parse the data, but once you have the "runtime" implementation is easy. • Code
n
I guess I'm the odd one out here, for whatever reason the parsing part was quick for me and the rearrangement part was slow. Below is my refactored code. My initial parsing was suboptimal, using
chunked(4).map { chunk -> chunk.first { it.isUppercase() } }
. But hey, it worked! I used ArrayDeques since it seemed so obvious that this was a Stack problem. This burned me a little in Part 2. My ignonimous solution was to transfer to a temporary stack, and then transferring to the destination stack. The ol' double reverse. https://github.com/nbanman/Play2022/blob/master/src/main/kotlin/org/gristle/adventOfCode/y2022/d5/Y2022D5.kt
p
Wow you write this extensive blog post for today's puzzle in this short time 👌
w
Here's my solution with input parsing and Deques
b
Here’s my attempt:
Copy code
fun main() {

    fun readInitialSupplyStackData(input: List<String>): List<ArrayDeque<String>> {
        // Create deque
        val supplyStackData = input.subList(0, input.indexOf(""))
        val lastStackNumber = supplyStackData.last().last().digitToInt()
        val supplyStacks = (1..lastStackNumber).map { ArrayDeque<String>() }
        val initialSupplyPositionData = supplyStackData.subList(0,supplyStackData.lastIndex)
        initialSupplyPositionData.forEach {
            val chuncks = it.chunked(4)
            chuncks.forEachIndexed {
                index, data ->
                if (data.isNotBlank()) {
                    supplyStacks[index].addLast(data[1].toString())
                }
            }
        }
        return supplyStacks
    }

    fun List<ArrayDeque<String>>.moveSupply(from: Int, to: Int){
        val supply = this[from-1].removeFirst()
        this[to-1].addFirst(supply)
    }

    fun List<ArrayDeque<String>>.getTopSupplies(): String {
        return this.filter { it.isNotEmpty() }
                .map { it.first() }
                .reduce { acc, s -> acc+s }
    }

    fun List<ArrayDeque<String>>.moveMultipleSupply(noOfSupplies: Int, from: Int, to: Int){
        val fromList = this[from-1]
        val toList = this[to-1]
        val supplies = this[from-1].take(noOfSupplies)
        val newFromContent = fromList.slice(noOfSupplies..fromList.lastIndex)
        fromList.clear()
        fromList.addAll(newFromContent)
        toList.addAll(0, supplies)
    }

    fun String.getMove(): Triple<Int, Int, Int> {
        val moveRegex = Regex("""^move (\d+) from (\d+) to (\d+)$""")
        val match = moveRegex.find(this)
        if (this.matches(moveRegex)) {
            val move = match?.destructured?.toList()?.map { it.toInt() }
            if (move != null) {
                return Triple(move[0], move[1], move[2] )
            }
        }
        return Triple(0, 0, 0)
    }

    fun part1(input: List<String>): String {
        val stacks = readInitialSupplyStackData(input)
        val moves = input.subList(input.indexOf("")+1, input.lastIndex+1)
        moves.forEach {
            val (count, from, to) = it.getMove()
            if (count == 1) {
                stacks.moveSupply(from, to)
            } else {
                (1..count).forEach { _ ->
                    stacks.moveSupply(from, to)
                }
            }
        }
        return stacks.getTopSupplies()
    }

    fun part2(input: List<String>): String {
        val stacks = readInitialSupplyStackData(input)
        val moves = input.subList(input.indexOf("")+1, input.lastIndex+1)
        moves.forEach {
            val (noOfSupplies, from, to) = it.getMove()
            stacks.moveMultipleSupply(noOfSupplies, from, to)
        }
        return stacks.getTopSupplies()
    }

    // test if implementation meets criteria from the description, like:
     val testInput = readInput("Day05_test")
     check(part1(testInput) == "CMZ")
     check(part2(testInput) == "MCD")
//
    val input = readInput("Day05")
    println(part1(input))
    println(part2(input))
}
e
@Marcin Wisniowski the JVM is a beast that is very good at what it does. any ahead-of-time compiler is going to have a tough time beating the code generated by a world-class JIT with runtime type and profile information
of course there are some disadvantages to JIT and the JVM too. warm-up time and the memory usage will always be worse than not needing a JIT (but this is completely invisible in my benchmark as it measures throughout). and there can be more efficient ways to structure program code than using Java's object model (but this doesn't affect my benchmark because it's the same program)
https://github.com/oracle/graal/issues/979 GraalVM native-image is aiming to match JIT performance if it's given profiling information up front. otherwise it expects to be worse
Kotlin Native still has a lot more optimization to go before it gets there, it's been a lower priority than getting it right first. I've seen performance gains and losses with the new memory manager; I'm not sure how much that plays into my current benchmarks though
f
Today was fun! When looking at the input I figured it would be fun to make
String.columns()
function. Probably overkill but maybe it'll come in handy later:
Copy code
fun String.columns(): Sequence<String> = sequence {
    val rows = lines()
    repeat(rows.maxOf(String::length)) { index ->
        val column = buildString {
            for (row in rows) {
                val letter = row.getOrNull(index) ?: continue
                if (!letter.isWhitespace()) append(letter)
            }
        }
        
        yield(column)
    }
}
(My full solution)
j
I got so annoyed with the fragility of parsing using division and stuff that I had to do something about it. This is using “Parboiled” lib for java. Not happy with a verbose old java lib but its scalable
If anyone has used lua, LPeg is so great for parsing, parboiled is some echo of this but less elegant
e
I've used https://copper-leaf.github.io/kudzu/ before. not on AOC though, and it's definitely less elegant than Haskell's Megaparsec, which an example of good parser combinator API design IMO
j
I have no clue about Megaparsec or Haskell really, but that Kudzu does not seem to be able to capture values “inline”, but rather produce a raw tree that I have to analyse after parsing. It could work too, but I miss LPeg still 🙂
This might be is a better kotlin lib https://github.com/h0tk3y/better-parse
j
Any one else see the towers and get hot sweats that we were about to get a variation on the Towers of Hanoi?
k
@Jacob Moulton literally the first thing that came to my mind when I started reading the problem K
m
My attempt at golfing it:
Copy code
fun day05B(input: String): List<Any?> {
    fun solve(part2: Boolean) = with(sortedMapOf<Int, ArrayDeque<Char>>()) {
        for (line in input.lines()) when {
            line.contains("[") -> line.chunked(4).map { it[1] }.forEachIndexed { i, c ->
                if (c != ' ') getOrPut(i + 1) { ArrayDeque() }.addFirst(c)
            }

            line.startsWith("move") -> line.split(" ").mapNotNull(String::toIntOrNull).let { (n, from, to) ->
                getValue(to) += List(n) { getValue(from).removeLast() }.run { if (part2) asReversed() else this }
            }
        }
        values.joinToString("") { it.last().toString() }
    }
    return listOf(solve(part2 = false), solve(part2 = true))
}
d
moar abstractions 😤
Copy code
fun main() {

    fun part1(input: List<String>): String {
        val crates = Crates.ofDrawing(input.readDrawing())
        CrateMover9000().execute(
            Instructions.ofList(input.readInstructions()),
            crates
        )
        return <http://crates.top|crates.top>()
    }

    fun part2(input: List<String>): String {
        val crates = Crates.ofDrawing(input.readDrawing())
        CrateMover9001().execute(
            Instructions.ofList(input.readInstructions()),
            crates
        )
        return <http://crates.top|crates.top>()
    }

    //val input = readInput("Day05_test")
    val input = readInput("Day05")
    println(part1(input))
    println(part2(input))
}

fun List<String>.readDrawing(): List<String> =
    takeWhile { it.isNotBlank() }

fun List<String>.readInstructions(): List<String> =
    takeLastWhile { it.startsWith("move") }

@JvmInline
value class Crates(val stacks: List<ArrayDeque<Char>>) {

    fun top(): String {
        return stacks.joinToString("") { it.last().toString() }
    }

    companion object {
        fun ofDrawing(drawing: List<String>): Crates {
            val height = drawing.lastIndex - 1
            var stack = 0
            val numbers = drawing.last()
            val stackCount = numbers.split(" ").count { it.trim().toIntOrNull() != null }
            val stacks = MutableList(stackCount) { ArrayDeque<Char>() }
            for ((i, stackNumber) in numbers.withIndex()) {
                var count = height
                if (stackNumber.isDigit()) {
                    while (count >= 0) {
                        val letter = drawing[count].getOrNull(i)
                        if (letter?.isLetter() == true) {
                            stacks[stack].add(letter)
                        }
                        count -= 1
                    }
                    stack += 1
                }
            }
            return Crates(stacks)
        }
    }
}

data class Instruction(
    private val count: Int,
    private val from: Int,
    private val to: Int,
) {
    fun execute(crateMover: CrateMover, crates: Crates) {
        crateMover.move(from, to, count, crates)
    }

    companion object {
        fun of(line: String): Instruction {
            val (count, from, to) = line.split(" ").mapNotNull { it.trim().toIntOrNull() }
            return Instruction(count, from, to)
        }
    }
}

@JvmInline
value class Instructions(private val instructions: List<Instruction>) {

    fun execute(crateMover: CrateMover, crates: Crates) {
        instructions.forEach { it.execute(crateMover, crates) }
    }

    companion object {
        fun ofList(lines: List<String>): Instructions {
            return lines
                .map(Instruction::of)
                .let(::Instructions)
        }
    }
}

interface CrateMover {
    fun move(from: Int, to: Int, count: Int, crates: Crates)
}

class CrateMover9000 : CrateMover {

    fun execute(instructions: Instructions, crates: Crates) {
        instructions.execute(this, crates)
    }

    override fun move(from: Int, to: Int, count: Int, crates: Crates) {
        val stacks = crates.stacks
        repeat(count) {
            val item = stacks[from - 1].removeLast()
            stacks[to - 1].add(item)
        }
    }
}

class CrateMover9001 : CrateMover {

    fun execute(instructions: Instructions, crates: Crates) {
        instructions.execute(this, crates)
    }

    override fun move(from: Int, to: Int, count: Int, crates: Crates) {
        val stacks = crates.stacks
        val fromStack = stacks[from - 1]
        val toMove = List(count) { fromStack.removeLast() }.reversed()
        stacks[to - 1].addAll(toMove)
    }
}
k
anyone fought with Intellij's remove trailing spaces ?:D
a
@kqr Yes, I did in Day 5's input. It removed the last column with empty crates.
e
my parser works fine even if the lines are jagged, but it's also ugly 🤷
r
My approach was to look at the last line before line-break (1,2,3,4.. stacks) and capture the indexes and then look at each line which contains stacks element and get the elements based on indexes you captured and put in a respective dequeue mapped to hashmap (where key is stack number)
Once i have the hashmap ready, algo is easy based on instructions, just move from one dequeue to another and in the end take the first or last element and add them to your string builder.
I do end up with extra space complexity, but i can live with it 😄