import java.io.File import java.math.BigInteger import java.security.MessageDigest tailrec fun Int.gcd(b: Int): Int = if (b == 0) this else b.gcd(this % b) tailrec fun Long.gcd(b: Long): Long = if (b == 0L) this else b.gcd(this % b) fun Int.lcm(b: Int): Int = this / gcd(b) * b fun Long.lcm(b: Long): Long = this / gcd(b) * b fun Iterable.lcm(): Int = reduce(Int::lcm) fun Iterable.lcm(): Long = reduce(Long::lcm) /** * Reads lines from the given input txt file. */ fun readInput(name: String) = File("src", "$name.txt") .readLines() fun String.splitInts(): List = split(" ").filter(String::isNotBlank).map(String::toInt) fun String.splitLongs(): List = split(" ").filter(String::isNotBlank).map(String::toLong) 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) fun translate(rp: RelPos) = RelPos(rp.dc + dc, rp.dr + 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 collectRelative(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 matchRelative(c: Int, r: Int, relposes: Iterable, predicate: (char: Char) -> Boolean): List = relposes.filter { predicate(get(c + it.dc, r + it.dr)) } fun findMatches(predicate: (char: Char) -> Boolean): List> { val matches = ArrayList>() 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 { 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() } override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as CharGrid if (width != other.width) return false if (height != other.height) return false if (!data.contentDeepEquals(other.data)) return false return true } override fun hashCode(): Int { var result = width result = 31 * result + height result = 31 * result + data.contentDeepHashCode() return result } } /** * 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)