diff --git a/aoc2024/src/main/kotlin/day12/Day12.kt b/aoc2024/src/main/kotlin/day12/Day12.kt new file mode 100644 index 0000000..6de3506 --- /dev/null +++ b/aoc2024/src/main/kotlin/day12/Day12.kt @@ -0,0 +1,60 @@ +package day12 + +import common.* +import java.io.File +import java.io.InputStream +import java.util.LinkedList +import java.util.Queue +import kotlin.streams.asSequence + +fun main() { + val input = parseInput(File("input/12.txt").inputStream()) + println("Part 1: ${part1(input)}") +} + +fun parseInput(stream: InputStream): Map { + return stream + .bufferedReader() + .lines() + .asSequence() + .flatMapIndexed { i, line -> line.mapIndexed { j, c -> Vec(i, j) to c }} + .toMap() +} + +fun findRegionFor(pos: Vec, map: Map): Set { + val regionChar = map[pos] ?: error("$pos not in map") + val region = mutableSetOf(pos) + val q: Queue = LinkedList().apply { add(pos) } + while (q.isNotEmpty()) { + val v = q.remove() + sequenceOf(Vec(1, 0), Vec(0, 1), Vec(-1, 0), Vec(0, -1)) + .map { v + it } + .filter { !region.contains(it) && map.getOrDefault(it, '?') == regionChar } + .forEach { q.add(it); region.add(it) } + } + return region +} + +fun getRegions(map: Map): List> { + val seen = mutableSetOf() + val regions = mutableListOf>() + map.keys.forEach { + if (!seen.contains(it)) { + val region = findRegionFor(it, map) + regions.add(region) + seen.addAll(region) + } + } + + return regions +} + +fun part1(input: Map): Int { + return getRegions(input).sumOf { it.size * it.sumOf { pos -> perimeterScore(pos, input) }} +} + +fun perimeterScore(pos: Vec, input: Map): Int { + val map = input.withDefault { '?' } + return sequenceOf(Vec(1, 0), Vec(0, 1), Vec(-1, 0), Vec(0, -1)) + .count { map.getValue(pos) != map.getValue(pos + it) } +} diff --git a/aoc2024/src/test/kotlin/Day12Test.kt b/aoc2024/src/test/kotlin/Day12Test.kt new file mode 100644 index 0000000..5a21cdc --- /dev/null +++ b/aoc2024/src/test/kotlin/Day12Test.kt @@ -0,0 +1,51 @@ +import common.Vec +import day12.parseInput +import day12.part1 +import kotlin.test.Test +import kotlin.test.assertEquals + +class Day12Test { + private val input1 = """ + AAAA + BBCD + BBCC + EEEC + """.trimIndent() + private val input2 = """ + OOOOO + OXOXO + OOOOO + OXOXO + OOOOO + """.trimIndent() + private val input3 = """ + RRRRIICCFF + RRRRIICCCF + VVRRRCCFFF + VVRCCCJFFF + VVVVCJJCFE + VVIVCCJJEE + VVIIICJJEE + MIIIIIJJEE + MIIISIJEEE + MMMISSJEEE + """.trimIndent() + + @Test + fun `Should parse input correctly`() { + val expected = mapOf( + Vec(0, 0) to 'A', Vec(0, 1) to 'A',Vec(0, 2) to 'A',Vec(0, 3) to 'A', + Vec(1, 0) to 'B', Vec(1, 1) to 'B',Vec(1, 2) to 'C',Vec(1, 3) to 'D', + Vec(2, 0) to 'B', Vec(2, 1) to 'B',Vec(2, 2) to 'C',Vec(2, 3) to 'C', + Vec(3, 0) to 'E', Vec(3, 1) to 'E',Vec(3, 2) to 'E',Vec(3, 3) to 'C', + ) + assertEquals(expected, parseInput(input1.byteInputStream())) + } + + @Test + fun testPart1() { +// assertEquals(140, part1(parseInput(input1.byteInputStream()))) + assertEquals(772, part1(parseInput(input2.byteInputStream()))) + assertEquals(1930, part1(parseInput(input3.byteInputStream()))) + } +}