From 0876e5c92ce78094ea01abdca00add9421fe40f3 Mon Sep 17 00:00:00 2001 From: chrisly42 Date: Sun, 8 Dec 2024 08:05:40 +0100 Subject: [PATCH] Refactored CharGrid class to use a generator instead of two for-loops. Deprecated old API. --- src/Utils.kt | 129 +++++++++++++++++-------------------------- src/aoc2023/Day03.kt | 8 +-- src/aoc2023/Day21.kt | 2 +- src/aoc2024/Day08.kt | 25 +++------ 4 files changed, 66 insertions(+), 98 deletions(-) diff --git a/src/Utils.kt b/src/Utils.kt index 4665d7b..d95f024 100644 --- a/src/Utils.kt +++ b/src/Utils.kt @@ -89,28 +89,37 @@ class CharGrid { height = inputGrid.size } + @Deprecated("Use RelPos version instead") operator fun get(col: Int, row: Int) = getOrNull(col, row) ?: bChar + + @Deprecated("Use RelPos version instead") operator fun set(col: Int, row: Int, newChar: Char?) { if (newChar != null && isInside(col, row)) data[row][col] = newChar } - operator fun get(pos: RelPos) = get(pos.dc, pos.dr) - operator fun set(pos: RelPos, newChar: Char?) = set(pos.dc, pos.dr, newChar) + operator fun get(pos: RelPos) = getOrNull(pos) ?: bChar + operator fun set(pos: RelPos, newChar: Char?) { + if (newChar != null && isInside(pos)) data[pos.dr][pos.dc] = newChar + } + + @Deprecated("Use RelPos version instead") fun setOrThrow(col: Int, row: Int, newChar: Char) { if (!isInside(col, row)) throw IndexOutOfBoundsException("$col, $row out of bounds") set(col, row, newChar) } + @Deprecated("Use RelPos version instead") fun getOrNull(col: Int, row: Int) = if (isInside(col, row)) data[row][col] else null + fun getOrNull(pos: RelPos) = if (isInside(pos)) data[pos.dr][pos.dc] else null + + @Deprecated("Use RelPos version instead") fun isInside(col: Int, row: Int) = (col in 0 until width) && (row in 0 until height) - fun isInside(pos: RelPos) = isInside(pos.dc, pos.dr) + fun isInside(pos: RelPos) = (pos.dc in 0 until width) && (pos.dr in 0 until height) - fun getHorizNumberValueAt(rp: RelPos) = getHorizNumberValueAt(rp.dc, rp.dr) - - fun getHorizNumberValueAt(col: Int, row: Int): Pair> { - val relPos = getHorizNumberRelPosAt(col, row) - return relPos.map { this[col + it.dc, row + it.dr].digitToInt() } + fun getHorizNumberValueAt(pos: RelPos): Pair> { + val relPos = getHorizNumberRelPosAt(pos.dc, pos.dr) + return relPos.map { this[pos.translate(it)].digitToInt() } .reduce { acc, digit -> acc * 10 + digit } to relPos } @@ -127,106 +136,72 @@ class CharGrid { fun copyOf() = CharGrid(Array(height) { data[it].copyOf() }, borderChar = bChar) - fun applyWithPos(op: (grid: CharGrid, col: Int, row: Int) -> Char?) { - for (r in 0 until height) { - for (c in 0 until width) { - this[c, r] = op(this, c, r) - } - } + fun generateGridPos() = generateSequence(RelPos(0, 0)) { + if (it.dc + 1 < width) RelPos(it.dc + 1, it.dr) else if (it.dr + 1 < height) RelPos(0, it.dr + 1) else null } - fun apply(op: (content: Char) -> Char?) { - applyWithPos { grid: CharGrid, col, row -> op(grid[col, row]) } - } + @Deprecated("Use RelPos version instead") + fun applyWithPos(op: (grid: CharGrid, col: Int, row: Int) -> Char?) = + generateGridPos().forEach { this[it] = op(this, it.dc, it.dr) } + + fun applyWithPos(op: (grid: CharGrid, pos: RelPos) -> Char?) = + generateGridPos().forEach { this[it] = op(this, it) } + + fun apply(op: (content: Char) -> Char?) = + applyWithPos { grid: CharGrid, pos -> op(grid[pos]) } fun grow( relposes: Iterable, predicate: (content: Char) -> Boolean, - op: (orgContent: Char, oldContent: Char, col: Int, row: Int) -> Char? = { orgContent: Char, _: Char, _: Int, _: Int -> orgContent } + op: (orgContent: Char, oldContent: Char, pos: RelPos) -> Char? = { orgContent: Char, _: Char, _: RelPos -> orgContent } ): CharGrid { val newGrid = copyOf() - for (r in 0 until height) { - for (c in 0 until width) { - if (predicate(get(c, r))) { - relposes - .map { it.translate(c, r) } - .filter { isInside(it.dc, it.dr) } - .forEach { newGrid[it.dc, it.dr] = op(this[c, r], newGrid[it.dc, it.dr], it.dc, it.dr) } - } + generateGridPos().filter { predicate(get(it)) } + .forEach { pos -> + relposes + .map { it.translate(pos) } + .filter { isInside(it) } + .forEach { newGrid[it] = op(this[pos], newGrid[it], it) } } - } return newGrid } fun collectRelative(c: Int, r: Int, relposes: Iterable, skipBorder: Boolean = true): List = + collectRelative(RelPos(c, r), relposes, skipBorder) + + fun collectRelative(pos: RelPos, relposes: Iterable, skipBorder: Boolean = true): List = if (skipBorder) { - relposes.mapNotNull { getOrNull(c + it.dc, r + it.dr) } + relposes.mapNotNull { getOrNull(pos.translate(it)) } } else { - relposes.map { get(c + it.dc, r + it.dr) } + relposes.map { get(pos.translate(it)) } } fun countStrings(relposeList: Iterable>, string: String) = findMatches(relposeList) { it == string }.count() - fun findMatches( - relposeList: Iterable>, - predicate: (content: String) -> Boolean - ): List> { - val matches = ArrayList>() - for (r in 0 until height) { - for (c in 0 until width) { - for (rp in relposeList) { - val st = collectRelative(c, r, rp).joinToString("") - if (predicate(st)) { - matches.add(st to RelPos(c, r)) - } - } + fun findMatches(relposeList: Iterable>, predicate: (content: String) -> Boolean): List> { + return generateGridPos().flatMap { pos -> + relposeList.mapNotNull { rp -> + val st = collectRelative(pos, rp).joinToString("") + if (predicate(st)) st to pos else null } - } - return matches + }.toList() } - fun findCombinedMatches( - relposeList: Iterable>, - predicate: (content: List) -> Boolean - ): List { - val matches = ArrayList() - for (r in 0 until height) { - for (c in 0 until width) { - val str = relposeList.map { collectRelative(c, r, it).joinToString("") } - if (predicate(str)) { - matches.add(RelPos(c, r)) - } - } - } - return matches + fun findCombinedMatches(relposeList: Iterable>, predicate: (content: List) -> Boolean): List { + return generateGridPos().filter { pos -> + predicate(relposeList.map { rp -> collectRelative(pos, rp).joinToString("") }) + }.toList() } fun matchRelative(c: Int, r: Int, relposes: Iterable, predicate: (char: Char) -> Boolean): List = relposes.filter { predicate(get(c + it.dc, r + it.dr)) } - fun findMatches(predicate: (char: Char) -> Boolean): List> { - val matches = ArrayList>() - for (r in 0 until height) { - for (c in 0 until width) { - if (predicate(get(c, r))) { - matches.add(c to r) - } - } - } - return matches + return generateGridPos().filter { predicate(get(it)) }.map { it.dc to it.dr }.toList() } fun collectMatches(predicate: (char: Char) -> Boolean): List> { - val matches = ArrayList>() - for (r in 0 until height) { - for (c in 0 until width) { - if (predicate(get(c, r))) { - matches.add(get(c, r) to RelPos(c, r)) - } - } - } - return matches + return generateGridPos().filter { predicate(get(it)) }.map { get(it) to it }.toList() } fun addBorder(borderChar: Char = bChar): CharGrid { diff --git a/src/aoc2023/Day03.kt b/src/aoc2023/Day03.kt index 4fd5e1e..66ee4aa 100644 --- a/src/aoc2023/Day03.kt +++ b/src/aoc2023/Day03.kt @@ -69,10 +69,10 @@ fun main() { symbolGrid.apply { c -> if (!c.isDigit()) c else '.' } val grownGrid = symbolGrid.grow(CharGrid.BOX_POS, { c -> c != '.' }) var sum = 0 - wholeGrid.applyWithPos { grid, col, row -> - if (grid[col, row].isDigit() && grownGrid[col, row] != '.') { - val (num, relpos) = grid.getHorizNumberValueAt(col, row) - relpos.forEach { grid[col + it.dc, row + it.dr] = '.' } + wholeGrid.applyWithPos { grid, pos -> + if (grid[pos].isDigit() && grownGrid[pos] != '.') { + val (num, relpos) = grid.getHorizNumberValueAt(pos) + relpos.forEach { grid[pos.translate(it)] = '.' } sum += num } null diff --git a/src/aoc2023/Day21.kt b/src/aoc2023/Day21.kt index d788b04..5dad6c6 100644 --- a/src/aoc2023/Day21.kt +++ b/src/aoc2023/Day21.kt @@ -100,7 +100,7 @@ fun main() { fun part1(input: List, steps: Int): Int { var grid = CharGrid(input, '#') for (i in 1..steps) { - grid = grid.grow(CharGrid.PLUS_POS, { it == 'S' }, { org: Char, old: Char, _: Int, _: Int -> + grid = grid.grow(CharGrid.PLUS_POS, { it == 'S' }, { org: Char, old: Char, _: RelPos -> if (old != '#') 'O' else old }) grid.apply { diff --git a/src/aoc2024/Day08.kt b/src/aoc2024/Day08.kt index a8e5b5e..14c1b62 100644 --- a/src/aoc2024/Day08.kt +++ b/src/aoc2024/Day08.kt @@ -132,24 +132,17 @@ fun main() { } } - var sum = 0 - for (r in 0 until grid.height) { - for (c in 0 until grid.width) { - if (antinodes.any { - val rr = r - it.first.dr - val rc = c - it.first.dc - val f1 = rr / it.second.dr - val f2 = rc / it.second.dc - ((rr % it.second.dr) == 0) && - ((rc % it.second.dc) == 0) && - (f1 == f2) - }) { - sum++ - } + return grid.generateGridPos().count { pos -> + antinodes.any { + val rr = pos.dr - it.first.dr + val rc = pos.dc - it.first.dc + val f1 = rr / it.second.dr + val f2 = rc / it.second.dc + ((rr % it.second.dr) == 0) && + ((rc % it.second.dc) == 0) && + (f1 == f2) } } - - return sum } // test if implementation meets criteria from the description, like: