{"id":29630035,"url":"https://github.com/dps/aoc","last_synced_at":"2025-07-21T10:08:03.877Z","repository":{"id":65088215,"uuid":"573293125","full_name":"dps/aoc","owner":"dps","description":null,"archived":false,"fork":false,"pushed_at":"2024-12-28T21:11:56.000Z","size":6770,"stargazers_count":9,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-12-28T22:19:06.848Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dps.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-12-02T05:42:55.000Z","updated_at":"2024-12-28T21:12:00.000Z","dependencies_parsed_at":"2023-10-27T06:24:17.532Z","dependency_job_id":"0f09927c-30ba-4fde-8219-2bada137591e","html_url":"https://github.com/dps/aoc","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dps/aoc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dps%2Faoc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dps%2Faoc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dps%2Faoc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dps%2Faoc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dps","download_url":"https://codeload.github.com/dps/aoc/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dps%2Faoc/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266278418,"owners_count":23904045,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-07-21T10:08:03.244Z","updated_at":"2025-07-21T10:08:03.870Z","avatar_url":"https://github.com/dps.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# aoc\n\nhttps://blog.singleton.io/posts/2023-01-14-advent-of-code/\n\n## Utils\n\n#### A bunch of useful imports\n```python\nimport itertools\nimport math\nimport operator\nimport re\nimport sys\nimport heapq\nfrom collections import Counter, defaultdict, deque\nfrom copy import deepcopy\nfrom functools import cache, reduce\n```\n\n#### `maxl(list)`\nReturns the `max` element in an iterable, or 0 if the iterable is empty.\n\n### Graph stuff\n\n#### `floyd_warshall(graph, bidirectional=False)`\nGiven a graph `dict` of format `{vertex: [edges]}`, returns the shortest path between every pair of nodes in the graph.\n𝚯( |V|^3 )\ne.g.\n```python\n\u003e\u003e\u003e graph = {'A':['B','C'],'B':['D'],'C':['B'],'D':['F'], 'E':['F', 'A'], 'F':[]}\n\u003e\u003e\u003e dict(floyd_warshall(graph))\n{('A', 'B'): 1, ('A', 'C'): 1, ... , ('F', 'E'): inf, ('F', 'F'): inf}\n\u003e\u003e\u003e floyd_warshall(graph)[('E','B')]\n2\n```\n\n#### `find_shortest_path(graph, start, end)`\nGiven the graph `dict` of format `{vertex: [edges]}` find the shortest path from node start to node end. Does a breadth first search. Returns the path as a list or `None`\n```python\n\u003e\u003e\u003e find_shortest_path(graph, 'A', 'F')\n['A', 'B', 'D', 'F']\n```\n\n#### `dijkstra(graph, start, end)`\nGiven a graph `dict` of format `{vertex: [(weight, neighbor), ...] }` finds the shortest path using Dijkstra's algorithm. Returns tuple `(sum(path weights), [path])`\n```python\n\u003e\u003e\u003e grid, dim, _ = grid_ints_from_strs([\"0000\",\"9913\", \"9199\", \"5432\"])\n\u003e\u003e\u003e graph = {(x,y): \n...             [(int(grid[n[1]][n[0]]),n) for n in grid_neighbors((x,y), dim)]\n...           for x,y in itertools.product(range(dim), range(dim))}\n\u003e\u003e\u003e \n\u003e\u003e\u003e start, end = (0,0), (dim-1, dim-1)\n\u003e\u003e\u003e dijkstra(graph, start, end)\n(14, [(0, 0), (1, 0), (2, 0), (3, 0), (3, 1), (3, 2), (3, 3)])\n```\n\n#### `dynamic_dijkstra(neighbors, start, end)`\nGiven `neighbors` is a function which takes a node and returns a list of `(weight, neighbor)` pairs or `()` if no neighbors exist, finds the shortest path from `start` to `end` using Dijkstra's algorithm and returns `(sum(shortest path weights), [path])`\n\ne.g.\n```python\n\u003e\u003e\u003e neighbors = lambda ch:[(ord(ch), chr(ord('A') + ((ord(ch) - ord('A') + 1) % 26)))]\n\u003e\u003e\u003e dynamic_dijkstra(neighbors, 'C', 'B')\n(1949, ['C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'A', 'B'])\n```\n\n#### `a_star(graph, start, end, heuristic)`\nGiven `graph` is a `dict` of `vertex: [(weight, neighbor), ...] `\n`heuristic` is a function that takes in a vertex and returns an estimated cost to reach the end from that vertex, returns (sum(path weights), path) of the shortest path from start to end using the A* algorithm. `heuristic` must be _admissible_ i.e. it never overestimates the cost of reaching the goal.\n\n```python\n\u003e\u003e\u003e grid, dim, _ = grid_ints_from_strs([\"0000\",\"9913\", \"9199\", \"5432\"])\n\u003e\u003e\u003e graph = {(x,y): \n            [(int(grid[n[1]][n[0]]),n) for n in grid_neighbors((x,y), dim)]\n          for x,y in itertools.product(range(dim), range(dim))}\n\u003e\u003e\u003e start, end = (0,0), (dim-1, dim-1)\n\u003e\u003e\u003e a_star(graph, start, end, lambda x,y:manhattan(x,y))\n(14, [(0, 0), (1, 0), (2, 0), (3, 0), (3, 1), (3, 2), (3, 3)])\n```\n\n#### `dynamic_a_star(next_fn, start, end, heuristic)`\nVerson of A* taking a `next_fn` to generate neighbors from current node. `next_fn` should take `vertex` =\u003e `[(weight, neighbor), ...]`\n\ne.g.\n```python\n\u003e\u003e\u003e # An arbitrary non-trivial weight space\n\u003e\u003e\u003e neighbors = lambda e: [(triangle(p[0])+p[1]*p[1], p) for p in grid_neighbors(e, 100)]\n\u003e\u003e\u003e dynamic_a_star(neighbors, (0,0), (99,99), manhattan)\n\u003e\u003e\u003e (902975, [(0, 0), (0, 1), (1, 1), ..., (99, 98), (99, 99)])\n```\n\n\n## Cardinal directions and grid stuff.\n \nMost of these have two versions - one for complex number represention and one for tuple representation.\n\n#### `COMPASS`\n`{'E': (1,0), 'W':(-1,0), 'N':(0,-1), 'S':(0,1) }`\n\n#### `COMPASS8`\n```python\n{'NE': (1, -1), 'NW': (-1, -1), 'SE': (1, 1), 'SW': (-1, 1), 'E': (1,0), 'W':(-1,0), 'N':(0,-1), 'S':(0,1)}\n```\n\n#### `RLUD` \"Right, Left, Up, Down\"\n```python\n{'R': (1,0), 'L':(-1,0), 'U':(0,-1), 'D':(0,1) }\n```\n#### `ARROWS`\n```python\n{'\u003e': (1,0), '\u003c':(-1,0), '^':(0,-1), 'v':(0,1) }\n```\n\n#### `DIR`\n```python\n[(1,0),(-1,0), (0,1), (0,-1)]\n```\n#### `DIR8`\n```python\n[(1, -1), (-1, -1), (1, 1), (-1, 1), (1, 0), (-1, 0), (0, -1), (0, 1)]\n```\n#### `CDIR8`\n`[p[0] + 1j*p[1] for p in DIR8]`\n#### `CDIR`\n`[p[0] + 1j*p[1] for p in DIR]`\n\n## Distance stuff\n#### `manhattan` and `manhattani(p, q)`\nReturns the Manhattan distance between points in `(x,y)` tuple and imaginary number format respectively. `manhattan3(p, q)` for 3D (tuple only for obvious reasons).\n#### `cartesian(p, q)`\n`p` and `q` are `(x, y)` tuples. Returns the cartesian distance between `p` and `q` using Pythagoras' theorem. \n\n\n\n## Numeric\n\n#### `triangle(n)`\nReturns the `n`th triangular number - a sequence like `1,3,6,10,15`\n```\n\u003e\u003e\u003e list(map(triangle, range(20)))\n[0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 66, 78, 91, 105, 120, 136, 153, 171, 190]\n```\n\n### `sign(a)`\nReturns `-1` if a is negative, `0` if a is zero and `1` if a is positive. See also `math.copysign` and note that the semantics are different!\n```\n\u003e\u003e\u003e sign(0)\n0\n\u003e\u003e\u003e math.copysign(1,0)\n1.0\n```\n\n\n## Input parsing stuff\n\nFirst up, note that this is at the start of my default day template:\n```\ninput = [i.strip() for i in open(\"input.txt\",\"r\").readlines()]\n```\n\n### Thanks mcpower!\n### `ints(s)`, `positive_ints(s)`, `floats(s)`, `positive_floats(s)`, `words(s)`\nReturns a list of all the ints, positive_ints etc in a string respectively.\n```python\n\u003e\u003e\u003e ints(\"708,862 -\u003e 100,862\")\n[708, 862, 100, 862]\n\u003e\u003e\u003e positive_ints(\"708,862 -\u003e 100,-862\")\n[708, 862, 100, 862] # Note 862 is there but no sign!\n\u003e\u003e\u003e floats(\"0.0,1.2 -\u003e 999.0,-3.1515\")\n[0.0, 1.2, 999.0, -3.1515]\n\u003e\u003e\u003e words(\"Returns a _list_ -- of all the ints. (foo)\")\n['Returns', 'a', 'list', 'of', 'all', 'the', 'ints', 'foo']\n```\n\n## flatten(list_of_lists)\nReturns a single list of all of the elements from a list of arbitrarily nested lists at the same level:\n\n```python\n\u003e\u003e\u003e flatten([[0,1],[2,3],[4,[5,6]]])\n[0, 1, 2, 3, 4, 5, 6]\n```\n\n## Grid stuff\n\n### `grid_from_strs(lines, mapfn=lambda x:x, spl='')`\n`lines` is a list of strs, each one representing a row in a grid.\nReturns `(2D array, width, height` 2D array is of grid rows, split on `spl` or each character by default. Optionally applies function `mapfn` to each element in the grid.\n\n```python\n\u003e\u003e\u003e grid_from_strs([\"1,2,3\",\"4,5,6\",\"7,8,9\"], spl=\",\")\n([['1', '2', '3'], ['4', '5', '6'], ['7', '8', '9']], 3, 3)\n```\n\n### `grid_ints_from_strs(lines, spl='')`\nGiven `lines` is a list of strs, each one representing a row in a grid, returns `(2D array of ints, width, height)`.\n\n```python\n\u003e\u003e\u003e grid_ints_from_strs([\"0000\",\"9913\", \"9199\", \"5432\"])\n([[0, 0, 0, 0], [9, 9, 1, 3], [9, 1, 9, 9], [5, 4, 3, 2]], 4, 4)\n```\n\n### `grid_neighbors(p, width, height=None)`, `grid_8_neighbors(p, width, height=None)`\nGiven `p` is a point `(x,y)` in a grid of `width` x `height`, generates the up to four/eight neighbors of `p` that also lie within the grid. Clips at the edges of the grid, which is why this is useful!\n```R\n8  4  8\n \\ | /\n4--p--4\n / | \\\n8  4  8\n```\n\n*Note - `x` `y` not `row`, `col`... Matters for non-square grids*\n\n```python\n\u003e\u003e\u003e list(grid_neighbors((0,0),2))\n[(1, 0), (0, 1)]\n\u003e\u003e\u003e list(grid_neighbors((1,1),400))\n[(2, 1), (0, 1), (1, 2), (1, 0)]\n\u003e\u003e\u003e list(grid_8_neighbors((1,1),400))\n[(2, 0), (0, 0), (2, 2), (0, 2), (2, 1), (0, 1), (1, 0), (1, 2)]\n```\n\n#### `wrap(p, max_x, max_y, min_x=0, min_y=0)`\nWraps an imaginary coordinate `x + y*1j` back into a grid of size `max_x, max_y` etc. Does no modular arithmetic - i.e. `max+2 =\u003e 0`\n\n## Visualizations\n_Useful for debugging_\n#### `print_grid(g, spacing=0, markfn=lambda r,c,ch:\"\")`\nGiven `g` is a grid - a 2D array as above, print the grid. Optionally add spacing (shorter values get padded so print out is tidy). Optionally use `markfn` to apply a _mark_ to certain elements (e.g. to show a path through a grid or similar)\n```python\n\u003e\u003e\u003e grid, _, _ = grid_from_strs([\"123\",\"456\",\"789\"])\n\u003e\u003e\u003e print_grid(grid, spacing=3, markfn=lambda r,c,ch:\"*\" if int(ch)%2==0 else \"\")\n1  2* 3  \n4* 5  6* \n7  8* 9 \n```\n#### `print_world(world)`\nGiven `world` is a set of imaginary numbers representing points in a 2D plane of form `x+y*1j`, prints the set as a matrix of orange (present) and black (absent) squares.\n```python\n\u003e\u003e\u003e world = {(triangle(y)+y*1j) for y in range(5)}\n\u003e\u003e\u003e print_world(world)\n🟧⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️\n⬛️🟧⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️\n⬛️⬛️⬛️🟧⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️\n⬛️⬛️⬛️⬛️⬛️⬛️🟧⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️\n⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️🟧⬛️⬛️⬛️⬛️⬛️\n⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️🟧\n```\n## Cookbooks\n\nA hashable `set`\n```python\ns = frozenset([1,2])\nt = set(s)\nt.add(3)\nt = frozenset(t)\n\u003e\u003e\u003e t\nfrozenset({1, 2, 3})\n```\n\n##### `functools.cache`\n```python\n@cache\ndef expensive_fn(hashable_args):\n  expensive()\n```\n\nBinary =\u003e decimal\n```python\nint(\"1001\", 2)\n```\n\nDefaultdicts\n```python\nfrom collections import defaultdict\n\nacc = defaultdict(int)\nacc['z'] += 1\n```\nDefaultdicts of defaultdicts\n```python\nfrom collections import defaultdict\n\nacc = defaultdict(lambda : defaultdict(int))\nacc['a']['circle'] += 1\n```\n\nCopy\n```python\nimport copy\n\nc = copy.deepcopy(input)\n```\n\nLists -- sum elements matching filter\n```python\nones = sum(map(lambda x : x == \"1\", list_var))\n# more pythonic\nones = sum([x == \"1\" for x in list_var])\n# simpler\nones = list_var.count(\"1\")\n```\nLists -- filter\n```python\noxy = [x for x in filter(lambda x : x[pos] == selected, oxy)]\n# more pythonic\noxy = [x for x in oxy if x[pos] == selected]\n```\n\n`itertools`\nhttps://docs.python.org/3/library/itertools.html\n\n`groupby` -- start with a sorted string\n```\n\u003e\u003e\u003e [list(g) for k,g in itertools.groupby('AABBBBA')]\n[['A', 'A'], ['B', 'B', 'B', 'B'], ['A']] \n```\n\nChar positions\n```\n\u003e\u003e\u003e list(zip(itertools.count(), 'David')) \n[(0, 'D'), (1, 'a'), (2, 'v'), (3, 'i'), (4, 'd')]\n```\n\n## References\nhttps://gist.github.com/mcpower/87427528b9ba5cac6f0c679370789661\n\nhttps://www.youtube.com/watch?v=IIaj7MSFEcU\u0026list=PLZhotmgEsCQNhE-X5bkcVvlyAMzcCqAEw\n\nhttps://blog.vero.site/post/advent-leaderboard\n\n## Notes\n\n*Write less code*. The best/fastest competitors all have short solutions - less code equals less bugs.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdps%2Faoc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdps%2Faoc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdps%2Faoc/lists"}