|
1 | 1 | --- |
2 | 2 | year: 2025 |
3 | 3 | day: 3 |
4 | | -title: "Title" |
| 4 | +title: "Lobby" |
5 | 5 | slug: 2025/day/3 |
6 | | -# pub_date: "2026-01-01" |
7 | | -# concepts: [] |
| 6 | +pub_date: "2025-12-03" |
| 7 | +# concepts: [recursion] |
8 | 8 | --- |
9 | 9 | ## Part 1 |
10 | 10 |
|
| 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 | + |
11 | 19 | ```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))) |
13 | 24 | ``` |
14 | 25 |
|
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. |
16 | 39 |
|
17 | 40 | ```py title="2025\day03\solution.py" |
18 | 41 | ... |
| 42 | + |
| 43 | +class Solution(StrSplitSolution): |
| 44 | + def part_1(self) -> int: |
| 45 | + return sum(int(max_joltage(line)) for line in self.input) |
19 | 46 | ``` |
| 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`! |
0 commit comments