{"id":23960806,"url":"https://github.com/danvk/aoc2024","last_synced_at":"2026-05-25T14:03:19.290Z","repository":{"id":266569910,"uuid":"898723373","full_name":"danvk/aoc2024","owner":"danvk","description":"Advent of Code, this time in Elixir","archived":false,"fork":false,"pushed_at":"2024-12-25T14:52:50.000Z","size":317,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-21T11:48:41.696Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Elixir","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/danvk.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":"2024-12-04T23:05:59.000Z","updated_at":"2025-01-03T16:34:30.000Z","dependencies_parsed_at":"2024-12-05T00:20:00.932Z","dependency_job_id":"db009d08-447c-4318-a2dd-22929dcd1649","html_url":"https://github.com/danvk/aoc2024","commit_stats":null,"previous_names":["danvk/aoc2024"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/danvk/aoc2024","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danvk%2Faoc2024","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danvk%2Faoc2024/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danvk%2Faoc2024/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danvk%2Faoc2024/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/danvk","download_url":"https://codeload.github.com/danvk/aoc2024/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danvk%2Faoc2024/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28071259,"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","status":"online","status_checked_at":"2025-12-27T02:00:05.897Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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-01-06T19:41:45.467Z","updated_at":"2025-12-27T03:30:47.022Z","avatar_url":"https://github.com/danvk.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Advent of Code 2024\n\nCompile and run code for a day with:\n\n    mix escript.build \u0026\u0026 ./main 4 input/day4/sample2.txt\n\n## Day 1 (40886 / 37736)\n\nQuite easy, interesting that the problem isn't entirely line-oriented. I can't imagine an LLM would have any trouble with the problem today.\n\nAfter working through the first three days of the 2016 Advent of Code in Elixir, setup was no problem today. I can tell that pipes, the various `Enum` methods and `Map`s are going to be critical. I'm also wondering if this is going to be a strange introduction to Elixir. The AoC problems are all short-running, single-threaded, one-off scripts. My sense is that Elixir is better known as a language for long-running, concurrent processes.\n\n## Day 2\n\nI raced to get this done in the 15 minutes I had before Spanish class. The trick was getting an index, which most Elixir methods don't want to give you. I wound up using `Enum.zip` with `1..Enum.count(xs)`. No idea if this is idiomatic, but it works. For part 2, I implemented a drop function and tried all drops.\n\n## Day 3\n\nDownloaded the puzzle via personal hot spot before our plane took off for Cali but didn't have time or energy (it was 4 AM!) to work on it before we left. The plane doesn't seem to have wifi so I can't submit ☹️\n\nFirst time using a regular expression. It's `~r//`, not a string. I guess that's a \"raw charlist?\"\n\nI'm starting to think that, from a JS/TS perspective, the most interesting thing I'm going to learn about Elixir is how it handles pipelines, generators and anonymous functions. For example:\n\n```elixir\nnums |\u003e Enum.sum()\n```\n\nThis feels natural, but it's actually pretty weird! `Enum.sum/1` takes one argument, but you're calling it with none. How does that work? I assume it's something to do with macros.\n\nLots of my pipeline expressions have been of the form:\n\n```elixir\nlist\n|\u003e Enum.map(fn1)\n|\u003e Enum.map(fn2)\n...\n```\n\nI assume this can be written more concisely (without all the `Enum.map` calls) using generators.\n\n## Day 4\n\nTotally missed that the XMAS could be _diagonal_ on part 1.\nThis is the first day involving a grid, Again, storing grids as a map from `{x, y}` tuples to characters is usually the way to go.\nElixir's way of representing strings (linked list of chars) worked well today since it was easy to throw `nil` into a string. `[?X, ?M, nil, nil]` works just fine and is not `== ?c\"XMAS\"`.\n\nI'm using more comprehensions and fewer pipeline operations. Comprehensions are a convenient way to flatten as well as map. I'm thinking that the angle for my eventual blog post will be \"what Elixir can teach JS about the pipeline operator proposal.\" Pipelines make you want more compact ways to define functions, but maybe they really make you want comprehensions.\n\nElixir ranges are inclusive on both ends: `x \u003c- 0..w` will include `x=w`. This is pretty unusual!\n\nThe Elixir docs say that charlists are rare and mostly for Erlang compatibility, so maybe I'm overusing them.\n\n## Day 5\n\nPart 1 was fiddly and annoying. I missed the \"ignore all rules mentioning a number not in the input.\" Part 2 was a head scratcher for a moment, until I realized you could sort by the number of required predecessors. I guess there's no transitivity in these rules? This could have been harder!\n\n## Day 6\n\nHow do you write one char to stdout?\nDoes Elixir have a `Set` type?\nGitHub copilot is mega-unhelpful here, giving me recursive `loop` constructs that don't work.\n\n`Map.put(map, key, value)` is very different than `map.put(key, value)`.\n\nI could have really used some type help to keep positions and values distinct.\n\nI keep finding it annoying that you can't put multiple statements inside a comprehension.\n\nJeremy reported having to do some optimizations to make part 2 finish in a reasonable amount of time. I did run in optimized mode, and it took ~20s to complete. So I guess I am benefiting from Elixir/Erlang's speed, at least relative to Python.\n\nJeremy also pointed out that I only needed to consider the locations visited in part 1 when solving part 2. Of course! After implementing that optimization, my code only takes ~4s to solve both parts.\n\nThis looks extremely helpful: https://hexdocs.pm/elixir/enum-cheat.html\n\n- There's a `:reduce` option with comprehensions.\n- Use `Enum.member?` to test for membership, or equivalently `item in enum`. (Presumably this is always O(N).)\n- `string =~ part` checks if `part` is part of `string`. (It also applies a regex, just like Perl.)\n- `Enum.map_reduce` lets you map and reduce simultaneously, resulting in a pair.\n- `Enum.scan` is equivalent to my `accumulate` helper: like `reduce`, but returns a list with the accumulator value at every item.\n- `Enum.frequencies` gives you a frequency Map.\n- `Enum.into` converts an enum into a \"collectable.\"\n- `Enum.to_list` does what you'd expect.\n- `Enum.uniq` de-dupes an entire collection, while `Enum.dedupe` only dedupes contiguous elements.\n- `Enum.with_index` produces a `value -\u003e index` Map.\n- `Enum.with_index` is like Python's `enumerate`. It's like `Enum.map` but with an index parameter. Annoyingly, it keeps the index in the return type.\n- `Enum.slide` moves an element or range to a new index.\n- `Enum.chunk_by` splits an enumerable every time a function returns a new value.\n- `Enum.unzip` converts a list of tuples to a tuple of lists.\n\nhttps://hexdocs.pm/elixir/gradual-set-theoretic-types.html#supported-types\n\n- Types are currently not very fine-grained (`tuple()` is an indivisible type) but they plan to change this after getting feedback on the quality of type errors.\n- Types are \"set-theoretic\" in that you can write `A or B` and `A and B`.\n- There's also a `not()` construct, which is unliked anything in TypeScript: `atom() and not (:foo or :bar)`.\n- The top type is `term()`. The bottom type is `none()`.\n- Typing is gradual via `dynamic()`. This is distinct from `any` in that you can write something like `dynamic(atom() or integer())` and you'll be allowed to call functions that accept `atom()` or `integer()` but not `string()`. I think Pyright does something like this, too.\n\nUse can use `with` to do a series of pattern matches.\n\n## Day 7\n\nMax of 12, not so bad.\n\nPart 1: Left to right, they said! I reversed all the inputs to get that to work.\n\nPart 2: Of course, after part 2, I had the `concat` call backwards.\n\nToday was pretty easy. I guess I could have sped it up by only considering the instructions that failed part 1 for part 2, but the whole thing ran in only 9s.\n\nI'm playing around with type hints for day 6 and am… disappointed. After writing a bunch of typespecs, I still get no quickinfo beyond \"variable\" when I inspect a function parameter. I'm also not getting type errors in the editor.\n\nI feel like I'm missing something. Are `@spec` and `@type` checked in any way? https://elixirschool.com/en/lessons/advanced/typespec indicates that they're mostly for documentation, but https://hexdocs.pm/elixir/typespecs.html#functions-which-raise-an-error indicates that Dialyzer can check them. This article has some advice: https://fly.io/phoenix-files/adding-dialyzer-without-the-pain/\n\nRunning `mix dialyzer` I get:\n\n    Total errors: 0, Skipped: 0, Unnecessary Skips: 0\n\nwhich seems wrong since I deliberately introduced a type error. If I make a very simple type error (returning string instead of integer) then I do get a warning in VS Code via Dialyzer. But I see no improvements in quickinfo. If this is true, I think it's something Elixir can learn from TS: types are about the full DX, not just catching errors. This was a mistake that Closure Compiler made.\n\n## Day 8\n\nPretty easy today. My one hiccup was that I needed _two_ `flat_map`s, one to flatten across frequencies and one to flatten across pairs of spots. I thought about iterating until I was out-of-bounds on part 2, but didn't bother. I just construct `max(w, h)` points in either direction from each pair and filter to what's out of bounds. Very inefficient, but trivially correct and fast enough for today.\n\n## Day 9\n\nOh man would this be easier with an array.\n\nInteresting that a default value for a parameter can't refer to previous parameters.\n\n```elixir\ndef repeat(x, n) do\n    for _ \u003c- 1..n, do: x\nend\n\n\u003e\u003e\u003e repeat(nil, 0)\n[nil, nil]\n```\n\nWhy isn't a `do..end` block allowed after a `-\u003e` in a `case`? I guess you don't need it, it's fine to put multiple statements in there.\n\nIn Elixir, `None \u003e 0`.\n\nWell that was brutal. I think today was uniquely poorly-suited to Elixir. It's way easier with an array and mutation. That being said, there were other problems, too. I got wrong answers for both parts. In part 1, the problem was Elixir's odd treatment of ranges: 1..0 is a non-empty range equal to [1, 0]. That is… strange. This wasn't a part of my code that I expected to break, so I didn't unit test it.\n\nMy code worked on the sample input, so I was kind of at a loss. To debug, I implemented a solution in Python, confirmed that it was correct by getting my star, and then checked its output vs. my Elixir code on prefixes of the input. 20 chars was enough to get a difference.\n\nFor part two, I switched to using a Map. I think I set this up backwards from what would be useful (range -\u003e value) instead of (value -\u003e range), but whatever. I got a correct answer on the sample input, but again, I got a wrong answer on my own input. I looked for off-by-one errors but didn't find any.\n\nSo once again, I re-implemented a solution in Python. Part 2 was much more challenging and a lot slower, but a brute force solution still worked and showed the difference. I had a bug in my Python code: I'd find the lowest gap and swap into it, but you only want to do that if the gap is before the span that you're considering. It turned out that my Elixir code had the same bug (it must have been slightly more subtle since I got the right answer on the sample input). Fixing it got me the correct answer (and it's faster than Python!).\n\nSo lesson: be really wary of Elixir ranges?\n\n## Day 10\n\nNot too bad today, just construct a graph and do a BFS. For part 2, I changed my `visited` set to a map from position -\u003e number of times I visited that node.\n\nA few Elixir notes for the day:\n\n- ~You can't do `nil or 0` in Elixir.~ You do `nil || 0` instead.\n  - https://stackoverflow.com/q/34605325/388951\n  - `or` and `and` require a boolean, `||` and `\u0026\u0026` do not.\n  - `or` and `and` short-circuit, `||` and `\u0026\u0026` do not.\n- A `MapSet` is Elixir's version of a set.\n- `Map.get_and_update` lets you retrieve the current value and fill in a new value for a map in one pass, but it has a really weird contract. It requires you to return a pair with the current value and the new value. Huh? Why does it need to old value? Another surprise: it returns a tuple with the new value and the map, not just the map. Maybe I should just write a wrapper for this.\n\nI'm going to try installing Elixir 1.18, which hit RC today. It has significant improvements to \"set-theoretic types\" and language services. It's on the `v1.18.0-rc.0` branch, but unclear to me how to install this with homebrew. Instead, I'm using\n\n    brew unlink elixir\n    brew install --HEAD elixir\n\nNo new type errors, no obvious difference in language service.\n\n## Day 11\n\nThis was a fun one. I had about 25 minutes between breakfast and our departure for a hike (Sendero Pre-Hispánico). I did manage to get both stars, but it was a nail-biter!\n\nPart 1 was straightforward. Part 2 obviously wasn't going to work. My first instinct was to fit a curve to the number of stones per blink. It's very close to an exponential, but not exactly. I thought it might be some kind of Fibonacci-like sequence, but I wasn't going to get that to work quickly enough.\n\nI decided to give up and quickly wrote down some ideas for what to do next in this doc. My second idea was to track just the count for each number, since the position of the stones doesn't matter. Wait, that might just work! I raced furiously to code it up and got my second star just as we had to leave.\n\nI did not notice any difference from the new version of Elixir today, so I'm not entirely confident I have it set up correctly.\n\n## Day 12\n\nToday was a bit of a strugglefest. I spent most of the time for part 1 writing code to relabel the grid so that all components were contiguous. This was harder than I expected -- you can't just iterate over the grid, you really need a DFS. Otherwise you risk splitting components in half.\n\nFor part 2 I checked if the next segment clockwise was a fence segment of the same type. It's a little ugly but it worked!\n\nThe lack of type checking really is a disaster for Elixir. I'm constantly making type errors with enum methods on maps.\n\n```\n- Map.get(grid, x, y)\n+ Map.get(grid, {x, y})\n```\n\nDiscussion on RC's #elixir suggests this may never be a thing type checking can help with. So maybe the better solution for me is to make a `Grid` module with stricter methods.\n\n## Day 13\n\nFirst thought: A*, priority search! Second thought: you only need to scan in one dimension since the number of A presses uniquely determines the number of B presses. It's like a line with a bend. Upon seeing part 2: oh wait, this really is just a system of equations. And fortunately none of the inputs have A \u0026 B parallel to each other, which would be a _much_ harder problem.\n\nSince we didn't have internet at Rio Claro, or in the room at El Paujil, this is reminding me a lot of 2019's AoC, where I was always catching up.\n\nI want to be able to pattern match on strings to extract numbers, e.g. `\"a=#{a}\" = txt`.\n\nThe Elixir language service is constantly crashing without internet. It's complaining about Elixir 1.19 not being supported. Since I'm getting no value out of the new version, I should probably downgrade when I get better wifi.\n\n## Day 14\n\nYou can write `String.slice(2..-1//1)` to read until the end of the string.\n\nPart 1 is (hopefully) not too bad. I assume part 2 will ask us to do a trillion steps, in which case my guess is that this all repeats after 103*101 steps. Or if not, then at least each individual robot will repeat with some cycle.\n\n… wow was that ever not what I expected. Things I tried:\n\n- Symmetric (according to quadrants) -- too many\n- Symmetric (truly) -- none\n- Look at first 100 -- nope\n- Look for states where an unusual number of robots have neighbors. Bingo!\n\n## Day 15\n\nPart 1: (hopefully) not as bad as I'd thought, shoving is just a matter of finding the next open square in that direction and swapping two cells.\n\nI honestly have no clue what part 2 will be. Iterate a lot longer? Do it with several robots? One of the moves spawns a new robot? There's gravity?\n\nIt's kind of Eric to put a boundary wall around his grids. It means you don't need any bounds checking.\n\nOK, that was definitely not what I was expecting! I think this is just going to be annoying. My swapping trick won't work. Instead, I need two recursive functions: one to check if shoving is feasible, and one to actually do it.\n\n… I'm actually reasonably happy with this solution. My one significant bug not setting the old location of a block to `.` after you shoved it. Printing the grid on every step helped debug this.\n\nThere's no way to do either/or matches in Elixir:\nhttps://stackoverflow.com/q/39139814/388951\n\n## Day 16\n\nI copy/pasted the text from the problem page that I'd loaded on my phone. This works surprisingly well!\n\nI don't think Elixir has a built-in Heap module, which will make implementing A* hard.\n\nI don't think I like putting destructuring patterns in function signatures. It feels like it exposes some of the implementation and prevents you from giving the parameters meaningful names.\n\nA* search always feels like a bit of a miracle when it works. But after many years of AoC, I feel pretty confident about implementing it!\n\nPart 2: I need to modify the search in three ways:\n\n1. To return paths\n2. To consider all optimal paths\n3. To not prune identical-scoring paths to the same state.\n\nI think I'll make it unconditionally return paths. Then maybe I'll have a variation that returns all paths to the target with a given length. I'm not sure how to implement the third one. Maybe I can't prune at all?\n\nOK, in retrospect I must prune. But I can prune only if the previous distance is strictly less than the current distance. Part 2 didn't wind up being as bad as I'd feared, though I did have to copy/paste `search.ex` for the day 16 variation, which is kinda gross.\n\n## Day 17\n\nThis seems tedious to implement. Probably a good first opportunity to use an Elixir struct.\n\nThere are a lot of silly, annoying things in Elixir's VS Code setup. For example, typing a \":\" anywhere in a comment autocompletes to \":application\". If you end a line with a \":\" (as I often do!) then you'll hit enter and accept the autocomplete.\n\nDefining a struct feels pretty funny without types. You just specify the field names. I wrote `ip: integer` without even thinking about it.\n\nEarly exit is not a thing, they recommend helper functions:\nhttps://elixirforum.com/t/how-to-return-or-break-from-an-action-early/20904/4\n\nThis is something I've observed, too. And I don't like it.\n\nElixir LSP crashloops if there are any errors when you start it.\n\n\"No such function foo/2 exists\" feels like a misleading message when you've just passed the wrong number of arguments.\n\nOnly the last six bits of A affect the next output.\n4 and 6 have unique last six bits that produce them.\nThere are two 4s and one 6 in the input.\n\n… I couldn't get that to work. Instead I implemented the machine encoded by my input in Python, then did brute force search digit-by-digit. I don't think you can do one digit at a time since the last six digits matter. So I tried 8*8 possibilities and then allowed the top oct to vary on the next round.\n\n## Day 18\n\nEasy application of search. I expected part 2 to be a time-based search (position X, Y becomes blocked at time T). This seems easier.\n\nFor part 2, I did a manual binary search, then a linear search.\n\n## Day 19\n\nPart 1: I'll create a graph of allowed letter transitions. Wait, isn't that how grep works? Uh, this is just grep?\n\nOf course that won't work for part 2. My first thought was that I might need to create the graph. But then I had a better thought: dynamic programming from the end of the string. For the last N characters, try matching each pattern against the start. Then you already know the number of ways to make the rest.\n\nI wound up implementing this in Python, but I'll go back and port it to Elixir later.\n\n## Day 20\n\nMy first thought was to add `hasCheated` to the search state, but that's not quite what you want since we're not just looking for the optimal cheat. I'd need to adapt my Day 16 search to really have a `maxDistance` option.\n\nSecond thought is to try adding cheats to each `#` adjacent to the optimal path and re-run the plain-vanilla shortest path search for each. I think you can only go through a single `#`, not `##`. Interestingly there are no walls with a width of exactly two. So I guess he specifically doesn't want to consider this case.\n\nI think these are equivalent but I don't really understand why:\n\n```elixir\ndefp parse_line(line), do: String.split(line)\n\ndefp parse_line(line) do\n    String.split(line)\nend\n```\n\nSecond thought worked great. Took ~1 minute to run, trying 9336 candidate cheats. An optimization might be to not reconstruct the path for the second pass (where you only care about distance).\n\nPart 2: now the ambiguity in the question is more front-and-center. He never shows a cheat passing over open track and back into a wall, but I don't see why you couldn't do that. I think I do want to go back to my first thought.\n\nI think the A* search will be too slow for part 2. I need a different approach.\n\nI can calculate the (non-cheating) distance from the finish to every open square.\nThen, for every cheat_start (9336 candidates), look at all the cheat_ends within d=20 (~200). This will form a diamond pattern. For each of these, check:\n\n1. Does it save enough distance?\n2. Is there a neighboring `#` within d=19 of the cheat_start?\n\nThis should be very fast.\n\n… this is surprisingly annoying to implement. The \"cheat start\" for uniqueness purposes is the position _before_ the first wall, not the position of the first wall.\n\nAlso, with max_cheat=20, the first wall you bust through might not be adjacent to the shortest non-cheaty path. I need to consider _all_ walls.\n\nOn second thought, I may have completely made up the constraint that the cheat ends immediately after going onto a `.` square.\n\nDo I need to consider non-optimal paths between cheat_start and cheat_end? I don't think so, the phrasing is \"this cheat saves X picosecond,\" which to me implies optimality.\n\n\"Each cheat has a distinct start position (the position where the cheat is activated, just before the first move that is allowed to go through walls)\" -\u003e this implies to me that a cheat must start just before you move onto a `#`.\n\nAh, maybe I'm not allowing you to cheat via the outer perimeter?\nThat was definitely a part of it. The other part was that cheats don't need to start right before you cross a wall. They can start anywhere. So the quote about \"just before the first move\" was extremely misleading.\n\nI found today's puzzle super frustrating. The most frustrating so far. Mostly because part 2 was so contrived and ambiguous.\n\nYou _can_ use a `when` clause in a comprehension, it just doesn't go where I expected:\n\n```elixir\nfor x when x \u003e= 0 \u003c- [1, -2, 3, -4], do: x\n```\n\n## Day 21\n\nMy assumptions are:\n\n1. It's not computationally feasible to treat this all as one big search problem.\n2. You need to take _an_ optimal path through the numeric keypad. Presumably there's a reason the problem mentions (and shows) the three optimal paths.\n3. It probably doesn't matter how you handle the directional keypads since you keep having to go back to the \"A\" button.\n\nTyping out explicit types in Elixir might actually be really annoying in practice since it pushes you to write so many tiny, external helper functions.\n\nIt seems that the sequence of dirpad presses _does_ matter, ever so slightly. I have to keep a pool of all the shortest sequences, I can't just pick one. This makes the problem blow up. For `029A` after the first robot, I have 128 optimal sequences. After the second, I have 589824 optimal sequences.\n\nIt might be interesting to see exactly why picking one shortest dirpad sequence doesn't give the optimal result. This only seems to be an issue with 2+ robot dirpads in the middle. For `029A`, I get a sequence with length 70 instead of 68.\n\nThis is the difference between the optimal and non-optimal sequences:\n\n```\n- ~c\"v\u003c\u003cA\u003e\u003e^A\u003cA\u003eAvA\u003c^AA\u003eA\u003cvAAA\u003e^A\"\n+ ~c\"\u003cv\u003cA\u003e\u003e^A\u003cA\u003eAvA\u003c^AA\u003eA\u003cvAAA\u003e^A\"\n```\n\nSince you start on `A`, the bad one travels a greater distance from `A`-\u003e`\u003c` unnecessarily.\nI can calculate this cost cheaply and sort by it.\n\nI'm _still_ blowing up. The next culprit was `dir_for_dir`, which calculates all direction sequences for a direction sequences. Throwing a `min_by` there lets me run instantly up to ~n=5. I'm still blowing up somewhere, though.\n\nAll places where I'm generating a list of sequences are suspect. There's also just some blowup in the sequence itself. Each sequence is ~2.5x the length of the previous one. So even storing the full sequence is too much. (It would be ~100B key presses.)\n\nMaybe I can do memoization / dynamic programming on `(dirkey1, dirkey2, depth)`.\nOr that won't quite work. It'll still blow up. But it does feel like there's some recurrence relation I can find here.\n\ncost(dirkey1, dirkey2, depth) = best path from dirkey1 -\u003e dirkey2 * cost of each transition at depth-1.\n\nThat was basically right. It just took a lot of fiddling to get the costs perfectly correct.\n\n## Day 22\n\nThis felt way too easy for day 22. After foibles the last few days, I moved very deliberately through the examples, making sure I didn't have any off-by-ones. Searching through the \"quads\" was too slow for my input, but making an index from quad -\u003e price resolved that. I just do brute force search across `(-9..9)*4` and, after a few hundred million map lookups, I have my answer.\n\nI did get a wrong answer for part 2 -- the example shows 10 secrets _including_ the initial secret, but then the problem asks for 2000 secrets _after_ the initial secret. Fortunately this was not too hard to spot.\n\nRyan pointed out a big speedup: you can merge the indexes and take the max value across them. Doing this sped up part 2 from ~45s -\u003e ~4.5s. I guess there are a lot of \"quads\" that never occur.\n\nThis is an interesting example of typing going differently than in TS:\n\n```elixir\ndef get_tail(list), do: tl(list)\n\ndef main(input_file) do\n  IO.puts(get_tail(1..10))\nend\n```\n\nI get this error on the `IO.puts` line:\n\n```\nThe function call will not succeed.\n\nDay22.get_tail(%Range{:first =\u003e 1, :last =\u003e 10, :step =\u003e 1})\n\nwill never return since the 1st arguments differ\nfrom the success typing arguments:\n\n(nonempty_maybe_improper_list())\n```\n\nThis comes from Dialyzer, not the new type checker. TS would make you put a type signature on `get_tail`, which would force the error to be either in the caller or the implementation.\n\n## Day 23\n\nAnother surprisingly easy one.\n\nHad a wrong answer because it needs to _start_ with `t`, not just _contain_ a `t`.\n\nFor part 2, trying to add single nodes to each fully-connected cluster works fine. Fully-connected is a pretty strong constraint, so the number of clusters doesn't blow up too much before it shrinks again:\n\nn: 2, num: 3380\nn: 3, num: 11011\nn: 4, num: 26455\nn: 5, num: 45045\nn: 6, num: 55770\nn: 7, num: 50622\nn: 8, num: 33462\nn: 9, num: 15730\nn: 10, num: 5005\nn: 11, num: 975\nn: 12, num: 91\nn: 13, num: 1\n\nIt's kind of weird how restricted \"when\" clauses are in comprehensions in Elixir.\n\n## Day 24\n\nPart 1 doesn't seem too bad. First instinct was to push forward but on second thought maybe I'll work backwards from the outputs and memoize. My guess is that part 2 is going to ask for the smallest input that produces 2024 as an output.\n\n… as it turns out, the memoize was not necessary.\n\n221 output wires.\n\n    221 * 220 * 219 * 218 * 217 * 216 * 215 * 214 / (2^4) ~= 2^58\n\nso brute force search isn't going to cut it.\n\nOne constraint is that z00 should only depend on x00 and y00. I have this in my input:\n\n    x00 XOR y00 -\u003e z00\n\nSo that rule is perfect and should not be swapped. One down!\n\nThere are 45 x and y wires, so this needs to work for all values between 0 and 2^45-1.\n\nBinary addition is pretty simple. We should have:\n\n    z(N) = (x(N) XOR y(N)) XOR (carry from N-1)\n    carry(N) = (x(N) and y(N)) OR (carry(N-1) and (x(N) or Y(N)))\n\nx00 AND y00 -\u003e sgv = carry(0)\ny01 XOR x01 -\u003e ntn\nsgv XOR ntn -\u003e z01 = (x01 XOR y01) XOR carry(0)\n\nSo z01 is also correct. I guess it's still possible that these could be involved in a swap, so long as it has no effect?\n\nIt should also be the case that z(N) does not depend on any x(M) or y(M) where M \u003e N. At least not in a way that's consequential. This might be hard to enforce in practice since there are expressions like X XOR X = 0.\n\nNo z wire appears on the RHS of a circuit. Swapping could potentially create a cycle.\n\nI could find the first z that's not correct.\n\nThe idea about looking for the expected inputs is promising. For my circuit, only one z has the wrong inputs:\n\nz34 is missing expected inputs: y17, y22, y06, y26, x29, x25, y33, x19, x26, y00, x01, x21, x09, x24, x13, x23, x22, y05, x11, y31, x31, x16, x06, y09, x05, y11, x04, x00, y07, y28, x20, y19, x30, x28, y16, y27, y20, x02, y08, x15, y14, x12, y03, y02, x07, y13, y32, x32, x18, x17, x14, x10, x08, y18, x33, y12, x03, y01, y25, y21, y04, y30, y29, y23, y24, y15, y10, x27\n\nSo can I assume that the wrong circuits are all in the tree that feeds into z34? That can't be right since swapping those circuits wouldn't introduce any new inputs. But it does feel like this is a pretty big hint! It also doesn't mean that z00..z33 are all wired correctly, just that they have the right inputs.\n\nz02 = [\n  \"x00 and y00 -\u003e sgv\", carry(0)\n\n  \"y01 xor x01 -\u003e ntn\"\n  \"sgv and ntn -\u003e rsk\",\n  \"y01 and x01 -\u003e gmn\",\n  \"rsk or gmn -\u003e vbb\",  carry(1)\n    carry(1) = (x01 and y01) or ((x01 xor y01) and carry(0))\n\n  \"x02 xor y02 -\u003e gcd\",\n  \"gcd xor vbb -\u003e z02\",\n]\n\nIf I can figure out what the carry outputs are then maybe I can do this by induction?\n\nz03 = [\n  \"x00 and y00 -\u003e sgv\",\n  \"y01 xor x01 -\u003e ntn\",\n  \"sgv and ntn -\u003e rsk\",\n\n  \"y01 and x01 -\u003e gmn\",\n  \"rsk or gmn -\u003e vbb\", carry(1)\n\n  \"y02 and x02 -\u003e jtv\",\n  \"x02 xor y02 -\u003e gcd\",\n  \"vbb and gcd -\u003e mpg\",\n  \"mpg or jtv -\u003e jmc\", carry(2)\n\n  \"y03 xor x03 -\u003e qdj\"\n  \"jmc xor qdj -\u003e z03\",\n]\n\nEach zNN should be a XOR. These are the exceptions:\n\n{10, {:and, \"mvs\", \"jvj\"}}\n{14, {:or, \"vjh\", \"fhq\"}}\n{34, {:and, \"y34\", \"x34\"}}\n\nmqk OR vbs -\u003e mvs\ny10 XOR x10 -\u003e jvj\nmvs AND jvj -\u003e z10\n\nShould this be the z10?\njvj XOR mvs -\u003e mkk\n\ncarry(N) should involve only x(N), y(N) and carry(N-1).\nThis is a pretty strong constraint! And it's easy to check that it's valid since there are only 8 possibilities to try.\n\nSwap 1: z10 \u003c-\u003e mkk\n\nz14 is wrong.\n\nvjh OR fhq -\u003e z14\nx14 XOR y14 -\u003e tsp\nndm XOR tsp -\u003e qbw\n\nz25 is wrong. And this one is going to be harder to fix.\n\ncvp XOR fqv -\u003e z25\nx25 AND y25 -\u003e cvp\njfm OR mcr -\u003e fqv\nx25 XOR y25 -\u003e wjb\nqcn OR wjb -\u003e fdg\n\nThe swap is one level down: cvp \u003c-\u003e wjb\n\ny34 AND x34 -\u003e z34\ny34 XOR x34 -\u003e jmq\njmq XOR mdh -\u003e wcb\n\nAnd this last one is straightforward. Woo!\n\n## Day 25\n\n250 keys, 250 locks. Should be straightforward.\n\n## Things to try in Elixir before this is done\n\n- Learn about macros.\n- Read the PDF about the type system.\n- Write a unit test\n\n## Misc Elixir notes\n\nParentheses are optional around function calls:\n\n```elixir\n\u003e xs = [1, 2, 3]\n\u003e Enum.sum(xs)\n6\n\u003e Enum.sum xs\n6\n```\n\nElixir makes a big deal of its built-in constructs being possible to implement in the language itself, and I guess this is part of what makes that work. For example:\n\n```elixir\niex(4)\u003e quote do\n...(4)\u003e   for x \u003c- xs, x * x\n...(4)\u003e end\n{:for, [],\n [\n   {:\u003c-, [], [{:x, [], Elixir}, {:xs, [], Elixir}]},\n   {:*, [context: Elixir, imports: [{2, Kernel}]],\n    [{:x, [], Elixir}, {:x, [], Elixir}]}\n ]}\n```\n\nCan `|\u003e` be implemented via a macro?\n\n```elixir\niex(1)\u003e quote do\n...(1)\u003e   [1, 2, 3] |\u003e Enum.sum()\n...(1)\u003e end\n{:|\u003e, [context: Elixir, imports: [{2, Kernel}]],\n [\n   [1, 2, 3],\n   {{:., [], [{:__aliases__, [alias: false], [:Enum]}, :sum]}, [], []}\n ]}\n ```\n\nI think it is:\nhttps://github.com/elixir-lang/elixir/blob/v1.18.0/lib/elixir/lib/kernel.ex#L4357\n\nThis is an interesting example of a macro for `regex_case`:\nhttps://stackoverflow.com/a/34692927/388951\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanvk%2Faoc2024","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdanvk%2Faoc2024","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanvk%2Faoc2024/lists"}