|
| 1 | +Advent of Code 2023 walkthrough |
| 2 | +=============================== |
| 3 | + |
| 4 | +**Note**: in the hope of speeding up the process of writing walkthroughs each |
| 5 | +day, this year I am *not* going to give a brief summary of the "part 1" problem |
| 6 | +statement at the beginning of each day. Instead, I will jump right at the |
| 7 | +solution. The official problem statements are linked throughout the document for |
| 8 | +reference. |
| 9 | + |
| 10 | +Table of Contents |
| 11 | +----------------- |
| 12 | + |
| 13 | +- [Day 1 - Trebuchet?!][d01] |
| 14 | +<!-- |
| 15 | +- [Day 2 - ][d02] |
| 16 | +- [Day 3 - ][d03] |
| 17 | +- [Day 4 - ][d04] |
| 18 | +- [Day 5 - ][d05] |
| 19 | +- [Day 6 - ][d06] |
| 20 | +- [Day 7 - ][d07] |
| 21 | +- [Day 8 - ][d08] |
| 22 | +- [Day 9 - ][d09] |
| 23 | +- [Day 10 - ][d10] |
| 24 | +- [Day 11 - ][d11] |
| 25 | +- [Day 12 - ][d12] |
| 26 | +- [Day 13 - ][d13] |
| 27 | +- [Day 14 - ][d14] |
| 28 | +- [Day 15 - ][d15] |
| 29 | +- [Day 16 - ][d16] |
| 30 | +- [Day 17 - ][d17] |
| 31 | +- [Day 18 - ][d18] |
| 32 | +- [Day 19 - ][d19] |
| 33 | +- [Day 20 - ][d20] |
| 34 | +- [Day 21 - ][d21] |
| 35 | +- [Day 22 - ][d22] |
| 36 | +- [Day 23 - ][d23] |
| 37 | +- [Day 24 - ][d24] |
| 38 | +- [Day 25 - ][d25] |
| 39 | +--> |
| 40 | + |
| 41 | + |
| 42 | +Day 1 - Trebuchet?! |
| 43 | +------------------- |
| 44 | + |
| 45 | +[Problem statement][d01-problem] — [Complete solution][d01-solution] — [Back to top][top] |
| 46 | + |
| 47 | +### Part 1 |
| 48 | + |
| 49 | +Task seems easy enough. How do you find out if a character is a digit? Simply |
| 50 | +check [`char.isdigit()`][py-str-isdigit]. We can do this for each character of |
| 51 | +each line of input, first iterating forward to find the first, and then |
| 52 | +iterating backwards (using `[::-1]`) to find the last. The digits we find will |
| 53 | +need to be converted to `int`, and the first one will need to also be multiplied |
| 54 | +by `10`. |
| 55 | + |
| 56 | +```python |
| 57 | +fin = open(...) |
| 58 | +total = 0 |
| 59 | + |
| 60 | +for line in fin: |
| 61 | + for char in line: |
| 62 | + if char.isdigit(): |
| 63 | + total += 10 * int(char) |
| 64 | + break |
| 65 | + |
| 66 | + for char in line[::-1]: |
| 67 | + if char.isdigit(): |
| 68 | + total += int(char) |
| 69 | + break |
| 70 | +``` |
| 71 | + |
| 72 | +We can simplify this with the help of the [`filter()`][py-builtin-filter] |
| 73 | +built-in function: just filter out any character that satisfies `str.isdigit()`. |
| 74 | +To only extrac the first such character from the iterator returned by `filter()` |
| 75 | +we can simply use [`next()`][py-builtin-next]. |
| 76 | + |
| 77 | +```python |
| 78 | +for line in fin: |
| 79 | + digit1 = next(filter(str.isdigit, line)) |
| 80 | + digit2 = next(filter(str.isdigit, line[::-1])) |
| 81 | + total += 10 * int(digit1) + int(digit2) |
| 82 | + |
| 83 | +print('Part 1:', total) |
| 84 | +``` |
| 85 | + |
| 86 | +### Part 2 |
| 87 | + |
| 88 | +Things get more complex and this is probably the "hardest" day 1 problem I have |
| 89 | +seen so far. We need to also consider English *words* when checking each line of |
| 90 | +input. The first and last digits to appear either as a digit or as an english |
| 91 | +word need to be found. |
| 92 | + |
| 93 | +There isn't much to do except checking each spelled out English digit for each |
| 94 | +line. We can simplify things by building a `dict` to use as a lookup table: |
| 95 | + |
| 96 | +```python |
| 97 | +DIGITS = { |
| 98 | + 'zero' : 0, |
| 99 | + 'one' : 1, |
| 100 | + 'two' : 2, |
| 101 | + 'three': 3, |
| 102 | + 'four' : 4, |
| 103 | + 'five' : 5, |
| 104 | + 'six' : 6, |
| 105 | + 'seven': 7, |
| 106 | + 'eight': 8, |
| 107 | + 'nine' : 9, |
| 108 | +} |
| 109 | +``` |
| 110 | + |
| 111 | +Now the check is a bit more annoying, so let's create a function for it: it will |
| 112 | +take a string and will check whether the first character is a digit (and in that |
| 113 | +case return it) or whether the string starts with a spelled-out English digit |
| 114 | +(and in that case convert and return it). We'll return `0` in case of no match |
| 115 | +for simplicity. |
| 116 | + |
| 117 | +```python |
| 118 | +def check_digit(string): |
| 119 | + if string[0].isdigit(): |
| 120 | + return int(string[0]) |
| 121 | + |
| 122 | + for d in DIGITS: |
| 123 | + if string.startswith(d): |
| 124 | + return DIGITS[d] |
| 125 | + |
| 126 | + return 0 |
| 127 | +``` |
| 128 | + |
| 129 | +The second loop above can again be simplified with the use of `filter()` + |
| 130 | +`next()`, but since this time we are not guaranteed to find an English word in |
| 131 | +`string`, we need to pass a second argument to `next()` for the default value to |
| 132 | +return in case `filter()` does not match anything. |
| 133 | + |
| 134 | +```python |
| 135 | +def check_digit(char, string): |
| 136 | + if string[0].isdigit(): |
| 137 | + return int(string[0]) |
| 138 | + |
| 139 | + d = next(filter(string.startswith, DIGITS), None) |
| 140 | + return DIGITS.get(d, 0) |
| 141 | +``` |
| 142 | + |
| 143 | +We can now integrate the above function in the loop we wrote for part 1, using a |
| 144 | +second variable for the total. This time, we'll have to iterate over each |
| 145 | +possible substring manually, first forward and then backwards. We can easily use |
| 146 | +[`range()`][py-builtin-range] for that. |
| 147 | + |
| 148 | +```python |
| 149 | +total1 = total2 = 0 |
| 150 | + |
| 151 | +for line in fin: |
| 152 | + # Part 1 |
| 153 | + total1 += 10 * int(next(filter(str.isdigit, line))) |
| 154 | + total1 += int(next(filter(str.isdigit, line[::-1]))) |
| 155 | + |
| 156 | + # Part 2 |
| 157 | + for i in range(len(line)): |
| 158 | + digit1 = check_digit(char, line[i:]) |
| 159 | + if digit1: |
| 160 | + break |
| 161 | + |
| 162 | + for i in range(len(line) - 1, -1, -1): |
| 163 | + digit2 = check_digit(line[i], line[i:]) |
| 164 | + if digit2: |
| 165 | + break |
| 166 | + |
| 167 | + total2 += 10 * digit1 + digit2 |
| 168 | + |
| 169 | +print('Part 1:', total1) |
| 170 | +print('Part 2:', total2) |
| 171 | +``` |
| 172 | + |
| 173 | +There is technically a way to "simplify" this even more, again with the use of |
| 174 | +`filter()` + `next()`, but it does not really help anything. However, here it |
| 175 | +is, just for the fun of it: |
| 176 | + |
| 177 | +```python |
| 178 | +for line in fin: |
| 179 | + total2 += 10 * next(filter(None, map(check_digit, (line[i:] for i in range(len(line)))))) |
| 180 | + total2 += next(filter(None, map(check_digit, (line[i:] for i in range(len(line) -1, -1, -1))))) |
| 181 | +``` |
| 182 | + |
| 183 | +First two starts of the year done. Welcome to Advent of Code 2024! |
| 184 | + |
| 185 | +--- |
| 186 | + |
| 187 | +*Copyright © 2023 Marco Bonelli. This document is licensed under the [Creative Commons BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) license.* |
| 188 | + |
| 189 | + |
| 190 | + |
| 191 | +[top]: #advent-of-code-2023-walkthrough |
| 192 | +[d01]: #day-1---trebuchet |
| 193 | +[d02]: #day-2--- |
| 194 | +[d03]: #day-3--- |
| 195 | +[d04]: #day-4--- |
| 196 | +[d05]: #day-5--- |
| 197 | +[d06]: #day-6--- |
| 198 | +[d07]: #day-7--- |
| 199 | +[d08]: #day-8--- |
| 200 | +[d09]: #day-9--- |
| 201 | +[d10]: #day-10--- |
| 202 | +[d11]: #day-11--- |
| 203 | +[d12]: #day-12--- |
| 204 | +[d13]: #day-13--- |
| 205 | +[d14]: #day-14--- |
| 206 | +[d15]: #day-15--- |
| 207 | +[d16]: #day-16--- |
| 208 | +[d18]: #day-18--- |
| 209 | +[d19]: #day-19--- |
| 210 | +[d20]: #day-20--- |
| 211 | +[d21]: #day-21--- |
| 212 | +[d22]: #day-22--- |
| 213 | +[d24]: #day-24--- |
| 214 | +[d25]: #day-25--- |
| 215 | + |
| 216 | +[d01-problem]: https://adventofcode.com/2023/day/1 |
| 217 | +[d02-problem]: https://adventofcode.com/2023/day/2 |
| 218 | +[d03-problem]: https://adventofcode.com/2023/day/3 |
| 219 | +[d04-problem]: https://adventofcode.com/2023/day/4 |
| 220 | +[d05-problem]: https://adventofcode.com/2023/day/5 |
| 221 | +[d06-problem]: https://adventofcode.com/2023/day/6 |
| 222 | +[d07-problem]: https://adventofcode.com/2023/day/7 |
| 223 | +[d08-problem]: https://adventofcode.com/2023/day/8 |
| 224 | +[d09-problem]: https://adventofcode.com/2023/day/9 |
| 225 | +[d10-problem]: https://adventofcode.com/2023/day/10 |
| 226 | +[d11-problem]: https://adventofcode.com/2023/day/11 |
| 227 | +[d12-problem]: https://adventofcode.com/2023/day/12 |
| 228 | +[d13-problem]: https://adventofcode.com/2023/day/13 |
| 229 | +[d14-problem]: https://adventofcode.com/2023/day/14 |
| 230 | +[d15-problem]: https://adventofcode.com/2023/day/15 |
| 231 | +[d16-problem]: https://adventofcode.com/2023/day/16 |
| 232 | +[d18-problem]: https://adventofcode.com/2023/day/18 |
| 233 | +[d19-problem]: https://adventofcode.com/2023/day/19 |
| 234 | +[d20-problem]: https://adventofcode.com/2023/day/20 |
| 235 | +[d21-problem]: https://adventofcode.com/2023/day/21 |
| 236 | +[d22-problem]: https://adventofcode.com/2023/day/22 |
| 237 | +[d24-problem]: https://adventofcode.com/2023/day/24 |
| 238 | +[d25-problem]: https://adventofcode.com/2023/day/25 |
| 239 | + |
| 240 | +[d01-solution]: solutions/day01.py |
| 241 | +[d02-solution]: solutions/day02.py |
| 242 | +[d03-solution]: solutions/day03.py |
| 243 | +[d04-solution]: solutions/day04.py |
| 244 | +[d05-solution]: solutions/day05.py |
| 245 | +[d06-solution]: solutions/day06.py |
| 246 | +[d07-solution]: solutions/day07.py |
| 247 | +[d08-solution]: solutions/day08.py |
| 248 | +[d09-solution]: solutions/day09.py |
| 249 | +[d10-solution]: solutions/day10.py |
| 250 | +[d11-solution]: solutions/day11.py |
| 251 | +[d12-solution]: solutions/day12.py |
| 252 | +[d13-solution]: solutions/day13.py |
| 253 | +[d14-solution]: solutions/day14.py |
| 254 | +[d15-solution]: solutions/day15.py |
| 255 | +[d16-solution]: solutions/day16.py |
| 256 | +[d18-solution]: solutions/day18.py |
| 257 | +[d19-solution]: solutions/day19.py |
| 258 | +[d20-solution]: solutions/day20.py |
| 259 | +[d21-solution]: solutions/day21.py |
| 260 | +[d22-solution]: solutions/day22.py |
| 261 | +[d24-solution]: solutions/day24.py |
| 262 | +[d25-solution]: solutions/day25.py |
| 263 | + |
| 264 | +[py-builtin-filter]: https://docs.python.org/3/library/functions.html#filter |
| 265 | +[py-builtin-next]: https://docs.python.org/3/library/functions.html#next |
| 266 | +[py-builtin-range]: https://docs.python.org/3/library/functions.html#range |
| 267 | +[py-str-isdigit]: https://docs.python.org/3/library/stdtypes.html#str.isdigic |
0 commit comments