Advent of Code 2023 day 2
12/02/2025, 5:00 AMphldavies
12/02/2025, 5:20 AMJakub Gwóźdź
12/02/2025, 5:25 AMprivate fun solve(data: String, filterOp: (String) -> Boolean): Long =
data.trim().split(",").map { it.split("-").let { (a, b) -> a.toLong()..b.toLong() } }
.flatMap { it.asSequence() }
.map { it.toString() }
.filter(filterOp)
.sumOf { it.toLong() }
fun part1(data: String): Any = solve(data) { s ->
s.length % 2 == 0 && s.take(s.length / 2) == s.takeLast(s.length / 2)
}
fun part2(data: String): Any = solve(data) { s ->
(1..s.length / 2).any { c ->
s.chunked(c).let { l -> l.all { p -> p == l.first() } }
}
}Norbert Kiesel
12/02/2025, 5:26 AM^ and $ in your regex because matches anyway only returns true if it matches the whole input.Marcin Wisniowski
12/02/2025, 5:26 AMMarcin Wisniowski
12/02/2025, 5:26 AMMarcin Wisniowski
12/02/2025, 5:29 AMNorbert Kiesel
12/02/2025, 5:30 AMprivate fun parse(input: List<String>) =
input[0].split(',').map { it.split('-').map { it.toLong() }.let { it[0]..it[1] } }
fun one(input: List<String>): Long {
val rx = Regex("""(\d+)\1""")
return parse(input).flatMap { r -> r.filter { rx.matches(it.toString()) } }.sum()
}
fun two(input: List<String>): Long {
val rx = Regex("""(\d+)\1+""")
return parse(input).flatMap { r -> r.filter { rx.matches(it.toString()) } }.sum()
}y9san9
12/02/2025, 5:42 AMy9san9
12/02/2025, 5:45 AM__FILE__ property that is java.io.FileAndriy
12/02/2025, 5:45 AMy9san9
12/02/2025, 5:51 AMMichael de Kaste
12/02/2025, 5:53 AMJakub Gwóźdź
12/02/2025, 5:57 AM= catching { part, I did it only so the exception does not cause run to fail and scroll the intellij output to meaningless gradle error)Andriy
12/02/2025, 6:02 AMAnirudh
12/02/2025, 6:03 AMprivate fun isInvalidPartTwo(num: Long) = """^(\d+)\1+$""".toRegex().matches(num.toString())
used with `data: List<ListLong>`:
fun partTwo(): Long =
data.flatMap {
LongProgression.fromClosedRange(it.first(), it.last(), 1L)
.filter(::isInvalidPartTwo)
}.sum()bj0
12/02/2025, 6:09 AMranges.sumOf { r ->
r.sumOf {
val s = it.toString()
if ((1..s.length / 2).any { i ->
val p = s.take(i)
var left = s.drop(i)
while (left.isNotEmpty() && (left.take(i) == p)) {
left = left.drop(i)
}
left.isEmpty()
}) it else 0
}
}y9san9
12/02/2025, 6:11 AMbj0
12/02/2025, 6:13 AMrecursive took 139.981438ms (1.00x): 46666175279
first took 148.612785ms (1.06x): 46666175279
chunk took 412.768798ms (2.95x): 46666175279Dan Fingal-Surma
12/02/2025, 6:22 AMy9san9
12/02/2025, 6:23 AMNorbert Kiesel
12/02/2025, 6:33 AMflatMap { ... }.distinct().sum(), perhaps your input produces the same invalid number more than once.Dan Fingal-Surma
12/02/2025, 6:34 AMAnirudh
12/02/2025, 6:37 AMNorbert Kiesel
12/02/2025, 6:43 AM.distinct() is relevant. My understanding is that a 95-115,99-112 should only return 99 and not 198 because we should return the sum of all invalid numbers, and the only invalid number in both of these ranges is 99.Anirudh
12/02/2025, 6:46 AMdistinct.Endre Deak
12/02/2025, 6:47 AMfun main() {
solve("Gift Shop") {
val input = lines[0]
.split(",")
.map { it.split("-") }
.map { (f, s) -> f.toLong()..s.toLong() }
fun calc(check: Long.() -> Boolean) = input.sumOf { r -> r.filter { check(it) }.sum() }
part1(-1) {
calc { "$this".let { s -> (s.length / 2).let { s.take(it) == s.drop(it) } } }
}
part2(-1) {
calc { "$this".matches(Regex("(.+)\\1+")) }
}
}
}Norbert Kiesel
12/02/2025, 6:52 AM.distinct() also did not make a difference for my input, but "find all of the invalid IDs" is the real challenge. It's just that we then return the sum of them as the single answer.Dan Fingal-Surma
12/02/2025, 6:58 AMAnirudh
12/02/2025, 7:14 AMDan Fingal-Surma
12/02/2025, 7:33 AM2579792 numbers and I test 908 for inclusion in part 1 and 1039 in part 2, of which 865 and 979 match, respectively. O(sqrt(n)) which should be optimal time complexity.Dan Fingal-Surma
12/02/2025, 7:41 AMKarloti
12/02/2025, 7:47 AMSequence) to handle large input streams with O(1) memory usage, preventing memory overflows on large single-line files.
Part 1: The "Double Pattern" (Exact Halves)
• Logic: A number is valid if it can be split into exactly two identical halves (e.g., 1212).
• Algorithm:
a. Check if the digit count L is even.
b. Split the string at index L / 2.
c. Compare the first half substring with the second half substring.
• Time Complexity: O(L) per number, where L is the number of digits. String slicing and comparison iterate over the digits once, making it linear relative to the digit count.
Part 2: The "Multi Pattern" (Repetition N >= 2)
• Problem: Identify if the number is composed of a repeating substring of any length (e.g., 123123123).
• Naive Approach (Discarded): Iterating through all possible substring lengths and reconstructing the string results in O(L^2) complexity. This is inefficient for large inputs.
• Optimal Approach (KMP Algorithm / LPS Array):
◦ Logic: We utilize the LPS (Longest Prefix which is also Suffix) array construction from the Knuth-Morris-Pratt algorithm.
◦ Mechanism: We compute the LPS array for the digit string. Let n be the string length and lastLps be the value of the last element in the LPS array.
◦ The length of the smallest repeating unit is calculated as: unitLength = n - lastLps.
◦ Validity Check: If lastLps > 0 and n is perfectly divisible by unitLength, the string is periodic.
• Time Complexity: O(L). The LPS array construction performs a single linear pass over the digits. This reduces the problem from quadratic to linear time complexity per ID.
https://github.com/karloti/aoc-2025-in-kotlin/blob/main/src/Day02.ktephemient
12/02/2025, 8:01 AMNeil Banman
12/02/2025, 8:25 AMNeil Banman
12/02/2025, 8:27 AMJaap Beetstra
12/02/2025, 8:30 AMRegex for part 2 felt like the easy choice...
Regex never feels like the easy choice for me… But it seems helpful here.kingsley
12/02/2025, 9:02 AMMax Thiele
12/02/2025, 9:37 AMAnirudh
12/02/2025, 9:42 AMMichael de Kaste
12/02/2025, 9:47 AMKarloti
12/02/2025, 9:49 AMY25D02.kt
In my implementation (), I relied on a linear scan with a filtering predicate. I iterated through the entire range, converting every integer to a string and checking for periodicity using KMP logic (). While this works, it scales linearly with the range size (O(N)). Day02.kt isMultiPattern
Your strategy of mathematically constructing the "next valid number" (via and ) is far superior. Given the sparse distribution of numbers with repeating sub-patterns, calculating the jumps allows you to skip the vast majority of candidates. Your complexity effectively scales with the number of solutions rather than the input range. getNextInvalid2 getNextByPortion
Excellent optimization!Charles Flynn
12/02/2025, 11:28 AM123444567 wouldn't qualify as the 4s repeat in the middle before realising it had to be a repeating pattern that covered the whole string.
We're lucky Kotlin has chunked as it trivialises the puzzle 🙂Karloti
12/02/2025, 1:02 PMduration = 1.691200ms (part2Fast) Complexity O(N)
duration = 75.588700ms ( part2 )
https://github.com/karloti/aoc-2025-in-kotlin/blob/main/src/Day02.ktMichael Böiers
12/02/2025, 1:29 PMfun main() {
val ranges = readln().split(",").map { s -> s.split("-").map(String::toLong) }
var part1 = 0L
var part2 = 0L
for (range in ranges) for (id in range[0]..range[1]) {
val s = id.toString()
for (l in (1..s.length / 2).reversed()) {
val chunks = s.chunked(l)
if (chunks.distinct().size == 1) {
if (chunks.size == 2) part1 += id
part2 += id
break
}
}
}
println(part1)
println(part2)
}ephemient
12/02/2025, 2:05 PMdownTo instead of .reversed() (or let it run in increasing chunk size, it doesn't matter)Michael Böiers
12/02/2025, 2:06 PMdenis
12/02/2025, 2:48 PMphldavies
12/02/2025, 5:04 PMWarming up 2 puzzles for 5s each for year 2025 day 2...
ChunkGeneration warmed up with 73655 iterations
Regex warmed up with 15 iterations
year 2025 day 2 part 1
ChunkGeneration took 50.747us 👑: 12850231731
Regex took 136.692622ms (2693.61x): 12850231731
year 2025 day 2 part 2
ChunkGeneration took 35.1us 👑: 24774350322
Regex took 196.730319ms (5604.85x): 24774350322ephemient
12/02/2025, 9:04 PMEdgar Avuzi
12/06/2025, 6:23 AMEdgar Avuzi
12/06/2025, 10:56 PMisInvalidId with this: