<Advent of Code 2023 day 2> :thread:
# advent-of-code
a
k
Whelp, I had to solve today in kotlin because my project was so borked it refused to compile
I think I need to start a new project and copy the file over cause nothing is gonna fix this
I'm gonna cry
πŸ‘€ 2
m
Part 1
πŸ™Œ 2
Part 2
πŸ™Œ 3
I found this easier than day 1. πŸ˜„ No gotchas this time like with the overlapping numbers.
πŸ‘ 1
πŸ‘ 4
βž• 2
j
Oh, I didn't know about
substringBefore
(and after). That's nice! πŸ™‚
K 4
n
No major problems, slow and steady wins the race. Did it on 3 glasses on wine. Probably picked an overly complicated data structure: List<List<Map<String, Int>>>. Will debrief sober.
🍷 5
n
This was a typical day 2: a bit of fancy parsing, but very simple logic
☝️ 1
n
Simplified things considerably:
p
p
seeing everyone's functional approaches has been very interesting
I completely forgot about return@ , could have sped up my solution!! Thanks @Marcin Wisniowski for your solution to open my eyes πŸ˜„
j
I did it in plain functional and later refactored out data types. https://github.com/bulldog98/advent-of-code/blob/main/advent2023/src/main/kotlin/year2023/Day02.kt
πŸ‘ 1
p
Looks like a similar idea to mine. Moving the parsing part to the construction of a custom type.
m
If we are practising parsing, then I better brush up my regex-fu, there may be more parsing to do soon
m
I spent more time parsing than actually writing the solution 🀦
same 3
n
Many early problems are best thought of as parsing problems.
πŸ‘ 3
p
carbon.png
carbon(1).png
p
Day 2 in the bag (heh) Much simpler than Day 1 - most of the complexity is in the parsing.
@ephemient regex certainly the easier way to go for parsing this one - for some reason I try not to reach for it with AoC though :)
n
To calculate minimum bag for part 2 I like
merge
method of mutable map. So you can do like on screen 1 (
for
loops will depend on your data structure). Alternatively, without using
buildMap
builder and mutable map inside, you can use
groupBy
- ineffective as it will collect list of values, before you will reduce it to max, or
groupingBy
with
fold
- screen 2 - more effective, as it will reduce on the fly, but I find it harder to udnerstand.
same 1
(better second screen)
r
My solution: https://github.com/rlippolis/advent-of-code-2023/blob/main/src/main/kotlin/com/riccardo/adventofcode/edition2023/day2/Day2.kt, a lot easier than yesterday indeed! Just used some `typealias`es to make sense of the
List<Map<String, Int>>
πŸ˜… (probably could have shortened the parsing code by using regex, but it was not a regex morning for me 😬)
p
I'm always annoyed by the lack of
merge
and
compute
on the
MutableMap
interface. Also for some reason this morning I was getting
Copy code
IllegalStateException: CallableReferencesCandidateFactory doesn't support candidates with multiple extension receiver candidates
when using
merge
in a
buildMap
so opted for
compute
🀷
m
Had to do some chores this morning, but a fun day!
Copy code
object Day2 : Challenge() {
    private val parsed: List<Game> = input.lines().map { line ->
        line.split(": ").let { (game, subgame) ->
            val gameId = game.substringAfter("Game ").toInt()
            val subgames = subgame.split("; ").map { cubes ->
                SubGame(
                    cubes.split(", ").map { cube ->
                        cube.split(" ").let { (amount, color) ->
                            amount.toInt() to color
                        }
                    }.associateBy({ it.second }, { it.first }).withDefault { 0 },
                )
            }
            Game(gameId, subgames)
        }
    }

    class Game(
        val id: Int,
        private val subGames: List<SubGame>,
    ) {
        fun isValidGame(red: Int, green: Int, blue: Int) =
            subGames.all { subgame -> subgame.red <= red && subgame.green <= green && subgame.blue <= blue }

        val power = subGames.fold(Triple(0, 0, 0)) { (maxRed, maxBlue, maxGreen), subGame ->
            Triple(
                max(maxRed, subGame.red),
                max(maxBlue, subGame.blue),
                max(maxGreen, subGame.green),
            )
        }.let { (red, green, blue) -> red * green * blue }
    }

    class SubGame(map: Map<String, Int>) {
        val red by map
        val green by map
        val blue by map
    }

    override fun part1() = parsed.filter { it.isValidGame(12, 13, 14) }.sumOf { it.id }
    override fun part2() = parsed.sumOf(Game::power)
}
main problem is always making sure the whole darn thing is readable
n
Nice one with
val red by map
props.
d
I also found this easier than yesterday, well, more straightforward at least, I could go from start to finish in a quite linear manner if you understand what I mean. So first parsing each line to a helper class with the ID and a list of maps from string to int representing the subsets by color, then some logic to check whether the game is possible and what its power is And for the power you can just take the max of each color in all of the different subsets
m
Nice spot that you didn't need to parse the game id in part 1 @Neil Banman
πŸ‘ 1
k
part 1 (part 2 is similar enough so I won't post)
j
Hm I spotted multiple solutions using a class that is inherited from with a trailing lambda as constructor parameter. You know why we are not allowed to pull those out like we can for functions?
p
Ambiguity on lambda Vs class body
l
solution day 2:
Copy code
import java.util.Collections.max

fun main() {
    val gameNumberRegex = """(?<=Game )\d+"""
    val redRegex = """\d+(?= red)"""
    val greenRegex = """\d+(?= green)"""
    val blueRegex = """\d+(?= blue)"""

    val legalRedNumber = 12
    val legalGreenNumber = 13
    val legalBlueNumber = 14

    fun String.gameNumber() = gameNumberRegex.toRegex().find(this)!!.value.toInt()
    fun String.findReds() = redRegex.toRegex().findAll(this).map { it.value.toInt() }.toList()
    fun String.findGreens() = greenRegex.toRegex().findAll(this).map { it.value.toInt() }.toList()
    fun String.findBlues() = blueRegex.toRegex().findAll(this).map { it.value.toInt() }.toList()

    fun checkLegalGames(games: List<String>): Long {
        var sum = 0L
        games.forEach { game ->
            val isLegalGame = game.findReds().all { it <= legalRedNumber }
                    && game.findGreens().all { it <= legalGreenNumber }
                    && game.findBlues().all { it <= legalBlueNumber }
            if (isLegalGame) sum += game.gameNumber()
        }
        return sum
    }

    fun powerOfSets(games: List<String>): Long {
        var sum = 0L
        games.forEach { game ->
            val minReds = max(game.findReds())
            val minGreens = max(game.findGreens())
            val minBlues = max(game.findBlues())
            sum += minReds * minGreens * minBlues
        }
        return sum
    }

    val games = readInput("Day_02_games")
    println(checkLegalGames(games))
    println(powerOfSets(games))
}
r
Copy code
data class GameRecord(val id: Int, val sets: Map<String, Int>)

val adventOfCodeTwo = object : AdventOfCode {
    fun List<String>.toGameRecords(): List<GameRecord> = mapIndexed { gameIndex, input ->
        val processed = input.substringAfter(":").split(";").map {
            it.split(",")
        }

        val sets = mutableMapOf<String, Int>()

        processed.mapIndexed { index, strings ->
            strings.map {
                val (count, color) = it.trim().split(" ")
                sets.put("$index$color", count.toInt())
            }
        }

        GameRecord(id = gameIndex + 1, sets = sets)
    }

    override fun part1(input: List<String>): Int {
        val totalGames = input.toGameRecords()

        return totalGames.filter { game ->
            game.sets.all { (colorCode, score) ->
                val color = colorCode.substring(1)
                when (color) {
                    "red" -> score <= 12
                    "green" -> score <= 13
                    "blue" -> score <= 14
                    else -> true
                }
            }
        }.sumOf { it.id }
    }

    override fun part2(input: List<String>): Int {
        val totalGames = input.toGameRecords()

        return totalGames.sumOf { game ->
            var maxRed = 0
            var maxGreen = 0
            var maxBlue = 0

            game.sets.forEach { (t, count) ->
                val color = t.substring(1)

                when (color) {
                    "red" -> maxRed = max(maxRed, count)
                    "green" -> maxGreen = max(maxGreen, count)
                    "blue" -> maxBlue = max(maxBlue, count)
                }
            }

            maxRed * maxGreen * maxBlue
        }
    }
}
t
Feels like I overengineered it a tiny bit
p
@Tolly Kulczycki welcome to Advent of OverEngineering πŸ˜‰
kodee happy 2
advent of code intensifies 1
o
Copy code
fun main() {
    println(File("input1.txt").useLines { lines ->
        lines.map { line ->
            val splitData = line.split(":")[1]
            (Regex("\\d+(?= green)")).findAll(splitData).map { it.value.toInt() }.max() *
                    (Regex("\\d+(?= red)")).findAll(splitData).map { it.value.toInt() }
                        .max() * (Regex("\\d+(?= blue)")).findAll(splitData).map { it.value.toInt() }.max()
        }.sum()
    })
}
πŸ‘ 1
r
Any ideas on how to simplify it?
j
Today was rather easy so I decided to take opportunity to learn something new and lo-and-behold I present to you Aoc2023Day2 serialization done in
kotlinx.serialization
style (probably not the best approach, but my first πŸ˜† )
K 3
t
All of my time was spent on the parser. And finding a bug in there because I wasn’t trimming substrings properly. β€’ Code β€’ Blog
πŸ‘ 1
o
@raphadlts you can try asking gpt… it might give good suggestions.
s
Copy code
fun main() {
    val games = readInput("Day02")

    val validGamesSum = games.sumOf { game -> getValidGameId(game) }
    println("Part 1 answer: $validGamesSum")

    val power = games.sumOf { game -> getPowerForGame(game) }
    println("Part 2 answer: $power")
}

val cubesLimit = mapOf(
    "red" to 12,
    "green" to 13,
    "blue" to 14
)

val cubesPower = mutableMapOf<String, Int>()

fun getValidGameId(game: String): Int {
    println(game.parseGameSets())
    game.parseGameSets().forEach { (color, count) ->
        if (count > (cubesLimit[color] ?: 0)) return 0
    }
    return game.substringBefore(":").filter { it.isDigit() }.toInt()
}

fun getPowerForGame(game: String): Int {
    game.parseGameSets().forEach { (color, count) ->
        cubesPower[color] = maxOf(cubesPower.getOrDefault(color, 0), count)
    }
    return cubesPower.values.fold(1) { acc, value -> acc * value }.also { cubesPower.clear() }
}

fun String.parseGameSets(): List<Pair<String, Int>> =
    split(":").last().split(";")
        .flatMap { set ->
            set.split(",").map { cubes ->
                val color = cubes.filter { it.isLetter() }
                val count = cubes.filter { it.isDigit() }.toInt()
                color to count
            }
        }
d
I see some people splitting on a single character, e.g. ; or , You are allowed to split on whatever you like, so
s.split("; ")
will work just fine
K 1
r
@Davio good one!
p
You can also split on multiple strings at once (see https://kotlinlang.slack.com/archives/C87V9MQFK/p1701505981923339?thread_ts=1701493204.018119&amp;cid=C87V9MQFK) Very useful with destructuring too.
j
groupingBy
&
aggregate
K
j
Screenshot 2023-12-03 at 13.57.42.png