diff options
Diffstat (limited to '2025')
| -rwxr-xr-x | 2025/01/puzzle-1.awk | 19 | ||||
| -rwxr-xr-x | 2025/01/puzzle-2.awk | 23 | ||||
| -rw-r--r-- | 2025/02/.gitignore | 1 | ||||
| -rw-r--r-- | 2025/02/Makefile | 1 | ||||
| -rw-r--r-- | 2025/02/puzzles.sh | 12 | ||||
| -rw-r--r-- | 2025/03/.gitignore | 1 | ||||
| -rw-r--r-- | 2025/03/Makefile | 1 | ||||
| -rw-r--r-- | 2025/03/puzzles.c | 48 | ||||
| -rw-r--r-- | 2025/04/.gitignore | 1 | ||||
| -rw-r--r-- | 2025/04/Makefile | 1 | ||||
| -rw-r--r-- | 2025/04/puzzles.py | 95 | ||||
| -rwxr-xr-x | 2025/05/puzzle-1.awk | 19 | ||||
| -rwxr-xr-x | 2025/05/puzzle-2.awk | 31 | ||||
| -rwxr-xr-x | 2025/06/puzzle-1.awk | 20 | ||||
| -rwxr-xr-x | 2025/06/puzzle-2.sh | 27 | ||||
| -rwxr-xr-x | 2025/07/puzzle-1.py | 23 | ||||
| -rwxr-xr-x | 2025/07/puzzle-2.py | 31 | ||||
| -rw-r--r-- | 2025/08/.gitignore | 1 | ||||
| -rw-r--r-- | 2025/08/Makefile | 1 | ||||
| -rw-r--r-- | 2025/08/puzzles.py | 53 | ||||
| -rwxr-xr-x | 2025/09/puzzle-1.py | 29 | ||||
| -rwxr-xr-x | 2025/09/puzzle-2.py | 49 | ||||
| -rwxr-xr-x | 2025/10/puzzle-1.py | 49 | ||||
| -rwxr-xr-x | 2025/11/puzzle-1.awk | 19 | ||||
| -rwxr-xr-x | 2025/11/puzzle-2.py | 37 |
25 files changed, 592 insertions, 0 deletions
diff --git a/2025/01/puzzle-1.awk b/2025/01/puzzle-1.awk new file mode 100755 index 0000000..4ed1d7f --- /dev/null +++ b/2025/01/puzzle-1.awk @@ -0,0 +1,19 @@ +#!/usr/bin/awk -f + +function mod(n, m) +{ + return ((n % m) + m) % m +} + +BEGIN { dial = 50 } + +/^L/ { sign = -1 } +/^R/ { sign = +1 } + +{ + n = substr($0, 2) + if ((dial = mod(dial + n*sign, 100)) == 0) + acc++ +} + +END { print acc } diff --git a/2025/01/puzzle-2.awk b/2025/01/puzzle-2.awk new file mode 100755 index 0000000..b0516bf --- /dev/null +++ b/2025/01/puzzle-2.awk @@ -0,0 +1,23 @@ +#!/usr/bin/awk -f + +function mod(n, m) +{ + return ((n % m) + m) % m +} + +BEGIN { dial = 50 } + +{ n = substr($0, 2) } + +/^L/ { + sign = -1 + acc += int(n / 100) + (n%100 >= dial && dial != 0) +} +/^R/ { + sign = +1 + acc += int((dial + n) / 100) +} + +{ dial = mod(dial + n*sign, 100) } + +END { print acc } diff --git a/2025/02/.gitignore b/2025/02/.gitignore new file mode 100644 index 0000000..62d9f48 --- /dev/null +++ b/2025/02/.gitignore @@ -0,0 +1 @@ +puzzle-[12].sh diff --git a/2025/02/Makefile b/2025/02/Makefile new file mode 100644 index 0000000..c1620aa --- /dev/null +++ b/2025/02/Makefile @@ -0,0 +1 @@ +include ../../Makefiles/sh.mk diff --git a/2025/02/puzzles.sh b/2025/02/puzzles.sh new file mode 100644 index 0000000..0ad12cc --- /dev/null +++ b/2025/02/puzzles.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +# START PART 1 +readonly REGEXP='^([0-9]+)\1$' +# END PART 1 START PART 2 +readonly REGEXP='^([0-9]+)\1+$' +# END PART 2 + +tr ',' '\n' <input | while IFS=- read -r x y +do + seq -f %1.0f $x $y +done | grep -E "$REGEXP" | awk '{ n += $1 } END { print n }' diff --git a/2025/03/.gitignore b/2025/03/.gitignore new file mode 100644 index 0000000..60d075d --- /dev/null +++ b/2025/03/.gitignore @@ -0,0 +1 @@ +puzzle-[12] diff --git a/2025/03/Makefile b/2025/03/Makefile new file mode 100644 index 0000000..de3f940 --- /dev/null +++ b/2025/03/Makefile @@ -0,0 +1 @@ +include ../../Makefiles/c.mk diff --git a/2025/03/puzzles.c b/2025/03/puzzles.c new file mode 100644 index 0000000..9bd68ee --- /dev/null +++ b/2025/03/puzzles.c @@ -0,0 +1,48 @@ +#include <err.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifdef PART1 + #define DIGITS ((size_t)(2)) +#else + #define DIGITS ((size_t)(12)) +#endif + +int +main(void) +{ + FILE *fp = fopen("input", "r"); + if (fp == nullptr) + err(EXIT_FAILURE, "failed to open input"); + + char *buf = nullptr; + size_t acc, bufsz; + ssize_t n; + + acc = bufsz = 0; + + while ((n = getline(&buf, &bufsz, fp)) != -1) { + if (buf[n - 1] == '\n') + buf[--n] = 0; + + char digits[DIGITS + 1]; + memcpy(digits, buf, DIGITS); + digits[DIGITS] = 0; + + for (size_t i = 1; i < n - DIGITS + 1; i++) { + for (size_t j = 0; j < DIGITS; j++) { + if (buf[i + j] > digits[j]) + memcpy(digits + j, buf + i + j, DIGITS - j); + } + } + + acc += (size_t)strtol(digits, nullptr, 10); + } + if (ferror(fp)) + err(EXIT_FAILURE, "failed to read record"); + + fclose(fp); + printf("%zu\n", acc); + return EXIT_SUCCESS; +} diff --git a/2025/04/.gitignore b/2025/04/.gitignore new file mode 100644 index 0000000..ffc46fe --- /dev/null +++ b/2025/04/.gitignore @@ -0,0 +1 @@ +puzzle-[12].py diff --git a/2025/04/Makefile b/2025/04/Makefile new file mode 100644 index 0000000..0a4e980 --- /dev/null +++ b/2025/04/Makefile @@ -0,0 +1 @@ +include ../../Makefiles/py.mk diff --git a/2025/04/puzzles.py b/2025/04/puzzles.py new file mode 100644 index 0000000..3558269 --- /dev/null +++ b/2025/04/puzzles.py @@ -0,0 +1,95 @@ +#!/usr/bin/python3 + +import itertools +from typing import Generator, Iterator, Self, TextIO + + +class Grid: + @classmethod + def from_file(cls, f: TextIO) -> Self: + g = Grid() + g._grid = mkmap( + [[c for c in l if c != '\n'] for l in f.readlines()]) + return g + + def in_bounds_p(self, z: complex) -> bool: + return ( + 0 <= z.real < len(self._grid[0]) + and 0 <= z.imag < len(self._grid) + ) + + def accessablep(self, z: complex) -> bool: + return self[z] is not None and self[z] < 4 + + def indicies(self) -> Iterator[complex]: + return itertools.starmap(complex, itertools.product( + range(len(self._grid[0])), + range(len(self._grid)), + )) + + def neighbors(self, z: complex) -> Generator[complex, None, None]: + for Δ in ( + -1-1j, -1, -1+1j, + 0-1j, 0+1j, + +1-1j, +1, +1+1j, + ): + if self.in_bounds_p(z + Δ): + yield z + Δ + + def __getitem__(self, z: complex) -> int | None: + return self._grid[int(z.real)][int(z.imag)] + + def __setitem__(self, z: complex, n: int | None) -> None: + self._grid[int(z.real)][int(z.imag)] = n + + +def mkmap(xs: list[list[str]]) -> list[list[int]]: + def get(pos: complex) -> str: + r, i = map(int, (pos.real, pos.imag)) + if 0 <= r < len(xs[0]) and 0 <= i < len(xs): + return xs[r][i] + return '.' + + return [ + [ + sum(get(complex(i, j) + Δ) == '@' for Δ in [ + -1-1j, -1, -1+1j, + 0-1j, 0+1j, + +1-1j, +1, +1+1j, + ]) + if get(complex(i, j)) == '@' + else None + for j in range(len(xs[i])) + ] + for i in range(len(xs)) + ] + + +def main() -> None: + with open('input', 'r') as f: + grid = Grid.from_file(f) + + if PUZZLE_PART == 1: + acc = sum(grid.accessablep(z) for z in grid.indicies()) + else: + acc = 0 + while True: + breakp = True + for z in grid.indicies(): + if not grid.accessablep(z): + continue + breakp = False + acc += 1 + grid[z] = None + for p in grid.neighbors(z): + if grid[p] is not None: + grid[p] -= 1 + + if breakp: + break + + print(acc) + + +if __name__ == '__main__': + main() diff --git a/2025/05/puzzle-1.awk b/2025/05/puzzle-1.awk new file mode 100755 index 0000000..acfe21b --- /dev/null +++ b/2025/05/puzzle-1.awk @@ -0,0 +1,19 @@ +#!/usr/bin/awk -f + +BEGIN { FS = "-" } + +NF == 2 { + xs[NR, 1] = $1 + xs[NR, 2] = $2 +} + +NF == 1 { + for (i = 1; i <= length(xs) / 2; i++) { + if ($1 >= xs[i, 1] && $1 <= xs[i, 2]) { + cnt++ + break + } + } +} + +END { print cnt } diff --git a/2025/05/puzzle-2.awk b/2025/05/puzzle-2.awk new file mode 100755 index 0000000..3dfc661 --- /dev/null +++ b/2025/05/puzzle-2.awk @@ -0,0 +1,31 @@ +#!/usr/bin/gawk -f + +function rangecmp(i1, v1, i2, v2) +{ + return v1[1] - v2[1] +} + +function max(x, y) +{ + return x > y ? x : y +} + +BEGIN { FS = "-" } + +NF == 2 { + xs[NR][1] = $1 + xs[NR][2] = $2 +} + +END { + asort(xs, xs, "rangecmp") + for (i in xs) { + if (xs[i][2] <= m) + continue + cnt += xs[i][2] - max(xs[i][1], m) + if (xs[i][1] > m) + cnt++ + m = xs[i][2] + } + print cnt +} diff --git a/2025/06/puzzle-1.awk b/2025/06/puzzle-1.awk new file mode 100755 index 0000000..4bb4338 --- /dev/null +++ b/2025/06/puzzle-1.awk @@ -0,0 +1,20 @@ +#!/usr/bin/awk -f + +NR == 1 { + for (i = 1; i <= NF; i++) + xs[i, 2] = 1 +} + +!/[+*]/ { + for (i = 1; i <= NF; i++) { + xs[i, 1] += $i + xs[i, 2] *= $i + } +} + +/[+*]/ { + for (i = 1; i <= NF; i++) + total += xs[i, $i == "+" ? 1 : 2] +} + +END { print total } diff --git a/2025/06/puzzle-2.sh b/2025/06/puzzle-2.sh new file mode 100755 index 0000000..cba270f --- /dev/null +++ b/2025/06/puzzle-2.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +{ + printf '(' + for i in $(seq "$(head -n1 input | wc -c)") + do + num="$(cut -c $i input | paste -sd '')" + num="${num%${num##*[![:space:]]}}" + + case "$num" in + *[+*]) + test -n "$op" && printf ') + (' + op="${num#${num%?}}" + num="${num%?}" + printf '%d %s ' $num "$op" + ;; + '') + test "$op" = + + printf '%d' $? + ;; + *) + printf '%d %s ' $num "$op" + ;; + esac + done + printf ')\n' +} | bc diff --git a/2025/07/puzzle-1.py b/2025/07/puzzle-1.py new file mode 100755 index 0000000..0c30dfa --- /dev/null +++ b/2025/07/puzzle-1.py @@ -0,0 +1,23 @@ +#!/usr/bin/python3 + + +def main() -> None: + with open('input', 'r') as f: + grid = tuple(tuple(x.strip()) for x in f.readlines()) + + xs = set() + xs.add(grid[0].index('S')) + cnt = 0 + + for row in grid[1:]: + for i, ch in enumerate(row): + if ch == '^' and i in xs: + cnt += 1 + xs.remove(i) + xs.add(i - 1) + xs.add(i + 1) + + print(cnt) + +if __name__ == '__main__': + main() diff --git a/2025/07/puzzle-2.py b/2025/07/puzzle-2.py new file mode 100755 index 0000000..3695889 --- /dev/null +++ b/2025/07/puzzle-2.py @@ -0,0 +1,31 @@ +#!/usr/bin/python3 + +import collections +import functools + + +Pos = collections.namedtuple('Pos', ['x', 'y']) + + +def main() -> None: + with open('input', 'r') as f: + grid = tuple(tuple(x.strip()) for x in f.readlines()) + + pos = Pos(grid[0].index('S'), 0) + print(npaths(grid, pos)) + + +@functools.cache +def npaths(grid: list[list[str]], pos: Pos) -> int: + if pos.y == len(grid): + return 1 + if grid[pos.y][pos.x] == '^': + return ( + npaths(grid, Pos(pos.x - 1, pos.y)) + + npaths(grid, Pos(pos.x + 1, pos.y)) + ) + return npaths(grid, Pos(pos.x, pos.y + 1)) + + +if __name__ == '__main__': + main() diff --git a/2025/08/.gitignore b/2025/08/.gitignore new file mode 100644 index 0000000..ffc46fe --- /dev/null +++ b/2025/08/.gitignore @@ -0,0 +1 @@ +puzzle-[12].py diff --git a/2025/08/Makefile b/2025/08/Makefile new file mode 100644 index 0000000..0a4e980 --- /dev/null +++ b/2025/08/Makefile @@ -0,0 +1 @@ +include ../../Makefiles/py.mk diff --git a/2025/08/puzzles.py b/2025/08/puzzles.py new file mode 100644 index 0000000..086baca --- /dev/null +++ b/2025/08/puzzles.py @@ -0,0 +1,53 @@ +#!/usr/bin/python3 + +import functools +import heapq +import itertools +import math +import operator + + +type Point = tuple[int, int, int] +type Circuit = set[Point] + + +def main() -> None: + with open('input', 'r') as f: + boxes = [tuple(map(int, l.split(','))) for l in f.readlines()] + + circuits: list[Circuit] = [] + pairs = itertools.combinations(boxes, 2) + pairs = iter(sorted(pairs, key=lambda p: math.dist(*p))) + +# START PART 1 + for p, q in itertools.islice(pairs, 1000): + connect(circuits, p, q) + + lens = heapq.nlargest(3, map(len, circuits)) + print(functools.reduce(operator.mul, lens)) +# END PART 1 START PART 2 + while len(circuits) != 1 or len(circuits[0]) != len(boxes): + p, q = next(pairs) + connect(circuits, p, q) + print(p[0] * q[0]) +# END PART 2 + + +def connect(circuits: list[Circuit], p: Point, q: Point) -> None: + pc = next((c for c in circuits if p in c), None) + qc = next((c for c in circuits if q in c), None) + + match (pc, qc): + case (None, None): + circuits.append({p, q}) + case (x, None): + pc.add(q) + case (None, y): + qc.add(p) + case (x, y) if x is not y: + circuits.remove(qc) + pc |= qc + + +if __name__ == '__main__': + main() diff --git a/2025/09/puzzle-1.py b/2025/09/puzzle-1.py new file mode 100755 index 0000000..6d42141 --- /dev/null +++ b/2025/09/puzzle-1.py @@ -0,0 +1,29 @@ +#!/usr/bin/python3 + +import itertools + + +type Point = tuple[int, int] + + +def main() -> None: + with open('input', 'r') as f: + xs = [tuple(map(int, l.split(','))) for l in f.readlines()] + + it = itertools.combinations(xs, 2) + it = itertools.starmap(area, it) + print(max(it)) + + +def area(p: Point, q: Point) -> int: + a, b = minmax(p[0], q[0]) + c, d = minmax(p[1], q[1]) + return (b - a + 1) * (d - c + 1) + + +def minmax(x: int, y: int) -> tuple[int, int]: + return (x, y) if x < y else (y, x) + + +if __name__ == '__main__': + main() diff --git a/2025/09/puzzle-2.py b/2025/09/puzzle-2.py new file mode 100755 index 0000000..f77c4c1 --- /dev/null +++ b/2025/09/puzzle-2.py @@ -0,0 +1,49 @@ +#!/usr/bin/python3 + +import itertools + + +type Point = tuple[int, int] +type Box = tuple[int, int, int, int] + + +def main() -> None: + with open('input', 'r') as f: + xs = [tuple(map(int, l.split(','))) for l in f.readlines()] + + it = itertools.pairwise(xs) + it = itertools.starmap(tobox, it) + ys = list(it) + + n = 0 + it = itertools.combinations(xs, 2) + it = itertools.starmap(tobox, it) + for x in it: + if (_area := area(x)) <= n: + continue + a, b, c, d = x + for p, q, r, s in ys: + if a < r and b < s and c > p and d > q: + break + else: + n = _area + + print(n) + + +def tobox(p: Point, q: Point) -> Box: + a, b = minmax(p[0], q[0]) + c, d = minmax(p[1], q[1]) + return a, c, b, d + + +def area(b: Box) -> int: + return (b[2] - b[0] + 1) * (b[3] - b[1] + 1) + + +def minmax(x: int, y: int) -> tuple[int, int]: + return (x, y) if x < y else (y, x) + + +if __name__ == '__main__': + main() diff --git a/2025/10/puzzle-1.py b/2025/10/puzzle-1.py new file mode 100755 index 0000000..e3bd232 --- /dev/null +++ b/2025/10/puzzle-1.py @@ -0,0 +1,49 @@ +#!/usr/bin/python3 + +import dataclasses +import functools +import itertools +import operator +from typing import Self + + +@dataclasses.dataclass +class Machine: + target: int + buttons: list[int] + + @classmethod + def from_record(cls, s: str) -> Self: + buttons: list[int] = [] + for token in s.split(' '): + match token[0]: + case '[': + token = token.translate({ + ord('.'): '0', + ord('#'): '1', + }) + target = int(token[len(token) - 2:0:-1], base=2) + case '(': + n = 0 + for x in map(int, token[1:-1].split(',')): + n |= 1 << x + buttons.append(n) + return cls(target, buttons) + + +def main() -> None: + with open('input', 'r') as f: + xs = [Machine.from_record(x) for x in f.readlines()] + print(sum(map(fewest_clicks, xs))) + + +def fewest_clicks(mach: Machine) -> int: + for i in itertools.count(start=1): + for comb in itertools.combinations(mach.buttons, i): + if functools.reduce(operator.xor, comb) == mach.target: + return i + # NOTREACHED + + +if __name__ == '__main__': + main() diff --git a/2025/11/puzzle-1.awk b/2025/11/puzzle-1.awk new file mode 100755 index 0000000..aa752e1 --- /dev/null +++ b/2025/11/puzzle-1.awk @@ -0,0 +1,19 @@ +#!/usr/bin/gawk -f + +function npaths(src, dst, n, i) +{ + if (src == dst) + return 1; + for (i in paths[src]) + n += npaths(paths[src][i], dst) + return n +} + +BEGIN { FS = ":? " } + +{ + for (i = 2; i <= NF; i++) + paths[$1][i - 1] = $i +} + +END { print npaths("you", "out") } diff --git a/2025/11/puzzle-2.py b/2025/11/puzzle-2.py new file mode 100755 index 0000000..629c10c --- /dev/null +++ b/2025/11/puzzle-2.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 + +import functools + + +paths: dict[str, list[str]] = {} + +def main() -> None: + with open('input', 'r') as f: + for line in f.readlines(): + x, *xs = line.split() + paths[x[:-1]] = xs + + print(npaths('svr', 'out')) + + +@functools.cache +def npaths( + src: str, + dst: str, + dacp: bool = False, + fftp: bool = False, +) -> bool | int: + return ( + dacp and fftp + if src == dst else + sum(npaths( + nsrc, + dst, + dacp or src == 'dac', + fftp or src == 'fft', + ) for nsrc in paths[src]) + ) + + +if __name__ == '__main__': + main() |