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 height = inputGrid.size
} }
@Deprecated("Use RelPos version instead")
operator fun get(col: Int, row: Int) = getOrNull(col, row) ?: bChar 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?) { operator fun set(col: Int, row: Int, newChar: Char?) {
if (newChar != null && isInside(col, row)) data[row][col] = newChar if (newChar != null && isInside(col, row)) data[row][col] = newChar
} }
operator fun get(pos: RelPos) = get(pos.dc, pos.dr) operator fun get(pos: RelPos) = getOrNull(pos) ?: bChar
operator fun set(pos: RelPos, newChar: Char?) = set(pos.dc, pos.dr, newChar) 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) { fun setOrThrow(col: Int, row: Int, newChar: Char) {
if (!isInside(col, row)) throw IndexOutOfBoundsException("$col, $row out of bounds") if (!isInside(col, row)) throw IndexOutOfBoundsException("$col, $row out of bounds")
set(col, row, newChar) 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(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(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(pos: RelPos): Pair<Int, List<RelPos>> {
val relPos = getHorizNumberRelPosAt(pos.dc, pos.dr)
fun getHorizNumberValueAt(col: Int, row: Int): Pair<Int, List<RelPos>> { return relPos.map { this[pos.translate(it)].digitToInt() }
val relPos = getHorizNumberRelPosAt(col, row)
return relPos.map { this[col + it.dc, row + it.dr].digitToInt() }
.reduce { acc, digit -> acc * 10 + digit } to relPos .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 copyOf() = CharGrid(Array(height) { data[it].copyOf() }, borderChar = bChar)
fun applyWithPos(op: (grid: CharGrid, col: Int, row: Int) -> Char?) { fun generateGridPos() = generateSequence(RelPos(0, 0)) {
for (r in 0 until height) { if (it.dc + 1 < width) RelPos(it.dc + 1, it.dr) else if (it.dr + 1 < height) RelPos(0, it.dr + 1) else null
for (c in 0 until width) {
this[c, r] = op(this, c, r)
}
}
} }
fun apply(op: (content: Char) -> Char?) { @Deprecated("Use RelPos version instead")
applyWithPos { grid: CharGrid, col, row -> op(grid[col, row]) } 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( fun grow(
relposes: Iterable<RelPos>, predicate: (content: Char) -> Boolean, 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 { ): CharGrid {
val newGrid = copyOf() val newGrid = copyOf()
for (r in 0 until height) { generateGridPos().filter { predicate(get(it)) }
for (c in 0 until width) { .forEach { pos ->
if (predicate(get(c, r))) { relposes
relposes .map { it.translate(pos) }
.map { it.translate(c, r) } .filter { isInside(it) }
.filter { isInside(it.dc, it.dr) } .forEach { newGrid[it] = op(this[pos], newGrid[it], it) }
.forEach { newGrid[it.dc, it.dr] = op(this[c, r], newGrid[it.dc, it.dr], it.dc, it.dr) }
}
} }
}
return newGrid return newGrid
} }
fun collectRelative(c: Int, r: Int, relposes: Iterable<RelPos>, skipBorder: Boolean = true): List<Char> = 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) { if (skipBorder) {
relposes.mapNotNull { getOrNull(c + it.dc, r + it.dr) } relposes.mapNotNull { getOrNull(pos.translate(it)) }
} else { } else {
relposes.map { get(c + it.dc, r + it.dr) } relposes.map { get(pos.translate(it)) }
} }
fun countStrings(relposeList: Iterable<Iterable<RelPos>>, string: String) = fun countStrings(relposeList: Iterable<Iterable<RelPos>>, string: String) =
findMatches(relposeList) { it == string }.count() findMatches(relposeList) { it == string }.count()
fun findMatches( fun findMatches(relposeList: Iterable<Iterable<RelPos>>, predicate: (content: String) -> Boolean): List<Pair<String, RelPos>> {
relposeList: Iterable<Iterable<RelPos>>, return generateGridPos().flatMap { pos ->
predicate: (content: String) -> Boolean relposeList.mapNotNull { rp ->
): List<Pair<String, RelPos>> { val st = collectRelative(pos, rp).joinToString("")
val matches = ArrayList<Pair<String, RelPos>>() if (predicate(st)) st to pos else null
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))
}
}
} }
} }.toList()
return matches
} }
fun findCombinedMatches( fun findCombinedMatches(relposeList: Iterable<Iterable<RelPos>>, predicate: (content: List<String>) -> Boolean): List<RelPos> {
relposeList: Iterable<Iterable<RelPos>>, return generateGridPos().filter { pos ->
predicate: (content: List<String>) -> Boolean predicate(relposeList.map { rp -> collectRelative(pos, rp).joinToString("") })
): List<RelPos> { }.toList()
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 matchRelative(c: Int, r: Int, relposes: Iterable<RelPos>, predicate: (char: Char) -> Boolean): List<RelPos> = 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)) } relposes.filter { predicate(get(c + it.dc, r + it.dr)) }
fun findMatches(predicate: (char: Char) -> Boolean): List<Pair<Int, Int>> { fun findMatches(predicate: (char: Char) -> Boolean): List<Pair<Int, Int>> {
val matches = ArrayList<Pair<Int, Int>>() return generateGridPos().filter { predicate(get(it)) }.map { it.dc to it.dr }.toList()
for (r in 0 until height) {
for (c in 0 until width) {
if (predicate(get(c, r))) {
matches.add(c to r)
}
}
}
return matches
} }
fun collectMatches(predicate: (char: Char) -> Boolean): List<Pair<Char, RelPos>> { fun collectMatches(predicate: (char: Char) -> Boolean): List<Pair<Char, RelPos>> {
val matches = ArrayList<Pair<Char, RelPos>>() return generateGridPos().filter { predicate(get(it)) }.map { get(it) to it }.toList()
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
} }
fun addBorder(borderChar: Char = bChar): CharGrid { fun addBorder(borderChar: Char = bChar): CharGrid {

View File

@ -69,10 +69,10 @@ fun main() {
symbolGrid.apply { c -> if (!c.isDigit()) c else '.' } symbolGrid.apply { c -> if (!c.isDigit()) c else '.' }
val grownGrid = symbolGrid.grow(CharGrid.BOX_POS, { c -> c != '.' }) val grownGrid = symbolGrid.grow(CharGrid.BOX_POS, { c -> c != '.' })
var sum = 0 var sum = 0
wholeGrid.applyWithPos { grid, col, row -> wholeGrid.applyWithPos { grid, pos ->
if (grid[col, row].isDigit() && grownGrid[col, row] != '.') { if (grid[pos].isDigit() && grownGrid[pos] != '.') {
val (num, relpos) = grid.getHorizNumberValueAt(col, row) val (num, relpos) = grid.getHorizNumberValueAt(pos)
relpos.forEach { grid[col + it.dc, row + it.dr] = '.' } relpos.forEach { grid[pos.translate(it)] = '.' }
sum += num sum += num
} }
null null

View File

@ -100,7 +100,7 @@ fun main() {
fun part1(input: List<String>, steps: Int): Int { fun part1(input: List<String>, steps: Int): Int {
var grid = CharGrid(input, '#') var grid = CharGrid(input, '#')
for (i in 1..steps) { 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 if (old != '#') 'O' else old
}) })
grid.apply { grid.apply {

View File

@ -132,24 +132,17 @@ fun main() {
} }
} }
var sum = 0 return grid.generateGridPos().count { pos ->
for (r in 0 until grid.height) { antinodes.any {
for (c in 0 until grid.width) { val rr = pos.dr - it.first.dr
if (antinodes.any { val rc = pos.dc - it.first.dc
val rr = r - it.first.dr val f1 = rr / it.second.dr
val rc = c - it.first.dc val f2 = rc / it.second.dc
val f1 = rr / it.second.dr ((rr % it.second.dr) == 0) &&
val f2 = rc / it.second.dc ((rc % it.second.dc) == 0) &&
((rr % it.second.dr) == 0) && (f1 == f2)
((rc % it.second.dc) == 0) &&
(f1 == f2)
}) {
sum++
}
} }
} }
return sum
} }
// test if implementation meets criteria from the description, like: // test if implementation meets criteria from the description, like: