diff --git a/src/main/kotlin/com/geistindersh/aoc/helper/caching/Memoize.kt b/src/main/kotlin/com/geistindersh/aoc/helper/caching/Memoize.kt new file mode 100644 index 0000000..b9f5d8a --- /dev/null +++ b/src/main/kotlin/com/geistindersh/aoc/helper/caching/Memoize.kt @@ -0,0 +1,103 @@ +package com.geistindersh.aoc.helper.caching + +/** + * Generate a function that will keep track of the input and output of the function + * and cache the results. + * Repeated calls with the same arguments will return the same result, and [fn] should + * be free of side effects. + * + * @param fn A function to memoize + * @return A function that takes the same arguments as the memoized function + */ +@Suppress("unused") +fun memoize(fn: (A) -> Z): (A) -> Z { + val memory = mutableMapOf() + return { memory.getOrPut(it) { fn(it) } } +} + +/** + * Generate a function that will keep track of the input and output of the function + * and cache the results. + * Repeated calls with the same arguments will return the same result, and [fn] should + * be free of side effects. + * + * @param fn A function to memoize + * @param defaultValues Values to pre-populate the input-output cache with + * @return A function that takes the same arguments as the memoized function + */ +@Suppress("unused") +fun memoize( + fn: (A) -> Z, + defaultValues: Map, +): (A) -> Z { + val memory = defaultValues.toMutableMap() + return { memory.getOrPut(it) { fn(it) } } +} + +/** + * Generate a function that will keep track of the input and output of the function + * and cache the results. + * Repeated calls with the same arguments will return the same result, and [fn] should + * be free of side effects. + * + * @param fn A function to memoize + * @return A function that takes the same arguments as the memoized function + */ +@Suppress("unused") +fun memoize(fn: (A, B) -> Z): (A, B) -> Z { + val memory = mutableMapOf, Z>() + return { a, b -> memory.getOrPut(a to b) { fn(a, b) } } +} + +/** + * Generate a function that will keep track of the input and output of the function + * and cache the results. + * Repeated calls with the same arguments will return the same result, and [fn] should + * be free of side effects. + * + * @param fn A function to memoize + * @param defaultValues Values to pre-populate the input-output cache with + * @return A function that takes the same arguments as the memoized function + */ +@Suppress("unused") +fun memoize( + fn: (A, B) -> Z, + defaultValues: Map, Z>, +): (A, B) -> Z { + val memory = defaultValues.toMutableMap() + return { a, b -> memory.getOrPut(a to b) { fn(a, b) } } +} + +/** + * Generate a function that will keep track of the input and output of the function + * and cache the results. + * Repeated calls with the same arguments will return the same result, and [fn] should + * be free of side effects. + * + * @param fn A function to memoize + * @return A function that takes the same arguments as the memoized function + */ +@Suppress("unused") +fun memoize(fn: (A, B, C) -> Z): (A, B, C) -> Z { + val memory = mutableMapOf, Z>() + return { a, b, c -> memory.getOrPut(Triple(a, b, c)) { fn(a, b, c) } } +} + +/** + * Generate a function that will keep track of the input and output of the function + * and cache the results. + * Repeated calls with the same arguments will return the same result, and [fn] should + * be free of side effects. + * @param fn A function to memoize + * @param defaultValues Values to pre-populate the input-output cache with + * + * @return A function that takes the same arguments as the memoized function + */ +@Suppress("unused") +fun memoize( + fn: (A, B, C) -> Z, + defaultValues: Map, Z>, +): (A, B, C) -> Z { + val memory = defaultValues.toMutableMap() + return { a, b, c -> memory.getOrPut(Triple(a, b, c)) { fn(a, b, c) } } +} diff --git a/src/main/kotlin/com/geistindersh/aoc/year2024/Day11.kt b/src/main/kotlin/com/geistindersh/aoc/year2024/Day11.kt index ea653ab..85061bc 100644 --- a/src/main/kotlin/com/geistindersh/aoc/year2024/Day11.kt +++ b/src/main/kotlin/com/geistindersh/aoc/year2024/Day11.kt @@ -1,6 +1,7 @@ package com.geistindersh.aoc.year2024 import com.geistindersh.aoc.helper.binary.digitCount +import com.geistindersh.aoc.helper.caching.memoize import com.geistindersh.aoc.helper.files.DataFile import com.geistindersh.aoc.helper.files.fileToString import com.geistindersh.aoc.helper.report @@ -10,22 +11,22 @@ class Day11( dataFile: DataFile, ) { private val stones = fileToString(2024, 11, dataFile).split(" ").associate { it.toLong() to 1L } - private val memory = mutableMapOf(0L to listOf(1L)) + private val memoizedCore = memoize(::core, mapOf(0L to listOf(1L))) + + private fun core(value: Long): List { + val digits = value.digitCount() + return if (digits % 2 == 0) { + val div = 10.0.pow(digits / 2.0).toLong() + listOf(value.floorDiv(div), value % div) + } else { + listOf(value * 2024) + } + } private fun Map.update() = this - .flatMap { (k, v) -> - memory - .computeIfAbsent(k) { - val digits = k.digitCount() - if (digits % 2 == 0) { - val div = 10.0.pow(digits / 2.0).toLong() - listOf(k.floorDiv(div), k % div) - } else { - listOf(k * 2024) - } - }.map { it to v } - }.groupingBy { it.first } + .flatMap { (k, v) -> memoizedCore(k).map { it to v } } + .groupingBy { it.first } .fold(0L) { acc, element -> acc + element.second } private fun Map.blink(times: Int) =