219 lines
7.7 KiB
Kotlin
219 lines
7.7 KiB
Kotlin
package be.vandewalleh.aoc.days
|
|
|
|
import be.vandewalleh.aoc.days.geometry.Grid
|
|
import be.vandewalleh.aoc.days.geometry.gridOf
|
|
import be.vandewalleh.aoc.days.geometry.transformations
|
|
import be.vandewalleh.aoc.utils.input.Day
|
|
import be.vandewalleh.aoc.utils.input.Groups
|
|
import be.vandewalleh.aoc.utils.input.Input
|
|
import be.vandewalleh.aoc.utils.input.createDay
|
|
import java.util.*
|
|
import kotlin.collections.ArrayList
|
|
import kotlin.math.sqrt
|
|
|
|
private typealias Tile = Grid<Char>
|
|
|
|
@Day(20)
|
|
class Day20(@Groups val input: Input<List<List<String>>>) {
|
|
private val tiles: Map<Int, Tile> = input.value
|
|
.map { it[0].let { it.substring(5 until it.indexOf(':')).toInt() } to it.drop(1) }
|
|
.associate { (id, tile) -> id to gridOf(tile) }
|
|
|
|
private fun Tile.allEdges() = transformations().flatMap { it.edges() }
|
|
|
|
private fun edgesMatch(a: Tile, b: Tile): Boolean {
|
|
val edges = b.allEdges()
|
|
return a.allEdges().any { a -> edges.any { a == it } }
|
|
}
|
|
|
|
private fun combine(tiles: List<Tile>) = sequence {
|
|
for (i in 0 until tiles.lastIndex) {
|
|
val a = tiles[i]
|
|
for (j in i + 1 until tiles.size) {
|
|
val b = tiles[j]
|
|
yield(a to b)
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun tilesByNeighbourCount(): MutableMap<Int, MutableList<Tile>> {
|
|
val neighbours = combine(tiles.values.toList())
|
|
.filter { (a, b) -> edgesMatch(a, b) }
|
|
.map { it.toList() }
|
|
.flatten()
|
|
.groupBy { it }
|
|
.values
|
|
|
|
val map = mutableMapOf<Int, MutableList<Tile>>()
|
|
for (neighbour in neighbours) {
|
|
map.computeIfAbsent(neighbour.size) { mutableListOf() }.add(neighbour.first())
|
|
}
|
|
return map
|
|
}
|
|
|
|
fun part1() = tilesByNeighbourCount()[2]!!
|
|
.map { tile -> tiles.entries.find { it.value == tile }!!.key.toLong() }
|
|
.reduce { acc, id -> acc * id }
|
|
|
|
private fun neighboursCount(grid: Grid<*>, x: Int, y: Int): Int {
|
|
var neighboursCount = 2
|
|
if (x != 0 && x != grid.lastColumnIndex) neighboursCount++
|
|
if (y != 0 && y != grid.lastRowIndex) neighboursCount++
|
|
return neighboursCount
|
|
}
|
|
|
|
private fun isLeftOk(grid: Grid<Tile?>, x: Int, y: Int, candidate: Tile) = grid[x - 1, y]
|
|
?.let { left -> candidate.firstColumn() == left.lastColumn() }
|
|
?: true
|
|
|
|
private fun isTopOk(grid: Grid<Tile?>, x: Int, y: Int, candidate: Tile) = grid[x, y - 1]
|
|
?.let { top -> candidate.firstRow() == top.lastRow() }
|
|
?: true
|
|
|
|
private fun isBottomOk(grid: Grid<*>, x: Int, y: Int, candidate: Tile, neighbours: MutableMap<Int, MutableList<Tile>>) =
|
|
if (y == grid.lastRowIndex) true
|
|
else neighbours[neighboursCount(grid, x, y + 1)]!!
|
|
.filterNot { it == candidate }
|
|
.count { it.transformations().any { candidate.lastColumn() == it.firstColumn() } } == 1
|
|
|
|
private fun isRightOk(grid: Grid<*>, x: Int, y: Int, candidate: Tile, neighbours: MutableMap<Int, MutableList<Tile>>) =
|
|
if (x == grid.lastColumnIndex) true
|
|
else neighbours[neighboursCount(grid, x + 1, y)]!!
|
|
.filterNot { it == candidate }
|
|
.count { it.transformations().any { candidate.lastRow() == it.firstRow() } } == 1
|
|
|
|
private fun assembleGrid(): Grid<Tile?> {
|
|
val size = sqrt(tiles.size.toDouble()).toInt()
|
|
val grid: Grid<Tile?> = Grid(Array(size) { Array(size) { null } })
|
|
|
|
val neighbours: MutableMap<Int, MutableList<Tile>> = tilesByNeighbourCount()
|
|
|
|
for (y in 0 until grid.height) {
|
|
for (x in 0 until grid.width) {
|
|
val neighboursCount = neighboursCount(grid, x, y)
|
|
val candidates = neighbours[neighboursCount]!!
|
|
|
|
val conditions = mutableListOf<(Tile) -> Boolean>()
|
|
conditions += { isLeftOk(grid, x, y, it) }
|
|
conditions += { isTopOk(grid, x, y, it) }
|
|
|
|
// why is this condition needed ?
|
|
if (x == 0 && y == 0) {
|
|
conditions += { isBottomOk(grid, x, y, it, neighbours) }
|
|
conditions += { isRightOk(grid, x, y, it, neighbours) }
|
|
}
|
|
|
|
var found: Tile? = null
|
|
|
|
outer@ for (candidate in candidates) {
|
|
for (transform in candidate.transformations()) {
|
|
if (conditions.all { it(transform) }) {
|
|
found = transform
|
|
candidates.remove(candidate)
|
|
break@outer
|
|
}
|
|
}
|
|
}
|
|
|
|
check(found != null)
|
|
grid[x, y] = found
|
|
}
|
|
}
|
|
|
|
return grid
|
|
}
|
|
|
|
private fun removeGaps(grid: Grid<Tile?>) {
|
|
for (y in 0 until grid.height) {
|
|
for (x in 0 until grid.width) {
|
|
grid[x, y] = removeGaps(grid[x, y]!!)
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun removeGaps(tile: Tile): Tile {
|
|
val oldData: ArrayList<ArrayList<Char>> = tile.data
|
|
val newData = ArrayList<ArrayList<Char>>(oldData.size - 2)
|
|
oldData.subList(1, oldData.size - 1).forEach { d ->
|
|
val l = ArrayList<Char>().apply {
|
|
addAll(d.subList(1, d.size - 1))
|
|
}
|
|
newData.add(l)
|
|
}
|
|
return Tile(newData)
|
|
}
|
|
|
|
private fun gridToTile(grid: Grid<Tile?>): Tile {
|
|
val newData = ArrayList<ArrayList<Char>>()
|
|
for (y in 0 until grid.height) {
|
|
val row = grid.row(y)
|
|
for (yy in 0 until row[0]!!.height) {
|
|
val combinedRow = ArrayList<Char>()
|
|
row.forEach { combinedRow.addAll(it!!.row(yy)) }
|
|
newData.add(combinedRow)
|
|
}
|
|
}
|
|
return Tile(newData)
|
|
}
|
|
|
|
private fun Tile.subGridData(startX: Int, startY: Int, width: Int, height: Int): List<List<Char>> {
|
|
val newData = ArrayList<ArrayList<Char>>()
|
|
for (y in startY until startY + height) {
|
|
val row = ArrayList<Char>()
|
|
newData.add(row)
|
|
for (x in startX until startX + width) {
|
|
row.add(this[x, y]!!)
|
|
}
|
|
}
|
|
return newData
|
|
}
|
|
|
|
private fun List<List<Char>>.isMonster(monster: List<List<Char>>): Boolean {
|
|
val monsterRe = monster.joinToString("") { it.joinToString("") }.replace(" ", ".").toRegex()
|
|
val str = this.joinToString("") { it.joinToString("") }
|
|
return monsterRe.matches(str)
|
|
}
|
|
|
|
fun part2() {
|
|
val grid = assembleGrid()
|
|
removeGaps(grid)
|
|
val megaTile = gridToTile(grid)
|
|
|
|
val monster: List<List<Char>> = """
|
|
| # |
|
|
|# ## ## ###|
|
|
| # # # # # # |
|
|
""".trimMargin("|").lines().map { it.toCharArray().toList().dropLast(1) }
|
|
|
|
println(monster)
|
|
|
|
val monsterWidth = monster[0].size
|
|
val monsterHeight = 3
|
|
val monsterSquares = monster.joinToString("") { it.joinToString("") }.count { it == '#' }
|
|
|
|
val squares = megaTile.data.joinToString("") { it.joinToString("") }.count { it == '#' }
|
|
|
|
for (g: Tile in megaTile.transformations()) {
|
|
var count = 0
|
|
for (y in 0 until g.lastRowIndex - monsterHeight) {
|
|
for (x in 0 until g.lastColumnIndex - monsterWidth) {
|
|
val subgrid = g.subGridData(x, y, monsterWidth, monsterHeight)
|
|
if (subgrid.isMonster(monster)) {
|
|
count++
|
|
}
|
|
}
|
|
}
|
|
if (count != 0) {
|
|
println(count)
|
|
println(squares - (count * monsterSquares))
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
fun main() = with(createDay<Day20>()) {
|
|
println(part1())
|
|
println(part2())
|
|
}
|