Refactored CharGrid class to use a generator instead of two for-loops. Deprecated old API.

This commit is contained in:
Chris Hodges 2024-12-08 08:05:40 +01:00
parent 9d61ce1bc4
commit 0876e5c92c
4 changed files with 66 additions and 98 deletions

View File

@ -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<Int, List<RelPos>> {
val relPos = getHorizNumberRelPosAt(col, row)
return relPos.map { this[col + it.dc, row + it.dr].digitToInt() }
fun getHorizNumberValueAt(pos: RelPos): Pair<Int, List<RelPos>> {
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<RelPos>, 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))) {
generateGridPos().filter { predicate(get(it)) }
.forEach { pos ->
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) }
}
}
.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<RelPos>, skipBorder: Boolean = true): List<Char> =
collectRelative(RelPos(c, r), relposes, skipBorder)
fun collectRelative(pos: RelPos, relposes: Iterable<RelPos>, skipBorder: Boolean = true): List<Char> =
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<Iterable<RelPos>>, string: String) =
findMatches(relposeList) { it == string }.count()
fun findMatches(
relposeList: Iterable<Iterable<RelPos>>,
predicate: (content: String) -> Boolean
): List<Pair<String, RelPos>> {
val matches = ArrayList<Pair<String, RelPos>>()
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<Iterable<RelPos>>, predicate: (content: String) -> Boolean): List<Pair<String, RelPos>> {
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<Iterable<RelPos>>,
predicate: (content: List<String>) -> Boolean
): List<RelPos> {
val matches = ArrayList<RelPos>()
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<Iterable<RelPos>>, predicate: (content: List<String>) -> Boolean): List<RelPos> {
return generateGridPos().filter { pos ->
predicate(relposeList.map { rp -> collectRelative(pos, rp).joinToString("") })
}.toList()
}
fun matchRelative(c: Int, r: Int, relposes: Iterable<RelPos>, predicate: (char: Char) -> Boolean): List<RelPos> =
relposes.filter { predicate(get(c + it.dc, r + it.dr)) }
fun findMatches(predicate: (char: Char) -> Boolean): List<Pair<Int, Int>> {
val matches = ArrayList<Pair<Int, Int>>()
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<Pair<Char, RelPos>> {
val matches = ArrayList<Pair<Char, RelPos>>()
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 {

View File

@ -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

View File

@ -100,7 +100,7 @@ fun main() {
fun part1(input: List<String>, 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 {

View File

@ -132,26 +132,19 @@ 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
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)
}) {
sum++
}
}
}
return sum
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2024/Day08_test")