diff --git a/2022-python/README.md b/2022-python/README.md new file mode 100644 index 0000000..2a5b22b --- /dev/null +++ b/2022-python/README.md @@ -0,0 +1,48 @@ +# Advent of Code 2022 + +Solutions for #aoc2022 in Python 3 (3.13.4). + +Programming setup: + +- Lenovo Thinkpad T14 +- OpenSUSE Tumbleweed with labwc +- Helix editor +- Vivaldi +- Foot + +## Help scripts + +Display all solved puzzles: + + python aoc.py + +To bootstrap a new puzzle (creates `input/.txt` and `output/day_.py`): + + python aoc.py new + +Manually copy the puzzle input from https://adventofcode.com and paste it in `input/.txt` +to start coding. + + wl-paste > input/.txt + +Solve separate puzzle (replace `XX` with the puzzle number): + + python -m output.day_XX + +Solve separate puzzle using stdin (replace `XX` with the puzzle number): + + wl-paste | python -m output.day_XX + cat tmpfile | python -m output.day_XX + +Execute separate puzzle on file save (replace `XX` with the puzzle number): + + ls output/*.py | entr -c -s 'wlpaste | python -m output.day_XX' + ls output/*.py | entr -c -s 'cat tmpfile | python -m output.day_XX' + ls output/*.py | entr -c -r python -m output.day_XX + +(requires `entr` and `wl-paste`, Mac users can instead use `pbpaste`. If you +prefer X at Linux, use `xclip -selection clipboard -o`). + +To lint files: + + ls output/*.py | entr -r -c flake8 output --ignore=E741,E501,E203 diff --git a/2022-python/aoc.py b/2022-python/aoc.py new file mode 100644 index 0000000..3bea33e --- /dev/null +++ b/2022-python/aoc.py @@ -0,0 +1,115 @@ +import sys +from pathlib import Path + + +def headline(n): + """Print day number and name, followed by a ruler. Used by the answer decorator""" + print(f"\nDay {int(n)} - https://adventofcode.com/{year}/day/{int(n)}\n") + + +year = 2022 + +try: + _, day_no, *name = sys.argv +except ValueError: + day_no = None + name = None + +Path("./input").mkdir(parents=True, exist_ok=True) +Path("./output").mkdir(parents=True, exist_ok=True) + +if day_no and name: + name = " ".join(name) + padded_no = day_no.zfill(2) + with open("output/day_{}.py".format(padded_no), "w") as s: + s.write( + f""" +import re +from collections import deque, Counter, defaultdict +from heapq import heappop, heappush +from itertools import compress, combinations, chain, permutations + +from output import matrix, D, DD, ADJ, ints, mhd, mdbg, vdbg, cw, ccw, bk + + +def solve(data): + p1 = None + p2 = None + return p1, p2 + + +if __name__ == "__main__": + import os + + # use dummy data + inp = \"\"\" + replace me + \"\"\".strip() + + # uncomment to instead use stdin + # import sys; inp = sys.stdin.read().strip() + + # uncomment to use AoC provided puzzle input + # with open("./input/{padded_no}.txt", "r") as f: + # inp = f.read().strip() + + # uncomment to do initial data processing shared by part 1-2 + p1, p2 = solve(inp) + + print(p1) + os.system(f"echo {{p1}} | wl-copy") + # print(p2) + # os.system(f"echo {{p2}} | wl-copy") + + # uncomment and replace 0 with actual output to refactor code + # and ensure nonbreaking changes + # assert p1 == 0 + # assert p2 == 0 +""".strip() + + "\n" + ) + exit(0) + +print( + f"\n\033[95m\033[1mAdvent of Code {year}\033[0m" + "\n###################" + "\n\n\033[96mby Anders Englöf Ytterström\033[0m" +) + + +stars = 0 +for i in [str(n).zfill(2) for n in range(1, 26)]: + if not day_no or day_no.zfill(2) == i: + try: + day = __import__( + "output.day_{}".format(i), + globals(), + locals(), + ["solve"], + 0, + ) + with open(f"./input/{i}.txt", "r") as f: + data = f.read().strip() + headline(i) + try: + data = day.presolve(data) + except AttributeError: + pass + try: + p1, p2 = day.solve(data) + except AttributeError: + pass + if p1: + print(f" \033[92m1)\033[0m {p1}") + stars += 1 + if p2: + print(f" \033[92m2)\033[0m {p2}") + stars += 1 + except IOError: + pass + except ImportError: + pass +if not day_no: + print(f"\nStars: {stars}") + print("".join("*" if n < stars else "•" for n in range(50))) +print("") diff --git a/2022-python/output/__init__.py b/2022-python/output/__init__.py new file mode 100644 index 0000000..ba3186c --- /dev/null +++ b/2022-python/output/__init__.py @@ -0,0 +1,195 @@ +import re + +# Directions/Adjacents for 2D matrices, in the order UP, RIGHT, DOWN, LEFT +D = [ + (-1, 0), + (0, 1), + (1, 0), + (0, -1), +] + +Di = [ + (-1, -1), + (-1, 1), + (1, -1), + (1, 1), +] + +# Directions for 2D matrices, as a dict with keys U, R, D, L +DD = { + "U": (-1, 0), + "R": (0, 1), + "D": (1, 0), + "L": (0, -1), +} + +DDa = { + "^": (-1, 0), + ">": (0, 1), + "v": (1, 0), + "<": (0, -1), +} + +# Adjacent relative positions including diagonals for 2D matrices, in the order NW, N, NE, W, E, SW, S, SE +ADJ = [ + (-1, -1), + (-1, 0), + (1, -1), + (0, -1), + (0, 1), + (1, 1), + (1, 0), + (1, -1), +] + + +def ints(s): + """Extract all integers from a string""" + return [int(n) for n in re.findall(r"\d+", s)] + + +def sints(s): + """Extract all signed integers from a string""" + return [int(n) for n in re.findall(r"-?\d+", s)] + + +def mhd(a, b): + """Calculates the Manhattan distance between 2 positions in the format (y, x) or (x, y)""" + ar, ac = a + br, bc = b + return abs(ar - br) + abs(ac - bc) + + +def matrix(d): + """Transform a string into an iterable matrix. Returns the matrix, row count and col count""" + m = [tuple(r) for r in d.split()] + return m, len(m), len(m[0]) + + +def mdbg(m): + """Print-debug a matrix""" + for r in m: + print("".join(r)) + + +def vdbg(seen, h, w): + """Print-debug visited positions of a matrix""" + for r in range(h): + print("".join(["#" if (r, c) in seen else " " for c in range(w)])) + + +def cw(y, x): + """Flip a (y, x) direction counterwise: U->R, R->D, D->L, L->U. + + >>> cw(-1, 0) + (0, 1) + >>> cw(0, 1) + (1, 0) + >>> cw(1, 0) + (0, -1) + >>> cw(0, -1) + (-1, 0) + """ + return (x, y) if y == 0 else (x, -y) + + +def ccw(y, x): + """Flip a (y, x) direction counterwise: U->L, L->D, D->R, R->U. + + >>> ccw(-1, 0) + (0, -1) + >>> ccw(0, -1) + (1, 0) + >>> ccw(1, 0) + (0, 1) + >>> ccw(0, 1) + (-1, 0) + """ + return (x, y) if x == 0 else (-x, y) + + +def bfs(S, E=None): + """BFS algorithm, equal weighted nodes""" + seen = set() + q = [(S, 0)] + g = {} # graph, required to be provided at some point + while q: + m, w = q.pop(0) + if m in seen: + continue + seen.add(m) + # investigate here + for s in g[m]: + q.append((s, w + 1)) + # return insights + + +def mhd_search(r, c, R=20): + """returns all coords that are within R manhattan distance from (r,c)""" + p = set() + for d in range(1, R + 1): + p.add((r, c + d)) + p.add((r, c - d)) + p.add((r + d, c)) + p.add((r - d, c)) + for dd in range(d): + p.add((r - dd, c - d + dd)) + p.add((r + dd, c - d + dd)) + p.add((r - dd, c - dd + d)) + p.add((r + dd, c - dd + d)) + return p + + +def dijkstras(grid, start, target): + """ + 1. Create an array that holds the distance of each vertex from the starting + vertex. Initially, set this distance to infinity for all vertices except + the starting vertex which should be set to 0. + 2. Create a priority queue (heap) and insert the starting vertex with its + distance of 0. + 3. While there are still vertices left in the priority queue, select the vertex + with the smallest recorded distance from the starting vertex and visit its + neighboring vertices. + 4. For each neighboring vertex, check if it is visited already or not. If it + isn’t visited yet, calculate its tentative distance by adding its weight + to the smallest distance found so far for its parent/previous node + (starting vertex in case of first-level vertices). + 5. If this tentative distance is smaller than previously recorded value + (if any), update it in our ‘distances’ array. + 6. Finally, add this visited vertex with its updated distance to our priority + queue and repeat step-3 until we have reached our destination or exhausted + all nodes. + """ + import heapq + + target = max(grid) + seen = set() + queue = [(start, 0)] + while queue: + cost, pos, direction, steps = heapq.heappop(queue) + y, x = pos + dy, dx = direction + + if pos == target: + return cost + + if ((pos, "and stuff")) in seen: + continue + + seen.add((pos, "and stuff")) + + neighbors = [] + for n in neighbors: + heapq.heappush(queue, ("stuffs")) + + return -1 + + +def bk(graph, p, r=set(), x=set()): + """Bron-Kerbosch algoritm, no pivot: https://en.wikipedia.org/wiki/Bron%E2%80%93Kerbosch_algorithm""" + if not p and not x: + yield r + while p: + v = p.pop() + yield from bk(graph, p & set(graph[v]), r | {v}, x & graph[v]) + x.add(v)