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) =