From 5c632e5fe9d80b4aab44fc6c7b9d0d0177f3ef9a Mon Sep 17 00:00:00 2001 From: "Mohsen.Biglari" Date: Fri, 13 Dec 2024 12:12:40 +0100 Subject: [PATCH] Day 12 --- src/Day12.kt | 209 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 src/Day12.kt diff --git a/src/Day12.kt b/src/Day12.kt new file mode 100644 index 0000000..e4d3c04 --- /dev/null +++ b/src/Day12.kt @@ -0,0 +1,209 @@ +import Direction.* + +fun main() { + data class Coord(val row: Int, val col: Int) { + fun move(direction: Direction) = copy(row = row + direction.y, col = col + direction.x) + fun neighbors() = listOf(Up, Down, Left, Right).map { move(it) } + fun isValid(size: Int) = row in 0..>.get(index: Coord) = this[index.row][index.col] + fun Array>.put(index: Coord, value: Int) { + this[index.row][index.col] = value + } + + operator fun Array.get(index: Coord) = this[index.row][index.col] + + + data class ConnectedComponents( + val graph: Array>, + val numOfComponents: Int, + ) { + + fun Coord.edges(): List { + val component = graph[this] + return buildList { + neighbors().forEach { neighbor -> + if (neighbor.isValid(graph.size)) { + if (graph[neighbor] != component) add(Edge(this@edges, getEdgeFacing(neighbor))) + } else add(Edge(this@edges, getEdgeFacing(neighbor))) + } + } + } + + fun numOfEdges(coord: Coord): Int { + val component = graph[coord] + return coord.neighbors().fold(0) { acc, neighbor -> + acc + if (neighbor.isValid(graph.size)) { + if (graph[neighbor] == component) 0 else 1 + } else 1 + } + } + + } + + class MapManager( + val map: Array, + ) { + constructor(input: List) : this(input.map { it.toCharArray() }.toTypedArray()) + + private val size = map.size + + private fun connectedComponents( + graph: Array>, + start: Coord, + componentIndex: Int + ) { + val queue = mutableSetOf() + val char = map[start] + + queue.add(start) + while (queue.isNotEmpty()) { + val curr = queue.elementAt(0) + queue.remove(curr) + graph.put(curr, componentIndex) + curr.neighbors().forEach { neighbor -> + if (neighbor.isValid(size) && map[neighbor] == char && graph[neighbor] == -1) { + queue.add(neighbor) + } + } + } + } + + private fun connectedComponents(): ConnectedComponents { + val graph = Array(size = size) { Array(size) { -1 } } + var componentIndex = 0 + + graph.forEachIndexed { row, rowChars -> + rowChars.forEachIndexed { col, _ -> + val coord = Coord(row, col) + if (graph[coord] == -1) { + connectedComponents(graph, coord, componentIndex) + componentIndex++ + } + } + } + return ConnectedComponents(graph, componentIndex) + } + + fun fencePrice(): Long { + val connectedComponents = connectedComponents() + val areas = mutableMapOf() + val perimeters = mutableMapOf() + + connectedComponents.graph.forEachIndexed { row, rowChars -> + rowChars.forEachIndexed { col, component -> + val coord = Coord(row, col) + areas[component] = areas.getOrPut(component) { 0 } + 1 + perimeters[component] = perimeters.getOrPut(component) { 0 } + connectedComponents.numOfEdges(coord) + } + } + return areas.keys.fold(0L) { acc, component -> + acc + areas[component]!! * perimeters[component]!! + } + } + + private fun List.numOfEdges(): Int { + var edgesCount = 0 + var lastEdge = this[0] + for (i in 1..() + val verEdges = mutableSetOf() + val queue = mutableSetOf() + val visited = mutableSetOf() + + queue.add(coord) + while (queue.isNotEmpty()) { + val curr = queue.elementAt(0) + queue.remove(curr) + visited.add(curr) + curr.edges().forEach { + if (it.isHorizontal()) horEdges.add(it) else verEdges.add(it) + } + curr.neighbors().forEach { neighbor -> + if (neighbor.isValid(size) && graph[neighbor] == component && !visited.contains(neighbor)) { + queue.add(neighbor) + } + } + } + val horEdgesPerRow = horEdges.groupBy { it.coord.row } + var horEdgesCount = 0 + horEdgesPerRow.forEach { (_, edges) -> + val upSorted = edges.filter { it.direction == Up }.sortedBy { it.coord.col } + val downSorted = edges.filter { it.direction == Down }.sortedBy { it.coord.col } + if (upSorted.isNotEmpty()) horEdgesCount += upSorted.numOfEdges() + if (downSorted.isNotEmpty()) horEdgesCount += downSorted.numOfEdges() + } + + val verEdgesPerCol = verEdges.groupBy { it.coord.col } + var verEdgesCount = 0 + verEdgesPerCol.forEach { (_, edges) -> + val leftSorted = edges.filter { it.direction == Left }.sortedBy { it.coord.row } + val rightSorted = edges.filter { it.direction == Right }.sortedBy { it.coord.row } + if (leftSorted.isNotEmpty()) verEdgesCount += leftSorted.numOfEdges() + if (rightSorted.isNotEmpty()) verEdgesCount += rightSorted.numOfEdges() + } + return horEdgesCount + verEdgesCount + } + + fun fenceBulkDiscountPrice(): Long { + val connectedComponents = connectedComponents() + val areas = mutableMapOf() + val edges = mutableMapOf() + + val visited = mutableSetOf() + connectedComponents.graph.forEachIndexed { row, rowChars -> + rowChars.forEachIndexed { col, component -> + val coord = Coord(row, col) + areas[component] = areas.getOrPut(component) { 0 } + 1 + if (!visited.contains(component)) { + edges[component] = connectedComponents.numOfSides(coord) + visited.add(component) + } + } + } + return areas.keys.fold(0L) { acc, component -> + acc + areas[component]!! * edges[component]!! + } + } + } + + fun part1(input: List): Long { + return MapManager(input).fencePrice() + } + + fun part2(input: List): Long { + return MapManager(input).fenceBulkDiscountPrice() + } + + val testInput11 = readInput("Day12_test1") + check(part1(testInput11) == 140L) + check(part2(testInput11) == 80L) + val testInput12 = readInput("Day12_test2") + check(part1(testInput12) == 1930L) + check(part2(testInput12) == 1206L) + + val input = readInput("Day12") + part1(input).println() + part2(input).println() +}