From c177890fe25dd9a59ac103eb66990110cfe8cac9 Mon Sep 17 00:00:00 2001 From: Alexandru Nedelcu Date: Wed, 3 Dec 2025 15:54:55 +0200 Subject: [PATCH 1/6] Document puzzle day03 with Scala solutions Added detailed explanations and Scala solutions for both parts of the puzzle, including input examples and algorithm descriptions. --- docs/2025/puzzles/day03.md | 113 +++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/docs/2025/puzzles/day03.md b/docs/2025/puzzles/day03.md index a2c0fd1dae..9f34a5d730 100644 --- a/docs/2025/puzzles/day03.md +++ b/docs/2025/puzzles/day03.md @@ -6,6 +6,119 @@ import Solver from "../../../../../website/src/components/Solver.js" https://adventofcode.com/2025/day/3 +Input is made of lines with digits, like: + +``` +987654321111111 +811111111111119 +234234234234278 +818181911112111 +``` + +## Part 1 + +Part 1 is about extracting, for each line, the largest 2-digit number made from individual digits picked from those lines, but note that ordering is important: + +- **98**7654321111111: `98` +- **8**1111111111111**9**: `89` +- 2342342342342**78**: `78` +- 818181**9**1111**2**111: `92` + +Given a line of text, to get that maximum, we can do a loop inside another loop, an algorithm with an `O(n^2)` complexity per line processed. With a bit of parsing of the original input, the solution for part 1 becomes: + +```scala +def part1(input: String): Long = { + input + // Split input into lines (separated by newline) + .split("\\s*\\n\\s*") + .view + // Trim and filter out non-empty strings (just a precaution) + .map(_.trim) + .filter(_.nonEmpty) + // Foreach line, calculate its maximum + .map { line => + // Loop in loop for getting that 2-char max + (0 until line.length - 1) + .view + .map { i => + // Second loop picking the second char (loop-in-loop) + (i + 1 until line.length) + // concatenate first char with second char + .map(j => s"${line.charAt(i)}${line.charAt(j)}") + // convert to number + .map(_.toLong) + .max + } + .max + } + // Sum them all + .sum +} +``` + +## Part 2 + +Part 2 complicates the problem above by asking for a 12-digits numbers (instead of 2-digit numbers). + +```scala +def max(line: String, remaining: Int): String = { + // We select the index of the biggest digit from our `line`. + // Crucially important is that we don't go over + // `line.length - remaining + 1`, because we need to have chars left + // for building the rest of our number. + val maxIdx = (0 until line.length - remaining + 1) + .maxBy(i => line.charAt(i)) + // Given the rest of the string (the suffix after our found `maxIdx`) + // we build the rest by doing a recursive call + val rest = + if remaining > 1 then + // recursive call, unsafe! + max(line.substring(maxIdx + 1), remaining - 1) + else + "" + // We concatenate the found char (at `maxIdx`) with the `rest` + s"${line.charAt(maxIdx)}$rest" +} +``` + +To give an example of how this would work in practice, for a line like "23**4**2**34234234278**", we have these steps executed via recursive calls, which will produce `434234234278`: + +```scala +"4" + max(line0.substring(3), 11) +"3" + max(line1.substring(2), 10) +"4" + max(line2.substring(1), 9) +"2" + max(line3.substring(1), 8) +"3" + max(line4.substring(1), 7) +"4" + max(line5.substring(1), 6) +"2" + max(line6.substring(1), 5) +"3" + max(line7.substring(1), 4) +"4" + max(line8.substring(1), 3) +"2" + max(line9.substring(1), 2) +"7" + max(line10.substring(1), 1) +"8" + "" +``` + +We can now describe a function that works for both parts 1 and 2: + +```scala +def process(charsCount: Int)(input: String): Long = + // Splits input by newlines + input.split("\\s*\\n\\s*") + .view + // Trim and filter out empty lines + .map(_.trim) + .filter(_.nonEmpty) + // For each line, calculate its max + .map { line => + max(line, charsCount).toLong + } + // sum them all up + .sum + +def part1 = process(2) +def part2 = process(12) +``` + ## Solutions from the community Share your solution to the Scala community by editing this page. From 6860f835c5dbf77c731d964d03dc72eab4430d79 Mon Sep 17 00:00:00 2001 From: Alexandru Nedelcu Date: Wed, 3 Dec 2025 16:04:50 +0200 Subject: [PATCH 2/6] Refine explanations --- docs/2025/puzzles/day03.md | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/docs/2025/puzzles/day03.md b/docs/2025/puzzles/day03.md index 9f34a5d730..d13c8d090b 100644 --- a/docs/2025/puzzles/day03.md +++ b/docs/2025/puzzles/day03.md @@ -17,14 +17,14 @@ Input is made of lines with digits, like: ## Part 1 -Part 1 is about extracting, for each line, the largest 2-digit number made from individual digits picked from those lines, but note that ordering is important: +Part 1 is about extracting, for each line, the largest 2-digit number made from individual digits picked from that line, while maintaining their original order: - **98**7654321111111: `98` - **8**1111111111111**9**: `89` - 2342342342342**78**: `78` - 818181**9**1111**2**111: `92` -Given a line of text, to get that maximum, we can do a loop inside another loop, an algorithm with an `O(n^2)` complexity per line processed. With a bit of parsing of the original input, the solution for part 1 becomes: +To find the maximum 2-digit number for each line, we use a nested loop approach with `O(n²)` time complexity per line. After parsing the input, the solution for part 1 becomes: ```scala def part1(input: String): Long = { @@ -43,9 +43,7 @@ def part1(input: String): Long = { .map { i => // Second loop picking the second char (loop-in-loop) (i + 1 until line.length) - // concatenate first char with second char .map(j => s"${line.charAt(i)}${line.charAt(j)}") - // convert to number .map(_.toLong) .max } @@ -58,18 +56,17 @@ def part1(input: String): Long = { ## Part 2 -Part 2 complicates the problem above by asking for a 12-digits numbers (instead of 2-digit numbers). +Part 2 extends the problem by asking for 12-digit numbers (instead of 2-digit numbers). ```scala def max(line: String, remaining: Int): String = { - // We select the index of the biggest digit from our `line`. - // Crucially important is that we don't go over - // `line.length - remaining + 1`, because we need to have chars left - // for building the rest of our number. + // Select the index of the largest digit from our `line`. + // We must not search beyond `line.length - remaining + 1` + // to ensure we have enough characters left for the remaining digits. val maxIdx = (0 until line.length - remaining + 1) .maxBy(i => line.charAt(i)) - // Given the rest of the string (the suffix after our found `maxIdx`) - // we build the rest by doing a recursive call + // Build the remaining digits by recursively processing + // the substring after our selected character val rest = if remaining > 1 then // recursive call, unsafe! @@ -98,7 +95,7 @@ To give an example of how this would work in practice, for a line like "23**4**2 "8" + "" ``` -We can now describe a function that works for both parts 1 and 2: +We can now create a unified function that handles both parts by parameterizing the number of digits: ```scala def process(charsCount: Int)(input: String): Long = From fb3b3e870d38577502b67a43ee76e90ba64495a8 Mon Sep 17 00:00:00 2001 From: Alexandru Nedelcu Date: Wed, 3 Dec 2025 16:07:23 +0200 Subject: [PATCH 3/6] Re-adds some comments --- docs/2025/puzzles/day03.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/2025/puzzles/day03.md b/docs/2025/puzzles/day03.md index d13c8d090b..ae5fd57a37 100644 --- a/docs/2025/puzzles/day03.md +++ b/docs/2025/puzzles/day03.md @@ -43,8 +43,9 @@ def part1(input: String): Long = { .map { i => // Second loop picking the second char (loop-in-loop) (i + 1 until line.length) - .map(j => s"${line.charAt(i)}${line.charAt(j)}") - .map(_.toLong) + // Concatenate the two chars + .map(j => s"${line.charAt(i)}${line.charAt(j)}".toLong) + // Calculate the maximum .max } .max From a956d68ba23afb247a43026ccde655540e9df1f4 Mon Sep 17 00:00:00 2001 From: Alexandru Nedelcu Date: Wed, 3 Dec 2025 16:09:02 +0200 Subject: [PATCH 4/6] Update docs/2025/puzzles/day03.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/2025/puzzles/day03.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/2025/puzzles/day03.md b/docs/2025/puzzles/day03.md index ae5fd57a37..cd9ad60d6d 100644 --- a/docs/2025/puzzles/day03.md +++ b/docs/2025/puzzles/day03.md @@ -113,8 +113,8 @@ def process(charsCount: Int)(input: String): Long = // sum them all up .sum -def part1 = process(2) -def part2 = process(12) +def part1(input: String): Long = process(2)(input) +def part2(input: String): Long = process(12)(input) ``` ## Solutions from the community From 242fb6667de36a4495ab55c7b6f12f542bc16dc2 Mon Sep 17 00:00:00 2001 From: Alexandru Nedelcu Date: Wed, 3 Dec 2025 16:09:10 +0200 Subject: [PATCH 5/6] Update docs/2025/puzzles/day03.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/2025/puzzles/day03.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/2025/puzzles/day03.md b/docs/2025/puzzles/day03.md index cd9ad60d6d..83507bf8b7 100644 --- a/docs/2025/puzzles/day03.md +++ b/docs/2025/puzzles/day03.md @@ -35,7 +35,7 @@ def part1(input: String): Long = { // Trim and filter out non-empty strings (just a precaution) .map(_.trim) .filter(_.nonEmpty) - // Foreach line, calculate its maximum + // For each line, calculate its maximum .map { line => // Loop in loop for getting that 2-char max (0 until line.length - 1) From 11a175e3a686944db2c06b6b9c265c2b25930284 Mon Sep 17 00:00:00 2001 From: Alexandru Nedelcu Date: Wed, 3 Dec 2025 16:10:48 +0200 Subject: [PATCH 6/6] Add author --- docs/2025/puzzles/day03.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/2025/puzzles/day03.md b/docs/2025/puzzles/day03.md index 83507bf8b7..13bb1f868b 100644 --- a/docs/2025/puzzles/day03.md +++ b/docs/2025/puzzles/day03.md @@ -2,6 +2,8 @@ import Solver from "../../../../../website/src/components/Solver.js" # Day 3: Lobby +by [@alexandru](https://github.com/alexandru) + ## Puzzle description https://adventofcode.com/2025/day/3