advent-of-code/src/Utils.kt

171 lines
5.7 KiB
Kotlin
Raw Normal View History

2023-11-17 10:37:33 +01:00
import java.io.File
import java.math.BigInteger
import java.security.MessageDigest
fun Int.gcd(b: Int): Int = if (b == 0) this else b.gcd(this % b)
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<Int>.lcm(): Int = reduce(Int::lcm)
fun Iterable<Long>.lcm(): Long = reduce(Long::lcm)
2023-11-17 10:37:33 +01:00
/**
* Reads lines from the given input txt file.
*/
fun readInput(name: String) = File("src", "$name.txt")
.readLines()
fun listOfIntegerLists(input: List<String>): ArrayList<List<Int>> {
val output = ArrayList<List<Int>>()
var currList = ArrayList<Int>()
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<CharArray>
val bChar: Char
constructor(input: List<String>, borderChar: Char = ' ') {
bChar = borderChar
width = input[0].length
height = input.size
data = Array(height) { input[it].toCharArray() }
}
constructor(inputGrid: Array<CharArray>, 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<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
}
fun getHorizNumberRelPosAt(col: Int, row: Int): List<RelPos> {
val relPos = ArrayList<RelPos>(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<RelPos>, 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<RelPos>, skipBorder: Boolean = true): List<Char> =
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()
}
}
2023-11-17 10:37:33 +01:00
/**
* 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)