From 6e2462c7ae52432abeb687ec49e53838d379bd6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20R=C3=B8dland?= Date: Fri, 13 Dec 2024 15:49:14 +0100 Subject: [PATCH] 2024 - Day 12 - part 2 --- src/main/kotlin/no/rodland/advent/Dir.kt | 2 +- .../kotlin/no/rodland/advent_2024/Day12.kt | 137 +++++++----------- .../no/rodland/advent_2024/Day12Test.kt | 6 +- 3 files changed, 56 insertions(+), 89 deletions(-) diff --git a/src/main/kotlin/no/rodland/advent/Dir.kt b/src/main/kotlin/no/rodland/advent/Dir.kt index 58d4db56..5ec69aed 100644 --- a/src/main/kotlin/no/rodland/advent/Dir.kt +++ b/src/main/kotlin/no/rodland/advent/Dir.kt @@ -1,7 +1,7 @@ package no.rodland.advent enum class Dir(val move: (input: Pos, howMuch: Int) -> Pos) { - N({ pos, howMuch -> pos.above(howMuch) }), + N({ pos, howMuch -> pos.above(howMuch) }), S({ pos, howMuch -> pos.below(howMuch) }), W({ pos, howMuch -> pos.left(howMuch) }), E({ pos, howMuch -> pos.right(howMuch) }), diff --git a/src/main/kotlin/no/rodland/advent_2024/Day12.kt b/src/main/kotlin/no/rodland/advent_2024/Day12.kt index 916e05b1..9e6a1e0d 100644 --- a/src/main/kotlin/no/rodland/advent_2024/Day12.kt +++ b/src/main/kotlin/no/rodland/advent_2024/Day12.kt @@ -1,38 +1,67 @@ package no.rodland.advent_2024 import no.rodland.advent.Day +import no.rodland.advent.Direction import no.rodland.advent.Pos -import kotlin.math.abs // template generated: 12/12/2024 // Fredrik Rødland 2024 -class Day12(val input: List) : Day, Array>> { +class Day12(val input: List) : Day> { - private val parsed = input.parse() - private val grid = parsed.first - private val rotated = parsed.second + private val grid = input.parse() - data class Region(val c: Char, val positions: Set) { - operator fun contains(pos: Pos): Boolean = pos in positions - fun area() = positions.size - fun neighbours() = positions.flatMap { it.neighbourCellsUDLR() }.filterNot { it in positions } - fun perimeter(): Int = neighbours().size - } override fun partOne(): Int { - return findAllRegions().sumOf { - it.area() * it.perimeter() + return findAllRegions().sumOf { region -> + region.area() * region.perimeter() } } + override fun partTwo(): Int { + return findAllRegions().sumOf { region -> + region.sides() * region.area() + } + } + + private fun Region.sides(): Int { + return positions.sumOf { p -> p.corners() } + } + + // got help from: https://todd.ginsberg.com/post/advent-of-code/2024/day12/ + private fun Pos.corners(): Int { + val c = grid[this] + return listOf(Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.NORTH) + .zipWithNext() + .map { (first, second) -> + listOf( + grid[next(first)], + grid[next(second)], + grid[next(first).next(second)] + ) + }.count { (side1, side2, corner) -> + (c != side1 && c != side2) || (side1 == c && side2 == c && corner != c) + } + } + + @Suppress("ConvertCallChainIntoSequence") private fun findAllRegions(): List { + fun getRegion(grid: Grid, pos: Pos, visited: MutableSet): Set { + return (setOf(pos) + pos.neighbourCellsUDLR() + .filter { it !in visited } + .filter { it in this@Day12.grid } + .filter { this@Day12.grid[it] == this@Day12.grid[pos] } + .onEach { visited.add(it) } + .flatMap { getRegion(grid, it, visited) } + .toList()).toSet() + } + val visited = mutableSetOf() return grid.flatMapIndexed { y, row -> row.mapIndexed { x, c -> val pos = Pos(x, y) if (pos !in visited) { - Region(c, grid.getRegion(pos, visited)) + Region(c, getRegion(grid, pos, visited)) } else { null } @@ -40,85 +69,23 @@ class Day12(val input: List) : Day, Arra } } - @Suppress("ConvertCallChainIntoSequence") - private fun Array.getRegion(pos: Pos, visited: MutableSet): Set { - return (setOf(pos) + pos.neighbourCellsUDLR() - .filter { it !in visited } - .filter { it in grid } - .filter { grid[it] == grid[pos] } - .onEach { visited.add(it) } - .flatMap { getRegion(it, visited) } - .toList()).toSet() - } - - - override fun partTwo(): Int { - val regions = findAllRegions() -// regions.forEach { println(it.toString() + ": " + it.neighbours()) } -// -// val initMaybe = regions[0].neighbours().map { setOf(it) } -// val test0 = regions[0].neighbours().fold(initMaybe) { acc: List>, pos: Pos -> -// val (fences, rest) = acc.partition { fence -> fence.sameFence(pos) } -// if (fences.isEmpty()) { -// rest -// } else { -// (rest + fences.map { it + pos }).filterNot { it == setOf(pos) } -// } -// }.map { it.sorted() }.toSet() - return 2 - } - - private fun Set.sameFence(pos: Pos): Boolean { - return if (size == 0) { - error("should not happen") - } else { - val first = first() - if (first == pos) { - true - } else if (size == 1) { - first.isAdjacent(pos) - } else if (all { it.x == first.x }) { - any { it.isAdjacentY(pos) } - } else if (all { it.y == first.y }) { - any { it.isAdjacentX(pos) } - } else { - error("should not happen either") - } - } - - - TODO("Not yet implemented") - } - - fun Pos.isAdjacent(other: Pos) = abs(x - other.x) + abs(y - other.y) == 1 - fun Pos.isAdjacentX(other: Pos) = abs(x - other.x) == 1 && y == other.y - fun Pos.isAdjacentY(other: Pos) = abs(y - other.y) == 1 && x == other.x operator fun Grid.contains(pos: Pos): Boolean = pos.x >= 0 && pos.x < this[0].size && pos.y >= 0 && pos.y < this.size - operator fun Grid.get(pos: Pos): Char = this[pos.y][pos.x] - - override fun List.parse(): Pair, Array> { - val upright = indices.map { y -> indices.map { x -> this[y][x] }.toCharArray() }.toTypedArray() - val rotated = indices.map { y -> indices.map { x -> this[x][y] }.toCharArray() }.toTypedArray() - return upright to rotated - } + operator fun Grid.get(pos: Pos): Char? = if (pos in this) grid[pos.y][pos.x] else null + override fun List.parse(): Grid = indices.map { y -> indices.map { x -> this[y][x] }.toCharArray() }.toTypedArray() override val day = "12".toInt() -// private fun Grid.fences(): Int { -// flatMap { row -> -// val windowed = row.toList().windowed(2).map { (c1, c2) -> -// if (c1 == c2) 0 else 2 -// } -// windowed -// } -// return 2 -// } - - + data class Region(val c: Char, val positions: Set) { + operator fun contains(pos: Pos): Boolean = pos in positions + fun area() = positions.size + fun neighbours() = positions.flatMap { it.neighbourCellsUDLR() }.filterNot { it in positions } + fun perimeter(): Int = neighbours().size + } } + diff --git a/src/test/kotlin/no/rodland/advent_2024/Day12Test.kt b/src/test/kotlin/no/rodland/advent_2024/Day12Test.kt index aecd0a69..90c559c4 100644 --- a/src/test/kotlin/no/rodland/advent_2024/Day12Test.kt +++ b/src/test/kotlin/no/rodland/advent_2024/Day12Test.kt @@ -16,9 +16,9 @@ internal class Day12Test { private val test12 = "2024/input_12_test.txt".readFile() private val resultTestOne = 1930 - private val resultTestTwo = 2 + private val resultTestTwo = 1206 private val resultOne = 1424472 - private val resultTwo = 2 + private val resultTwo = 870202 val test = defaultTestSuiteParseOnInit( Day12(data12), @@ -30,7 +30,7 @@ internal class Day12Test { { Day12(data12) }, { Day12(test12) }, numTestPart1 = 5, - numTestPart2 = 1 + numTestPart2 = 5 ) @Nested