Compare commits

..

No commits in common. "0876e5c92ce78094ea01abdca00add9421fe40f3" and "15ce1ad06e8d0799e644d3d9b6e33caaf8621115" have entirely different histories.

4 changed files with 73 additions and 221 deletions

View File

@ -89,37 +89,28 @@ 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) = getOrNull(pos) ?: bChar operator fun get(pos: RelPos) = get(pos.dc, pos.dr)
operator fun set(pos: RelPos, newChar: Char?) { operator fun set(pos: RelPos, newChar: Char?) = set(pos.dc, pos.dr, newChar)
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) = (pos.dc in 0 until width) && (pos.dr in 0 until height) fun isInside(pos: RelPos) = isInside(pos.dc, pos.dr)
fun getHorizNumberValueAt(pos: RelPos): Pair<Int, List<RelPos>> { fun getHorizNumberValueAt(rp: RelPos) = getHorizNumberValueAt(rp.dc, rp.dr)
val relPos = getHorizNumberRelPosAt(pos.dc, pos.dr)
return relPos.map { this[pos.translate(it)].digitToInt() } 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() }
.reduce { acc, digit -> acc * 10 + digit } to relPos .reduce { acc, digit -> acc * 10 + digit } to relPos
} }
@ -136,72 +127,94 @@ class CharGrid {
fun copyOf() = CharGrid(Array(height) { data[it].copyOf() }, borderChar = bChar) fun copyOf() = CharGrid(Array(height) { data[it].copyOf() }, borderChar = bChar)
fun generateGridPos() = generateSequence(RelPos(0, 0)) { fun applyWithPos(op: (grid: CharGrid, col: Int, row: Int) -> Char?) {
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 (r in 0 until height) {
for (c in 0 until width) {
this[c, r] = op(this, c, r)
}
}
} }
@Deprecated("Use RelPos version instead") fun apply(op: (content: Char) -> Char?) {
fun applyWithPos(op: (grid: CharGrid, col: Int, row: Int) -> Char?) = applyWithPos { grid: CharGrid, col, row -> op(grid[col, row]) }
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, pos: RelPos) -> Char? = { orgContent: Char, _: Char, _: RelPos -> orgContent } op: (orgContent: Char, oldContent: Char, col: Int, row: Int) -> Char? = { orgContent: Char, _: Char, _: Int, _: Int -> orgContent }
): CharGrid { ): CharGrid {
val newGrid = copyOf() val newGrid = copyOf()
generateGridPos().filter { predicate(get(it)) } for (r in 0 until height) {
.forEach { pos -> for (c in 0 until width) {
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(pos.translate(it)) } relposes.mapNotNull { getOrNull(c + it.dc, r + it.dr) }
} else { } else {
relposes.map { get(pos.translate(it)) } relposes.map { get(c + it.dc, r + it.dr) }
} }
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(relposeList: Iterable<Iterable<RelPos>>, predicate: (content: String) -> Boolean): List<Pair<String, RelPos>> { fun findMatches(
return generateGridPos().flatMap { pos -> relposeList: Iterable<Iterable<RelPos>>,
relposeList.mapNotNull { rp -> predicate: (content: String) -> Boolean
val st = collectRelative(pos, rp).joinToString("") ): List<Pair<String, RelPos>> {
if (predicate(st)) st to pos else null 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))
} }
}.toList() }
}
}
return matches
} }
fun findCombinedMatches(relposeList: Iterable<Iterable<RelPos>>, predicate: (content: List<String>) -> Boolean): List<RelPos> { fun findCombinedMatches(
return generateGridPos().filter { pos -> relposeList: Iterable<Iterable<RelPos>>,
predicate(relposeList.map { rp -> collectRelative(pos, rp).joinToString("") }) predicate: (content: List<String>) -> Boolean
}.toList() ): 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 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>> {
return generateGridPos().filter { predicate(get(it)) }.map { it.dc to it.dr }.toList()
}
fun collectMatches(predicate: (char: Char) -> Boolean): List<Pair<Char, RelPos>> { fun findMatches(predicate: (char: Char) -> Boolean): List<Pair<Int, Int>> {
return generateGridPos().filter { predicate(get(it)) }.map { get(it) to it }.toList() 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
} }
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, pos -> wholeGrid.applyWithPos { grid, col, row ->
if (grid[pos].isDigit() && grownGrid[pos] != '.') { if (grid[col, row].isDigit() && grownGrid[col, row] != '.') {
val (num, relpos) = grid.getHorizNumberValueAt(pos) val (num, relpos) = grid.getHorizNumberValueAt(col, row)
relpos.forEach { grid[pos.translate(it)] = '.' } relpos.forEach { grid[col + it.dc, row + it.dr] = '.' }
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, _: RelPos -> grid = grid.grow(CharGrid.PLUS_POS, { it == 'S' }, { org: Char, old: Char, _: Int, _: Int ->
if (old != '#') 'O' else old if (old != '#') 'O' else old
}) })
grid.apply { grid.apply {

View File

@ -1,161 +0,0 @@
package aoc2024
import CharGrid
import RelPos
import println
import readInput
/*
--- Day 8: Resonant Collinearity ---
You find yourselves on the roof of a top-secret Easter Bunny installation.
While The Historians do their thing, you take a look at the familiar huge antenna. Much to your surprise, it seems to have been reconfigured to emit a signal that makes people 0.1% more likely to buy Easter Bunny brand Imitation Mediocre Chocolate as a Christmas gift! Unthinkable!
Scanning across the city, you find that there are actually many such antennas. Each antenna is tuned to a specific frequency indicated by a single lowercase letter, uppercase letter, or digit. You create a map (your puzzle input) of these antennas. For example:
............
........0...
.....0......
.......0....
....0.......
......A.....
............
............
........A...
.........A..
............
............
The signal only applies its nefarious effect at specific antinodes based on the resonant frequencies of the antennas. In particular, an antinode occurs at any point that is perfectly in line with two antennas of the same frequency - but only when one of the antennas is twice as far away as the other. This means that for any pair of antennas with the same frequency, there are two antinodes, one on either side of them.
So, for these two antennas with frequency a, they create the two antinodes marked with #:
..........
...#......
..........
....a.....
..........
.....a....
..........
......#...
..........
..........
Adding a third antenna with the same frequency creates several more antinodes. It would ideally add four antinodes, but two are off the right side of the map, so instead it adds only two:
..........
...#......
#.........
....a.....
........a.
.....a....
..#.......
......#...
..........
..........
Antennas with different frequencies don't create antinodes; A and a count as different frequencies. However, antinodes can occur at locations that contain antennas. In this diagram, the lone antenna with frequency capital A creates no antinodes but has a lowercase-a-frequency antinode at its location:
..........
...#......
#.........
....a.....
........a.
.....a....
..#.......
......A...
..........
..........
The first example has antennas with two different frequencies, so the antinodes they create look like this, plus an antinode overlapping the topmost A-frequency antenna:
......#....#
...#....0...
....#0....#.
..#....0....
....0....#..
.#....A.....
...#........
#......#....
........A...
.........A..
..........#.
..........#.
Because the topmost A-frequency antenna overlaps with a 0-frequency antinode, there are 14 total unique locations that contain an antinode within the bounds of the map.
Calculate the impact of the signal. How many unique locations within the bounds of the map contain an antinode?
*/
fun main() {
val inlineTestInput = """
............
........0...
.....0......
.......0....
....0.......
......A.....
............
............
........A...
.........A..
............
............
"""
fun part1(input: List<String>): Int {
val grid = CharGrid(input)
val antennas = grid.collectMatches { it != '.' }.groupBy({ it.first }) { it.second }
val antinodes = HashSet<RelPos>()
for (at in antennas.values) {
for ((i1, p1) in at.withIndex()) {
for (i2 in i1 + 1 until at.size) {
val p2 = at[i2]
val ap1 = RelPos(p2.dc + (p2.dc - p1.dc), p2.dr + (p2.dr - p1.dr))
val ap2 = RelPos(p1.dc - (p2.dc - p1.dc), p1.dr - (p2.dr - p1.dr))
if (grid.isInside(ap1)) antinodes.add(ap1)
if (grid.isInside(ap2)) antinodes.add(ap2)
}
}
}
return antinodes.size
}
fun part2(input: List<String>): Int {
val grid = CharGrid(input)
val antennas = grid.collectMatches { it != '.' }.groupBy({ it.first }) { it.second }
val antinodes = HashSet<Pair<RelPos, RelPos>>()
for (at in antennas.values) {
for ((i1, p1) in at.withIndex()) {
for (i2 in i1 + 1 until at.size) {
val p2 = at[i2]
val dc = p2.dc - p1.dc
val dr = p2.dr - p1.dr
val sc = p1.dc
val sr = p1.dr
antinodes.add(RelPos(sc, sr) to RelPos(dc, dr))
}
}
}
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)
}
}
}
// test if implementation meets criteria from the description, like:
val testInput = inlineTestInput.trim().reader().readLines()
//val testInput = readInput("aoc2024/Day08_test")
val testInputPart1Result = part1(testInput)
println("Part 1 Test: $testInputPart1Result")
val testInputPart2Result = part2(testInput)
println("Part 2 Test: $testInputPart2Result")
check(testInputPart1Result == 14)
//check(testInputPart2Result == 34)
val input = readInput("aoc2024/Day08")
part1(input).println()
part2(input).println()
}