Skip to content

Commit b1105df

Browse files
committed
Add 2025 Day 3
1 parent 79a8fa8 commit b1105df

File tree

3 files changed

+154
-5
lines changed

3 files changed

+154
-5
lines changed
Lines changed: 116 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,130 @@
11
---
22
year: 2025
33
day: 3
4-
title: "Title"
4+
title: "Lobby"
55
slug: 2025/day/3
6-
# pub_date: "2026-01-01"
7-
# concepts: []
6+
pub_date: "2025-12-03"
7+
# concepts: [recursion]
88
---
99
## Part 1
1010

11+
Part 1 today feels a bit easy -- almost _too_ easy.
12+
13+
[`itertools.combinations`](https://docs.python.org/3/library/itertools.html#itertools.combinations)
14+
can be used to get all possible pairs of batteries, in the same order as they
15+
are in the input. So to get the maximum "joltage" level for a bank of batteries,
16+
we _could_ just get the `max` of all the combinations of 2 batteries, then join
17+
them into a single string with `"".join(...)`.
18+
1119
```py title="2025\day03\solution.py"
12-
...
20+
from itertools import combinations
21+
22+
def max_joltage(bank: str) -> str:
23+
return "".join(max(combinations(bank, 2)))
1324
```
1425

15-
## Part 2
26+
:::tip
27+
In most scenarios where you're treating a number as a series of digits instead
28+
of as a mathematical quantity, it's better to represent it as a `str` instead of
29+
an `int`. As a rule of thumb, if it doesn't make sense to add 1 to a number or
30+
multiply it by 2, it should probably be a string.
31+
32+
In this case, I chose to make each joltage level a `str` because they're made by
33+
literally placing digits side by side.
34+
:::
35+
36+
Then we can simply `sum` over the max joltage of each line of our input --
37+
remembering to convert our joltage strings to `int`s so we can add them
38+
together.
1639

1740
```py title="2025\day03\solution.py"
1841
...
42+
43+
class Solution(StrSplitSolution):
44+
def part_1(self) -> int:
45+
return sum(int(max_joltage(line)) for line in self.input)
1946
```
47+
48+
That's a pretty quick brute-force solution. But I'm guessing we won't be able to
49+
brute-force Part 2 in the same way.
50+
51+
## Part 2
52+
53+
My suspicions were correct.
54+
55+
Sure, you _could_ just replace the `2` with a `12` for Part 2... but you won't
56+
get a result very quickly.
57+
58+
```py title="2025\day03\solution.py" ins=", batteries: int" ins=/bank(, batteries)/ ins=/, batteries=\d+/
59+
from itertools import combinations
60+
61+
def max_joltage(bank: str, batteries: int) -> str:
62+
return "".join(max(combinations(bank, batteries)))
63+
64+
class Solution(StrSplitSolution):
65+
def part_1(self) -> int:
66+
return sum(int(max_joltage(line, batteries=2)) for line in self.input)
67+
68+
def part_2(self) -> int:
69+
return sum(int(max_joltage(line, batteries=12)) for line in self.input)
70+
```
71+
72+
So let's think a little harder about what we're doing. We're trying to get a
73+
combination of some number of digits from our battery bank, to make the highest
74+
number possible. Let's think about this recursively and see if that helps.
75+
76+
The simplest case I can think of is where we're trying to get the max joltage
77+
with a single battery. Obviously, the max joltage will be whatever the highest
78+
battery value is.
79+
80+
- **Base case**: If we can turn on only 1 battery, the result will be the
81+
maximum value of any battery in the bank.
82+
83+
And in the case of even more batteries, the most important thing is that the
84+
first battery's value should be as high as possible; for example, _any_ joltage
85+
level starting with a `9` will be higher than even the highest joltage level
86+
starting with an `8` or lower.
87+
88+
So in the case of two batteries, the first battery will be the highest-valued
89+
battery anywhere in the bank -- except for the last battery slot, because
90+
nothing could go after it. In general, in the case of $n$ batteries, the first
91+
battery will be the highest-valued battery anywhere except the last $n - 1$
92+
slots. From here, we can use recursion to find the result for the remaining
93+
$n - 1$ batteries.
94+
95+
- **Recursive case**: If we can turn on $n$ batteries, the first battery's value
96+
will be the maximum value of any battery in the bank (excluding the final
97+
$n - 1$ batteries). So the result will be that first battery, concatenated with
98+
the maximum joltage level for the _remaining_ section of the bank using $n - 1$
99+
batteries.
100+
101+
We can implement this rather straightforwardly.
102+
103+
- `max(bank)` is all we need for the base case.
104+
- We can use "slicing" to retrieve only certain portions of the battery bank.
105+
`bank[:-n]` gets everything in `bank` from the start up to (but not including)
106+
the `n`th element from the end, and `bank[n:]` gets everything in `bank` from
107+
the `n`th element from the start all the way to the end. We'll need these, as
108+
well as [`str.index`](https://docs.python.org/3/library/stdtypes.html#str.index)
109+
(for getting the first digit's index), to implement the recursive case.
110+
111+
```py title="2025\day03\solution.py" del={1} ins={"Base case":4-7} ins={"Recursive case":9-15}
112+
from itertools import combinations
113+
114+
def max_joltage(bank: str, batteries: int) -> str:
115+
116+
# The max joltage with a single battery is the bank's highest digit
117+
if batteries == 1:
118+
return max(bank)
119+
120+
121+
# Find highest first digit (leaving enough space for the rest of the
122+
# batteries)
123+
first_digit = max(bank[: -(batteries - 1)])
124+
# Combine that digit with the max joltage for the rest of the bank
125+
i = bank.index(first_digit)
126+
return first_digit + max_joltage(bank[i + 1 :], batteries - 1)
127+
```
128+
129+
With this new `max_joltage` function, the solution becomes extremely fast -- not
130+
to mention, we've eliminated the need for `itertools.combinations`!

solutions/2025/day03/solution.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# https://adventofcode.com/2025/day/3
2+
3+
from ...base import StrSplitSolution, answer
4+
5+
6+
def max_joltage(bank: str, batteries: int) -> str:
7+
# No possible joltage if there aren't enough batteries for it
8+
assert len(bank) >= batteries, "no possible max joltage"
9+
# The max joltage with a single battery is the bank's highest digit
10+
if batteries == 1:
11+
return max(bank)
12+
13+
# Find highest first digit (leaving enough space for the rest of the
14+
# batteries)
15+
first_digit = max(bank[: -(batteries - 1)])
16+
# Combine that digit with the max joltage for the rest of the bank
17+
i = bank.index(first_digit)
18+
return first_digit + max_joltage(bank[i + 1 :], batteries - 1)
19+
20+
21+
class Solution(StrSplitSolution):
22+
"""
23+
Solution for Advent of Code 2025 Day 3.
24+
"""
25+
_year = 2025
26+
_day = 3
27+
28+
@answer(17092)
29+
def part_1(self) -> int:
30+
return sum(int(max_joltage(line, batteries=2)) for line in self.input)
31+
32+
@answer(170147128753455)
33+
def part_2(self) -> int:
34+
return sum(int(max_joltage(line, batteries=12)) for line in self.input)

solutions/2025/day03/test.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
987654321111111
2+
811111111111119
3+
234234234234278
4+
818181911112111

0 commit comments

Comments
 (0)