import java.io.File import java.math.BigInteger import java.security.MessageDigest /** * Reads lines from the given input txt file. */ fun readInput(name: String) = File("src", "$name.txt") .readLines() fun listOfIntegerLists(input: List): ArrayList> { val output = ArrayList>() var currList = ArrayList() for (i in input) { if (i.isEmpty()) { output.add(currList) currList = ArrayList() } else { currList.add(i.toInt()) } } if (currList.isNotEmpty()) { output.add(currList) } return output } data class RelPos(val dc: Int, val dr: Int) { fun translate(c: Int, r: Int) = RelPos(c + dc, r + dr) } class CharGrid { companion object { val PLUS_POS = listOf(RelPos(0, -1), RelPos(-1, 0), RelPos(1, 0), RelPos(0, 1)) val PLUS_CENTER_POS = listOf(RelPos(0, -1), RelPos(-1, 0), RelPos(0, 0), RelPos(1, 0), RelPos(0, 1)) val CROSS_POS = listOf(RelPos(-1, -1), RelPos(1, -1), RelPos(-1, 1), RelPos(1, 1)) val CROSS_CENTER_POS = listOf(RelPos(-1, -1), RelPos(1, -1), RelPos(0, 0), RelPos(-1, 1), RelPos(1, 1)) val BOX_POS = listOf( RelPos(-1, -1), RelPos(0, -1), RelPos(1, -1), RelPos(-1, 0), RelPos(1, 0), RelPos(-1, 1), RelPos(0, 1), RelPos(1, 1) ) val NINE_POS = listOf( RelPos(-1, -1), RelPos(0, -1), RelPos(1, -1), RelPos(-1, 0), RelPos(0, 0), RelPos(1, 0), RelPos(-1, 1), RelPos(0, 1), RelPos(1, 1) ) } val width: Int val height: Int val data: Array val bChar: Char constructor(input: List, borderChar: Char = ' ') { bChar = borderChar width = input[0].length height = input.size data = Array(height) { input[it].toCharArray() } } constructor(inputGrid: Array, borderChar: Char = ' ') { bChar = borderChar this.data = inputGrid width = inputGrid[0].size height = inputGrid.size } operator fun get(col: Int, row: Int) = getOrNull(col, row) ?: bChar operator fun set(col: Int, row: Int, newChar: Char?) { if (newChar != null && isInside(col, row)) data[row][col] = newChar } fun setOrThrow(col: Int, row: Int, newChar: Char) { if (!isInside(col, row)) throw IndexOutOfBoundsException("$col, $row out of bounds") set(col, row, newChar) } fun getOrNull(col: Int, row: Int) = if (isInside(col, row)) data[row][col] else null fun isInside(col: Int, row: Int) = (col in 0 until width) && (row 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() }.reduce { acc, digit -> acc * 10 + digit } to relPos } fun getHorizNumberRelPosAt(col: Int, row: Int): List { val relPos = ArrayList(5) var c = 0 while (col + c > 0 && get(col + c - 1, row).isDigit()) c-- while (col + c < width && get(col + c, row).isDigit()) { relPos.add(RelPos(c, 0)) c++ } return relPos } 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 apply(op: (content: Char) -> Char?) { applyWithPos { grid: CharGrid, col, row -> op(grid[col, row]) } } fun grow( relposes: Iterable, predicate: (content: Char) -> Boolean, op: (orgContent: Char, oldContent: Char, col: Int, row: Int) -> Char? = { orgContent: Char, _: Char, _: Int, _: Int -> 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) } } } } return newGrid } fun collect(c: Int, r: Int, relposes: Iterable, skipBorder: Boolean = true): List = if (skipBorder) { relposes.mapNotNull { getOrNull(c + it.dc, r + it.dr) } } else { relposes.map { get(c + it.dc, r + it.dr) } } fun addBorder(borderChar: Char = bChar): CharGrid { val topBottom = CharArray(width + 2) { borderChar } return CharGrid(Array(height + 2) { if (it == 0 || it == height + 1) topBottom else CharArray(width + 2) { r -> if (r == 0 || r == width + 1) borderChar else get(it - 1, r - 1) } }, borderChar) } fun debug() { data.joinToString("\n") { it.concatToString() }.println() } } /** * Converts string to md5 hash. */ fun String.md5() = BigInteger(1, MessageDigest.getInstance("MD5").digest(toByteArray())) .toString(16) .padStart(32, '0') /** * The cleaner shorthand for printing output. */ fun Any?.println() = println(this)