├── .gitignore ├── 22.jpg ├── README.markdown ├── advent-prelude.noul ├── advent.noul ├── env.fish ├── p1.noul ├── p10.noul ├── p11.noul ├── p12.noul ├── p13.noul ├── p14-fast.noul ├── p14.noul ├── p15.noul ├── p16.noul ├── p17.noul ├── p18.noul ├── p19.noul ├── p2.noul ├── p20.noul ├── p21.noul ├── p22.noul ├── p23.noul ├── p24.noul ├── p25.noul ├── p3.noul ├── p4.noul ├── p5-handsfree.noul ├── p5.noul ├── p6.noul ├── p7.noul ├── p8.noul ├── p9-post.noul └── p9.noul /.gitignore: -------------------------------------------------------------------------------- 1 | session 2 | advent.log 3 | *.in 4 | -------------------------------------------------------------------------------- /22.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betaveros/advent-of-code-2022/91165bf29cad71085561c07edec0dc77eac866d4/22.jpg -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # advent of code 2022 2 | 3 | **note:** on 12-13 i rewrote this repo's entire git history to [remove input files](https://www.reddit.com/r/adventofcode/wiki/faqs/copyright/inputs/); update your remotes 4 | 5 | what? for the first time ever, the advent of code solutions i'm actually competing with, in [noulith](https://github.com/betaveros/noulith/), a programming language that [got into my head this year](https://tvtropes.org/pmwiki/pmwiki.php/Main/IJustWriteTheThing). (i'm not going for perfect "authenticity" and will polish my solutions a bit between submitting and pushing.) that repo's README has some documentation, plus i did the [2016 puzzles](https://github.com/betaveros/advent-of-code-2016) a few days ago if you want more code samples. 6 | 7 | how? you can build noulith with the full feature flags `cargo build --release --features cli,request,crypto` as you would any other rust project, put your session cookie from the advent of code website (a length-128 hexadecimal string) into a file called `session`, then run any day's program. or if you don't trust this with your session cookie, you can just make an empty file called `session`, put your input into `p$DAY.in`, and see what it tries to submit; it will ask for confirmation via stdin/stdout first. (i will try not to make breaking changes to the noulith interpreter this month, but can't guarantee they won't happen) for more realism, use [fish shell](https://fishshell.com/) and source `env.fish` before each day. 8 | 9 | why? why not? 10 | 11 | note: my goal this year is firstly to finish all puzzles using only noulith, and only secondly to go for the leaderboard (if that?); if this language isn't performant enough or lacks some feature needed to solve some day's puzzle within a competitive timeframe, oh well. 12 | 13 | note: i don't really anticipate anybody actually trying to learn or run noulith programs, or i'd write more. if you actually try to do any of this and have questions, run into problems, or just generally think more documentation would help, feel free to open an issue or message me on any of the 200 social media platforms i'm on 14 | -------------------------------------------------------------------------------- /advent-prelude.noul: -------------------------------------------------------------------------------- 1 | year := 2022; 2 | 3 | bold := \text -> F"\x1b[1m{text}\x1b[0m"; 4 | bold_red := \text -> F"\x1b[1;31m{text}\x1b[0m"; 5 | bold_green := \text -> F"\x1b[1;32m{text}\x1b[0m"; 6 | bold_yellow := \text -> F"\x1b[1;33m{text}\x1b[0m"; 7 | 8 | advent_session := try 9 | read_file("session") then strip 10 | catch e -> 11 | throw F"couldn't get session: {e}. do you have a session file?"; 12 | try day catch _ -> throw "Please declare day first! (lol what a hack)"; 13 | advent_input := \-> ( 14 | if ("clipboard" in argv) ( 15 | print! bold_red("USING CLIPBOARD"); 16 | utf8_decode! run_process! 'xclip', '-selection c -out'.words 17 | ) else switch (read_file? F"p{day}.in") 18 | case null -> ( 19 | print "requesting..."; 20 | t := request! 21 | F"https://adventofcode.com/{year}/day/{day}/input", 22 | { 23 | "headers": { 24 | "Cookie": F"session={advent_session}", 25 | "User-Agent": "betaveros's AoC noulith library https://github.com/betaveros/advent-of-code-2022", 26 | } 27 | }; 28 | write_file(F"p{day}.in")(t); 29 | print "requested"; 30 | t 31 | ) 32 | case s -> (print "cached"; s) 33 | ); 34 | submit := \level, answer -> ( 35 | if ("clipboard" in argv) print! bold_red("SUBMITTING FROM CLIPBOARD"); 36 | echo! F"Day {day} level {level} solving: {bold_green(answer)} [y/N/2]? "; 37 | flush(); 38 | response := input(); 39 | while (response starts_with "2") ( 40 | level = 2; 41 | echo F"Level 2 solving: {answer}? "; 42 | flush(); 43 | response = input(); 44 | ); 45 | if (not! response.upper starts_with 'Y') ( 46 | print "Stopping"; 47 | return; 48 | ); 49 | url := F"https://adventofcode.com/{year}/day/{day}/answer"; 50 | args := { 51 | "method": "POST", 52 | "headers": { 53 | "Cookie": F"session={advent_session}", 54 | "User-Agent": "betaveros's AoC noulith library https://github.com/betaveros/advent-of-code-2022" 55 | }, 56 | "form": {"answer": answer, "level": $level}, 57 | }; 58 | # print! url, args; 59 | resp := request! url, args; 60 | append_file("advent.log")(resp); 61 | for (line <- lines resp; if "answer" in line or "level" in line) print line; 62 | throw "Finished" 63 | ); 64 | 65 | ints := \s -> ( 66 | if (s search R"\d-\d") throw "dangerous!" else s search_all R"-?\d+" map int 67 | ); 68 | nn_ints := \s -> s search_all R"\d+" map int; 69 | signed_ints := \s -> s search_all R"-?\d+" map int; 70 | 71 | four_adjacencies := \[i, j] -> [V(i-1, j), V(i, j-1), V(i, j+1), V(i+1, j)]; 72 | five_adjacencies := \[i, j] -> [V(i-1, j), V(i, j-1), V(i, j), V(i, j+1), V(i+1, j)]; 73 | nine_adjacencies := \[i, j] -> ((i-1) to (i+1)) ** ((j-1) to (j+1)) map vector; 74 | eight_adjacencies := freeze \p -> nine_adjacencies(p) filter (!= p); 75 | 76 | !!! := \arr, ixs -> ixs fold !! from arr; 77 | !!!["precedence"] = !!["precedence"]; 78 | !!? := \arr, ixs -> ixs fold !? from arr; 79 | !!?["precedence"] = !?["precedence"]; 80 | 81 | # for (r, c) or (x, y) 82 | rotated_left := \[i, j] -> V(-j, i); 83 | rotated_right := \[i, j] -> V(j, -i); 84 | 85 | digits := \n -> str(n) map int; 86 | 87 | caesar_shift_one := \c, n -> 88 | if ('a' <= c <= 'z') chr(ord('a') + (ord(c) - ord('a') + n) %% 26) 89 | else if ('A' <= c <= 'Z') chr(ord('A') + (ord(c) - ord('A') + n) %% 26) 90 | else c; 91 | caesar_shift := \s, n -> s map (_ caesar_shift_one n) join ""; 92 | 93 | struct Deque (dq_buffer, dq_front_index, dq_back_index); 94 | # Equal when empty. Else, "front < back" (in the modulo universe) 95 | # For simplicity we never completely fill the buffer. It's fine. 96 | 97 | # It's probably a bug that we don't yet have a way to define constructors, but 98 | # hilariously I think our functions almost coincidentally correctly accept 99 | # (null, null, null) as a valid empty state. 100 | 101 | dq_len := freeze \dq: Deque -> if (dq_back_index(dq) == null) 0 else 102 | (dq_back_index(dq) - dq_front_index(dq)) %% len(dq_buffer(dq)); 103 | 104 | dq_to_list := freeze \dq: Deque -> ( 105 | ret := []; 106 | i := dq[dq_front_index]; 107 | while (i != dq[dq_back_index]) ( 108 | ret append= consume dq[dq_buffer][i]; 109 | i = (i + 1) % len(dq[dq_buffer]); 110 | ); 111 | ret 112 | ); 113 | 114 | dq_make_space := freeze \dq: Deque -> ( 115 | switch (dq[dq_buffer]) 116 | case null or [] -> Deque([null, null], 0, 0) 117 | case _ -> if ((dq[dq_back_index] + 1) % len(dq[dq_buffer]) == dq[dq_front_index]) ( 118 | print("Expanding"); 119 | contents := dq_to_list(consume dq); 120 | Deque(contents ++ (null .* (2 + len(contents))), 0, len(contents)) 121 | ) else dq 122 | ); 123 | 124 | dq_push_back := freeze \dq: Deque, e -> ( 125 | dq .= dq_make_space; 126 | next_back := (dq[dq_back_index] + 1) % len(dq[dq_buffer]); 127 | dq[dq_buffer][dq[dq_back_index]] = e; 128 | dq[dq_back_index] = next_back; 129 | # print("pushed", dq); 130 | dq 131 | ); 132 | 133 | dq_push_front := freeze \dq: Deque, e -> ( 134 | dq .= dq_make_space; 135 | next_front := (dq[dq_front_index] - 1) %% len(dq[dq_buffer]); 136 | dq[dq_front_index] = next_front; 137 | dq[dq_buffer][dq[dq_front_index]] = e; 138 | # print("pushed", dq); 139 | dq 140 | ); 141 | 142 | dq_pop_front := freeze \dq: Deque -> ( 143 | if (dq[dq_front_index] == dq[dq_back_index]) throw "deque empty"; 144 | ret := consume dq[dq_buffer][dq[dq_front_index]]; 145 | dq[dq_front_index] = (dq[dq_front_index] + 1) % len(dq[dq_buffer]); 146 | [dq, ret] 147 | ); 148 | 149 | dq_pop_back := freeze \dq: Deque -> ( 150 | if (dq[dq_front_index] == dq[dq_back_index]) throw "deque empty"; 151 | dq[dq_back_index] = (dq[dq_back_index] - 1) %% len(dq[dq_buffer]); 152 | ret := consume dq[dq_buffer][dq[dq_back_index]]; 153 | [dq, ret] 154 | ); 155 | 156 | -------------------------------------------------------------------------------- /advent.noul: -------------------------------------------------------------------------------- 1 | day := now(-5)["day"]; 2 | import "advent-prelude.noul"; 3 | 4 | # dim := \text -> F"\x1b[38;5;242m{text}\x1b[0m"; 5 | 6 | stats := \text -> ( 7 | lns := lines text; 8 | lens := sort! lns map len; 9 | s := F"{len(text)} chars in {len(words text)} words over {len(lns)} lines"; 10 | if (lens) s $= F" med {lens[lens//2]} max {lens[-1]}"; 11 | s 12 | ); 13 | 14 | summarize := \text -> ( 15 | if (len(text) <= 60) 16 | print(text) 17 | else 18 | print(F"{bold_red(text[:30])}[... {len(text) - 60} chars ...]{bold_red(text[-30:])}") 19 | ); 20 | 21 | switch (argv) 22 | case [] -> print("Hello!") 23 | case ["day"] -> print(day) 24 | case ["wait"] -> ( 25 | while (1) ( 26 | t := now(-5); 27 | print(F"{t['hour']#02}:{t['minute']#02}:{t['second']#02}:{t['nanosecond']#09}"); 28 | if (t['hour'] == 0) break; 29 | sleep(1.337) 30 | ); 31 | day = now(-5)["day"]; # (!!) 32 | summarize(advent_input()); 33 | ) 34 | case a -> throw F"Unsupported: {a}" 35 | -------------------------------------------------------------------------------- /env.fish: -------------------------------------------------------------------------------- 1 | function wait 2 | noulith advent.noul wait 3 | end 4 | function go 5 | noulith p(noulith advent.noul day).noul $argv 6 | end 7 | function clip 8 | noulith p(noulith advent.noul day).noul clipboard $argv 9 | end 10 | function do 11 | noulith p(noulith advent.noul day).noul $argv 12 | end 13 | 14 | functions -c fish_prompt _old_fish_prompt 15 | function fish_prompt 16 | # Save the current $status, for fish_prompts that display it. 17 | set -l old_status $status 18 | 19 | printf '%s(advent 2022) ' (set_color normal) 20 | 21 | # Restore the original $status 22 | echo "exit $old_status" | source 23 | _old_fish_prompt 24 | end 25 | function deactivate 26 | functions -e fish_prompt 27 | functions -c _old_fish_prompt fish_prompt 28 | functions -e _old_fish_prompt 29 | end 30 | function advent 31 | noulith advent.noul $argv 32 | end 33 | -------------------------------------------------------------------------------- /p1.noul: -------------------------------------------------------------------------------- 1 | day := 1; 2 | import "advent-prelude.noul"; 3 | 4 | puzzle_input := advent_input(); 5 | 6 | submit! 1, puzzle_input split "\n\n" map ints map sum then max; 7 | submit! 2, puzzle_input split "\n\n" map ints map sum then sort then (_[-3:]) then sum; 8 | -------------------------------------------------------------------------------- /p10.noul: -------------------------------------------------------------------------------- 1 | day := 10; 2 | import "advent-prelude.noul"; 3 | 4 | puzzle_input := advent_input(); 5 | 6 | increases := puzzle_input.lines map words flat_map \switch 7 | case "noop", -> [0] 8 | case "addx", v -> [0, int(v)]; 9 | vals := increases scan + from 1; 10 | 11 | submit! 1, 20 to 220 by 40 map (\x -> vals[x-1] * x) then sum; 12 | 13 | # depicts the part 2 answer; type it in yourself 14 | vals group 40 15 | map (_ zip (0 to 39) with (\a, b -> abs(a-b) <= 1)) 16 | map (map (" #"!!)) 17 | map (join "") 18 | each print; 19 | -------------------------------------------------------------------------------- /p11.noul: -------------------------------------------------------------------------------- 1 | day := 11; 2 | import "advent-prelude.noul"; 3 | 4 | puzzle_input := advent_input(); 5 | 6 | monkeys_init := puzzle_input split "\n\n" map \par -> ( 7 | _name, starter, op, test, t_target, f_target := par.lines; 8 | starter .= ints; 9 | op = eval F"\\old -> {op split '=' then last}"; 10 | test, = ints test; 11 | t_target, = ints t_target; 12 | f_target, = ints f_target; 13 | [starter, op, test, t_target, f_target, 0 #(number of inspections)] 14 | ); 15 | 16 | for (part, rounds <- [[1, 20], [2, 10000]]) ( 17 | monkeys := monkeys_init; 18 | modulus := monkeys map (!!2) then product; 19 | 20 | for (itc <- 1 to rounds; i <- 0 til len(monkeys)) ( 21 | its, op, test, t_target, f_target, activity := monkeys[i]; 22 | for (it <- its) ( 23 | new := switch (part) 24 | case 1 -> op(it) // 3 25 | case 2 -> op(it) % modulus; 26 | if (new % test == 0) ( 27 | monkeys[t_target][0] append= new; 28 | ) else ( 29 | monkeys[f_target][0] append= new; 30 | ) 31 | ); 32 | monkeys[i][0] = []; 33 | monkeys[i][-1] += len(its); 34 | ); 35 | 36 | submit! part, sort(monkeys map last)[-2:] then product 37 | ) 38 | -------------------------------------------------------------------------------- /p12.noul: -------------------------------------------------------------------------------- 1 | day := 12; 2 | import "advent-prelude.noul"; 3 | 4 | puzzle_input := advent_input(); 5 | 6 | grid := puzzle_input.lines; 7 | 8 | get_height := \p -> switch (grid !!? p) 9 | case 'S' -> 'a' 10 | case 'E' -> 'z' 11 | case c -> c; 12 | transitions := \st -> four_adjacencies(st) filter \st' -> ( 13 | g := get_height st'; 14 | g != null and ord(g) - ord(get_height st) >= (-1) 15 | ); 16 | 17 | start := null; 18 | end := null; 19 | for (i, row <<- grid; j, cell <<- row) ( 20 | if (cell == 'S') (start = V(i, j)); 21 | if (cell == 'E') (end = V(i, j)); 22 | ); 23 | 24 | # as you can imagine, when submitting part 1 the transition height check was 25 | # flipped, and there was no try/catch machinery. I was planning to add a 26 | # labeled break at some point... 27 | for (part <- [1, 2]) try 28 | seen := {end}; 29 | dist := 0; 30 | batch := [end]; 31 | next_batch := []; 32 | while (batch) ( 33 | for (st <- batch; t <- transitions(st); if t not_in seen) ( 34 | switch (part) 35 | case 1 -> if (t == start) ( 36 | submit! 1, dist + 1; throw "done" 37 | ) 38 | case 2 -> if (get_height(t) == 'a') ( 39 | submit! 2, dist + 1; throw "done" 40 | ); 41 | seen |.= t; 42 | next_batch append= t; 43 | ); 44 | batch = next_batch; 45 | next_batch = []; 46 | dist += 1; 47 | ); 48 | catch "done" -> null 49 | -------------------------------------------------------------------------------- /p13.noul: -------------------------------------------------------------------------------- 1 | day := 13; 2 | import "advent-prelude.noul"; 3 | 4 | puzzle_input := advent_input(); 5 | 6 | cmp := \a, b -> switch ([a, b]) 7 | case a: int, b: int -> a <=> b 8 | case a: list, b: int -> a cmp [b] 9 | case a: int, b: list -> [a] cmp b 10 | case [], [] -> 0 11 | case [], _: list -> -1 12 | case _: list, [] -> 1 13 | case [a, ...as], [b, ...bs] -> a cmp b or as cmp bs; 14 | 15 | submit! 1, puzzle_input split "\n\n" then enumerate sum \[i, p] -> ( 16 | a, b := p.lines; 17 | a .= eval; 18 | b .= eval; 19 | if ((a cmp b) < 0) i + 1 else 0 20 | ); 21 | 22 | sentinels := [[[2]], [[6]]]; 23 | res := puzzle_input.lines filter id map eval then (++ sentinels) sort cmp; 24 | submit! 2, sentinels map (res locate) product (+1); 25 | -------------------------------------------------------------------------------- /p14-fast.noul: -------------------------------------------------------------------------------- 1 | day := 14; 2 | import "advent-prelude.noul"; 3 | 4 | puzzle_input := advent_input(); 5 | 6 | orig_grid := puzzle_input.lines flat_map (_ then ints group 2 map vector pairwise (\a, b -> ( 7 | (a iterate (+(signum(b-a))) take (!= b)) +. b 8 | )) then flatten) then set; 9 | 10 | max_y := (orig_grid map second then max) + 2; 11 | 12 | grid := orig_grid; 13 | cnt := 0; 14 | dfs := \v -> v in grid or v[1] < max_y and ( 15 | blocked := dfs(v + V(0, 1)) and dfs(v + V(-1, 1)) and dfs(v + V(1, 1)); 16 | if (blocked) (grid |.= v; cnt += 1); 17 | blocked 18 | ); 19 | 20 | # part 1 21 | dfs(V(500, 0)); 22 | submit! 1, cnt; 23 | 24 | # part 2 25 | grid = orig_grid || ((-1000) to 1000 map (\x -> V(x, max_y)) then set); 26 | cnt = 0; 27 | dfs(V(500, 0)); 28 | submit! 2, cnt; 29 | -------------------------------------------------------------------------------- /p14.noul: -------------------------------------------------------------------------------- 1 | day := 14; 2 | import "advent-prelude.noul"; 3 | 4 | puzzle_input := advent_input(); 5 | 6 | orig_grid := {}; 7 | 8 | for (line <- puzzle_input.lines) ( 9 | ints(line) group 2 pairwise \a, b -> ( 10 | a .= vector; 11 | b .= vector; 12 | d := signum(b - a); 13 | while (1) ( 14 | orig_grid[a] = '#'; 15 | if (a == b) break; 16 | a += d; 17 | )); 18 | ); 19 | 20 | for (part <- [1, 2]) try 21 | grid := orig_grid; 22 | if (part == 2) ( 23 | max_y := (grid map second then max) + 2; 24 | for (x <- (-1000) to 1000) grid[V(x, max_y)] = '#'; 25 | ); 26 | 27 | for (cnt <- iota 0) ( 28 | cur := V(500, 0); 29 | if (part == 2 and cur in grid) (submit! 2, cnt; throw "done"); 30 | while (1) ( 31 | if (part == 1 and cur[1] >= 1000) (submit! 1, cnt; throw "done"); 32 | nxt := null; 33 | for (d <- [V(0, 1), V(-1, 1), V(1, 1)]) ( 34 | if ((cur + d) not_in grid) (nxt = cur + d; break) 35 | ); 36 | if (nxt == null) (grid[cur] = 'o'; break) 37 | else (cur = nxt); 38 | ); 39 | ) 40 | catch "done" -> null 41 | -------------------------------------------------------------------------------- /p15.noul: -------------------------------------------------------------------------------- 1 | day := 15; 2 | import "advent-prelude.noul"; 3 | 4 | puzzle_input := advent_input(); 5 | 6 | beacons := puzzle_input.lines map ints map \[a, b, c, d] -> [V(a, b), V(c, d)]; 7 | 8 | dist := \p, q -> (p - q) map abs then sum; 9 | 10 | events := []; 11 | target_y := 2000000; 12 | ans := 0; 13 | evil := {}; 14 | 15 | for (sensor, closest <- beacons) ( 16 | d := sensor dist closest; 17 | a := abs(sensor[1] - target_y); 18 | if (d - a >= 0) ( 19 | # Ban sensor[0] ± (d - a) inclusive. 20 | events +.= [sensor[0] - (d - a), -1]; 21 | events +.= [sensor[0] + (d - a) + 1, 1]; 22 | ); 23 | if (closest[1] == target_y) evil |.= closest[0]; 24 | ); 25 | 26 | events .= sort; 27 | 28 | depth := 0; 29 | last_x := -(10^10); 30 | for (x, d <- events) ( 31 | if (depth < 0) ans += x - last_x; 32 | depth += d; 33 | last_x = x; 34 | ); 35 | submit! 1, ans - len(evil); 36 | 37 | try for ([s1, c1], [s2, c2] <- beacons combinations 2) ( 38 | d1 := (s1 dist c1) + 1; 39 | d2 := (s2 dist c2) + 1; 40 | x1, y1 := s1; 41 | x2, y2 := s2; 42 | # Basically since the point we're looking for is unique, it has to be at 43 | # the intersection of the boundaries of squares. So compute that for every 44 | # pair of squares and verify if each candidate actually satisfies the 45 | # problem condition. Don't think too hard about avoiding spurious 46 | # solutions, because checking is cheap enough that a few extra factors of 2 47 | # don't matter. 48 | # |x - x1| + |y - y1| = d1 49 | # |x - x2| + |y - y2| = d2 50 | # 51 | # x - x1 = ±d1 ± (y - y1) 52 | # x - x2 = ±d2 ± (y - y2) 53 | # if this has a "unique" solution, the x signs and y signs must be 54 | # opposite, so: 55 | # 2x - x1 - x2 = ±d1 ± d2 ± (y - y1) ∓ (y - y2) 56 | # x = (x1 + x2 ±d1 ± d2 ∓ y1 ± y2)/2 57 | for ( 58 | xx1 <- [d1, -d1]; 59 | xx2 <- [d2, -d2]; 60 | xx3 <- [y1 - y2, y2 - y1]; 61 | xx := x1 + x2 + xx1 + xx2 + xx3; 62 | yy1 <- [d1, -d1]; 63 | yy2 <- [d2, -d2]; 64 | yy3 <- [x1 - x2, x2 - x1]; 65 | yy := y1 + y2 + yy1 + yy2 + yy3) ( 66 | p := V(xx//2, yy//2); 67 | if (p all (0 <= _ <= 4000000) and beacons all \[s, c] -> (s dist p) > (s dist c)) ( 68 | submit! 2, p[0] * 4000000 + p[1]; 69 | throw "done"; 70 | ) 71 | ) 72 | ) catch "done" -> null; 73 | -------------------------------------------------------------------------------- /p16.noul: -------------------------------------------------------------------------------- 1 | day := 16; 2 | import "advent-prelude.noul"; 3 | names := []; 4 | flows := []; 5 | conns := []; 6 | for (line <- advent_input().lines) ( 7 | names append= (line split ' has ')[0][-2:]; 8 | flows append= line.ints.only; 9 | conns append= (line split ' valve')[1][1:].strip split ', '; 10 | ); 11 | 12 | dist := 99 .* len(names) .* len(names); 13 | for (i, cl <<- conns) ( 14 | for (conn <- cl) ( 15 | dist[i][names locate conn] = 1; 16 | ) 17 | ); 18 | 19 | # Floyd-Warshall 20 | for (k <- 0 til len(names); i <- 0 til len(names); j <- 0 til len(names)) 21 | dist[i][j] min= dist[i][k] + dist[k][j]; 22 | 23 | choose_one := \xs -> for (i <- 0 til len(xs)) yield [xs[i], xs[:i] ++ xs[i+1:]]; 24 | 25 | # This memoize actually pulls its weight! 26 | dfs := memoize \cur: int, rest: list, t: int -> 27 | max! (for (r, rr <- choose_one(rest); if dist[cur][r] < t) yield 28 | flows[r] * (t - dist[cur][r] - 1) + dfs(r, rr, t - dist[cur][r] - 1)) 29 | +. 0; 30 | 31 | dfs2 := \cur: int, rest: list, t: int -> 32 | max! (for (r, rr <- choose_one(rest); if dist[cur][r] < t) yield 33 | flows[r] * (t - dist[cur][r] - 1) + dfs2(r, rr, t - dist[cur][r] - 1)) 34 | +. dfs(names locate "AA", rest, 26); 35 | 36 | # t := time(); 37 | submit! 1, dfs(names locate "AA", enumerate(flows) filter (\[i, f] -> f > 0) map first, 30); 38 | # print(F"Part 1: {time() - t}"); 39 | # t = time(); 40 | submit! 2, dfs2(names locate "AA", enumerate(flows) filter (\[i, f] -> f > 0) map first, 26); 41 | # print(F"Part 2: {time() - t}"); 42 | -------------------------------------------------------------------------------- /p17.noul: -------------------------------------------------------------------------------- 1 | day := 17; 2 | import "advent-prelude.noul"; 3 | 4 | puzzle_input := advent_input(); 5 | shapes := "#### 6 | 7 | .#. 8 | ### 9 | .#. 10 | 11 | ..# 12 | ..# 13 | ### 14 | 15 | # 16 | # 17 | # 18 | # 19 | 20 | ## 21 | ##" split "\n\n" map \rep -> for (i, line <<- rep.lines; j, c <<- line; if c == '#') yield V(i, j); 22 | 23 | puzzle_input .= strip; 24 | for (part, rock_count <- [[1, 2022], [2, 1000000000000]]) ( 25 | occupied := {}; # set of occupied row, col pairs 26 | # we use a cursed coordinate system: 27 | # - the floor is the line row=0 28 | # - the open columns are 0 <= col < 7 29 | # - rows go down, so every row coordinate in occupied will be negative 30 | r_mins := []; 31 | drift_i := 0; 32 | in_bounds := \v: vector -> 0 <= v[1] < 7 and v[0] < 0 and v not_in occupied; 33 | occupied_r_min := 0; 34 | seen := {}; 35 | shape_i_target := rock_count - 1; 36 | bonus := 0; 37 | try for (shape_i, shape <<- cycle(shapes)) ( 38 | r_max := shape map first then max; 39 | # bottom edge 3 units from top existing edge means bottom rock 4 units 40 | # above top existing rock 41 | shape = shape map (+ V((-r_max) - 4 + occupied_r_min, 2)); 42 | while (1) ( 43 | drift := switch (puzzle_input !% drift_i) 44 | case "<" -> V(0,-1) 45 | case ">" -> V(0,1); 46 | if (shape map (+ drift) all in_bounds) shape .= map (+ drift); 47 | drift_i += 1; 48 | if (shape map (+ V(1,0)) all in_bounds) ( 49 | shape .= map (+ V(1,0)) 50 | ) else ( 51 | # assert! not! occupied && set(shape); 52 | occupied ||= set(shape); 53 | occupied_r_min min= min(shape map first); 54 | r_mins append= occupied_r_min; 55 | if (shape_i == shape_i_target) ( 56 | submit! part, bonus - occupied_r_min; 57 | throw "done" 58 | ); 59 | # construct a crude approximation of the board state and history of 60 | # rocks/drifts, such that when it repeats we've found a period 61 | memkey := [ 62 | shape_i % len(shapes), 63 | drift_i % len(puzzle_input), 64 | r_mins[-6:] pairwise - 65 | ]; 66 | # have we seen it before? 67 | switch (seen !? memkey) 68 | case null -> ( 69 | # we have not seen it before 70 | seen[memkey] = [shape_i, r_mins[-1]] 71 | ) 72 | case [shape_i', old_ans] -> ( 73 | # we have seen it before 74 | if (shape_i_target == rock_count - 1) ( 75 | # and have not seen any repetitions before, so just 76 | # calculate the next time we'll hit the point in the 77 | # period corresponding to the goal, wait til we get 78 | # there, and then be equipped to calculate the answer. 79 | # (or, in hindsight, we could have just looked it up in 80 | # r_mins...) 81 | period := shape_i - shape_i'; 82 | # I have little confidence there aren't some off-by-one 83 | # errors here but ehhhh 84 | bonus = ((-(r_mins[-1])) - (-old_ans)) * 85 | ((rock_count - shape_i) // period); 86 | shape_i_target = shape_i + (rock_count - 1 - shape_i) % period; 87 | ) 88 | ); 89 | break; 90 | ) 91 | ) 92 | ) catch "done" -> null 93 | ) 94 | -------------------------------------------------------------------------------- /p18.noul: -------------------------------------------------------------------------------- 1 | day := 18; 2 | import "advent-prelude.noul"; 3 | 4 | puzzle_input := advent_input(); 5 | 6 | objs := puzzle_input.lines map ints map vector then set; 7 | 8 | dirs := [ 9 | V(0,0,1), 10 | V(0,0,-1), 11 | V(0,1,0), 12 | V(0,-1,0), 13 | V(1,0,0), 14 | V(-1,0,0), 15 | ]; 16 | 17 | submit! 1, objs ** dirs map (apply +) count (not_in objs); 18 | 19 | xs := objs map first; 20 | ys := objs map second; 21 | zs := objs map last; 22 | 23 | lb := V(min(xs) - 1, min(ys) - 1, min(zs) - 1); 24 | ub := V(max(xs) + 1, max(ys) + 1, max(zs) + 1); 25 | 26 | in_bounds := \p -> 0 til 3 all \i -> lb[i] <= p[i] <= ub[i]; 27 | seen := {}; 28 | # Just a DFS, unrolled into a loop because Noulith stack frames are totally 29 | # unoptimized and correspond to tons of Rust stack frames, causing an overflow 30 | stack := [lb]; 31 | while (stack) ( 32 | p := pop stack; 33 | if (p not_in seen and p not_in objs and in_bounds p) ( 34 | seen |.= p; 35 | for (d <- dirs) stack append= p + d; 36 | ) 37 | ); 38 | 39 | submit! 2, objs ** dirs map (apply +) count (in seen) 40 | -------------------------------------------------------------------------------- /p19.noul: -------------------------------------------------------------------------------- 1 | day := 19; 2 | import "advent-prelude.noul"; 3 | 4 | puzzle_input := advent_input(); 5 | 6 | solve := freeze \bp_line, minutes -> ( 7 | bp_id, ore_bot_cost, clay_bot_cost, obs_bot_ore_cost, obs_bot_clay_cost, geode_bot_ore_cost, geode_bot_obs_cost := ints bp_line; 8 | max_ore_cost := max(ore_bot_cost, clay_bot_cost, obs_bot_ore_cost, geode_bot_ore_cost); 9 | print("solving", bp_id); 10 | # ore, clay, obs, geode 11 | ans := 0; 12 | dfs := \minute: int, resources: vector, bots: vector -> ( 13 | # print("dfs", minute, resources, bots); 14 | # assert! resources all (>= 0); 15 | # assert! bots all (>= 0); 16 | ans_idle := resources[3] + bots[3] * minute; 17 | if (ans_idle > ans) ( 18 | ans = ans_idle; 19 | print("- new best:", ans); 20 | ); 21 | ans_opti := ans_idle + (minute * (minute - 1) // 2); 22 | if (ans_opti <= ans) return; 23 | turns_to_do := \cost -> ( 24 | ts := resources zip bots zip cost with \r, b, c -> 25 | if (r >= c) 0 else if (b) (c - r + b - 1) // b else null; 26 | if (ts all (!= null)) max ts else null 27 | ); 28 | costs := [ 29 | [3, V(geode_bot_ore_cost, 0, geode_bot_obs_cost, 0)], 30 | [2, V(obs_bot_ore_cost, obs_bot_clay_cost, 0, 0)], 31 | [1, V(clay_bot_cost, 0, 0, 0)], 32 | [0, V(ore_bot_cost, 0, 0, 0)], 33 | ]; 34 | for (i, c <- costs) ( 35 | if (i == 0 and bots[i] >= max_ore_cost) continue; 36 | if (i == 1 and bots[i] >= obs_bot_clay_cost) continue; 37 | if (i == 2 and bots[i] >= geode_bot_obs_cost) continue; 38 | t := turns_to_do c; 39 | if (t != null and t < minute) ( 40 | bots' := bots; 41 | bots'[i] += 1; 42 | dfs(minute - t - 1, resources + (t + 1) * bots - c, bots'); 43 | ) 44 | ); 45 | ); 46 | dfs(minutes, V(0, 0, 0, 0), V(1, 0, 0, 0)); 47 | [bp_id, ans] 48 | ); 49 | 50 | submit! 1, for (bp <- puzzle_input.lines) yield solve(bp, 24) apply * into sum; 51 | 52 | submit! 2, for (bp <- puzzle_input.lines take 3) yield solve(bp, 32).second into product; 53 | -------------------------------------------------------------------------------- /p2.noul: -------------------------------------------------------------------------------- 1 | day := 2; 2 | import "advent-prelude.noul"; 3 | 4 | puzzle_input := advent_input(); 5 | 6 | ans1 := 0; 7 | ans2 := 0; 8 | for (line <- puzzle_input.lines) ( 9 | left := switch (line[0]) case "A" -> 1 case "B" -> 2 case "C" -> 3; 10 | right := switch (line[2]) case "X" -> 1 case "Y" -> 2 case "Z" -> 3; 11 | 12 | ans1 += right; 13 | 14 | if (left == right) ans1 += 3; 15 | if ((right - left) %% 3 == 1) ans1 += 6; 16 | # The neat version of the above: 17 | # ans += (right - left + 1) %% 3 * 3; 18 | 19 | ans2 += (left + right) %% 3 + 1; 20 | ans2 += (right - 1) * 3; 21 | ); 22 | submit! 1, ans1; 23 | submit! 2, ans2; 24 | -------------------------------------------------------------------------------- /p20.noul: -------------------------------------------------------------------------------- 1 | day := 20; 2 | import "advent-prelude.noul"; 3 | 4 | puzzle_input := advent_input(); 5 | 6 | # sooo slow 7 | 8 | for (part <- [1, 2]) ( 9 | iterations, xs := switch (part) 10 | case 1 -> [1, enumerate! ints(puzzle_input)] 11 | case 2 -> [10, enumerate! ints(puzzle_input) map (*811589153)]; 12 | xs' := xs; 13 | print(len(xs), xs map second map abs then sum); 14 | for (it <- 1 to iterations) ( 15 | print("iteration", it); 16 | for (ix <- xs) ( 17 | src := xs' locate ix; 18 | _, x := ix; 19 | if (x > 0) ( 20 | for (i <- 0 til (x % (len(xs)-1))) ( 21 | swap xs'[src], xs'[(src + 1) % len(xs)]; 22 | src = (src + 1) % len(xs); 23 | ) 24 | ) else if (x < 0) ( 25 | for (i <- 0 til ((-x) % (len(xs)-1))) ( 26 | swap xs'[src], xs'[(src - 1) %% len(xs)]; 27 | src = (src - 1) %% len(xs); 28 | ) 29 | ); 30 | assert! xs'[src] == ix; 31 | ) 32 | ); 33 | print("final", xs'); 34 | t := xs' locate \x -> x[1] == 0; 35 | ans := [xs' !% (t + 1000), xs' !% (t + 2000), xs' !% (t + 3000)]; 36 | 37 | print(ans); 38 | print(time()); 39 | submit! part, sum (ans map second); 40 | ) 41 | -------------------------------------------------------------------------------- /p21.noul: -------------------------------------------------------------------------------- 1 | day := 21; 2 | import "advent-prelude.noul"; 3 | 4 | puzzle_input := advent_input(); 5 | 6 | solve := \h -> ( 7 | known := {}; 8 | if (h != null) known["humn"] = h; 9 | while (1) for (line <- puzzle_input.lines) ( 10 | lhs, rhs := line split ": "; 11 | if (lhs in known) continue; 12 | switch (rhs.words) 13 | case d, -> (known[lhs] = int(d)) 14 | case a, op, b -> ( 15 | switch ([known !? a, known !? b]) 16 | case av != null, bv != null -> ( 17 | known[lhs] = eval(op)(av, bv); 18 | if (lhs == "root") return [known[lhs], av, bv]; 19 | ) 20 | case _ -> null 21 | ) 22 | ) 23 | ); 24 | 25 | submit! 1, solve(null)[0]; 26 | 27 | # binary search from bounds found manually; i'm too lazy to work out a 28 | # reasonably general way to juggle the sign possibilities (and this strategy 29 | # obviously doesn't work for general inputs anyway since rational polynomials 30 | # aren't monotonic) 31 | lo := 1000000000000; # lhs > rhs 32 | hi := 10000000000000; # lhs < rhs 33 | while (1) ( 34 | mid := (lo + hi) // 2; 35 | switch (solve(mid)[1:] apply <=>) 36 | case 0 -> (submit(2, mid); break) 37 | case 1 -> (lo = mid) 38 | case -1 -> (hi = mid) 39 | ) 40 | -------------------------------------------------------------------------------- /p22.noul: -------------------------------------------------------------------------------- 1 | day := 22; 2 | import "advent-prelude.noul"; 3 | 4 | puzzle_input := advent_input(); 5 | 6 | grid, instructions := puzzle_input split "\n\n"; 7 | grid .= lines; 8 | instructions .= strip; 9 | 10 | locate_last := \x, f -> for (i <- (len(x) - 1) to 0 by (-1)) if (f(x[i])) return i; 11 | 12 | # Portals are represented by the coordinates of the upper-left corner and then 13 | # whether it's horizontal or vertical. The third item represents whether the 14 | # coordinates along the two portals are flipped (1 if the top or left edge of 15 | # each portal matches the other, -1 if not); the fourth item represents whether 16 | # the directions into the portals are flipped (1 if going right/down into one 17 | # portal is the same as going right/down into the other, -1 if not). There is 18 | # redundancy in these columns but it's too hard to reason about confidently 19 | # :zzz: 20 | portals := [ 21 | [[0, 50, "H"], [150, 0, "V"], 1, -1], 22 | [[0, 100, "H"], [200, 0, "H"], 1, 1], 23 | [[0, 50, "V"], [100, 0, "V"], -1, -1], 24 | [[50, 50, "V"], [100, 0, "H"], 1, -1], 25 | [[0, 150, "V"], [100, 100, "V"], -1, -1], 26 | [[50, 100, "H"], [50, 100, "V"], 1, -1], 27 | [[150, 50, "H"], [150, 50, "V"], 1, -1], 28 | ]; 29 | 30 | # Given a location and a direction to be traveling, does it pass through the 31 | # portal? If so, return the coordinate along the portal's dimension (satisfying 32 | # 0 <= c < 50) and whether the original location is above or to the left of the 33 | # portal. Otherwise, return null. 34 | try_pass := \portal, loc, delta -> ( 35 | switch (delta) 36 | case 0, (1 or -1) -> ( 37 | if (portal[2] == "V" and 0 <= loc[0] - portal[0] < 50 and {loc[1], (loc+delta)[1]} == {portal[1], portal[1] - 1}) 38 | [loc[0] - portal[0], loc[1] < portal[1]] 39 | else null 40 | ) 41 | case (1 or -1), 0 -> ( 42 | if (portal[2] == "H" and 0 <= loc[1] - portal[1] < 50 and {loc[0], (loc+delta)[0]} == {portal[0], portal[0] - 1}) 43 | [loc[1] - portal[1], loc[0] < portal[0]] 44 | else null 45 | ) 46 | ); 47 | 48 | # Given a portal, a coordinate along the portal's dimension (satisfying 0 <= c 49 | # < 50), and whether the desired location is above or to the left of the 50 | # portal, return the coordinates of the location next to the portal. 51 | unpass := \portal, px, is_less -> switch (portal[2]) 52 | case "V" -> V(portal[0] + px, portal[1] - is_less) 53 | case "H" -> V(portal[0] - is_less, portal[1] + px); 54 | 55 | # Given a location and a direction to be traveling that would lead off the 56 | # grid, warp it through a portal to a new location and direction. 57 | warp := \loc, delta -> ( 58 | res := for (pa, pb, m, dm <- portals; p1, p2 <- [[pa, pb], [pb, pa]]; passed := try_pass(p1, loc, delta); if passed != null) yield ( 59 | px, is_less := passed; 60 | print("is_less", is_less); 61 | assert! 0 <= px < 50; 62 | px' := if (m == (-1)) 49 - px else px; 63 | delta' := delta; 64 | if (pa[2] != pb[2]) (swap delta'[0], delta'[1]); 65 | delta' *= dm; 66 | 67 | print("is_less after", is_less xor (dm == (1))); 68 | base' := unpass(p2, px', is_less xor (dm == (1))); 69 | 70 | [base', delta'] 71 | ); 72 | print(res); 73 | only(res) 74 | ); 75 | 76 | 77 | for (part <- [1, 2]) ( 78 | loc := V(0, grid[0] locate (\x -> x != ' ' and x != '#')); 79 | delta := V(0, 1); 80 | 81 | for (instr <- instructions group (== on is_digit)) ( 82 | switch (instr) 83 | case 'L' -> (delta .= rotated_left) 84 | case 'R' -> (delta .= rotated_right) 85 | case d -> ( 86 | print(loc, delta, d); 87 | prev_loc := null; 88 | for (s <- 1 to int(d)) ( 89 | nxt := loc + delta; 90 | # print(grid, nxt); 91 | print('step', s, loc, nxt); 92 | 93 | delta' := delta; 94 | 95 | if (grid !!? nxt in [' ', null]) 96 | switch (part) 97 | case 1 -> (switch (delta) 98 | case 1, 0 -> (nxt[0] = grid locate (\x -> (x !? nxt[1]) not_in [' ', null])) 99 | case -1, 0 -> (nxt[0] = grid locate_last (\x -> (x !? nxt[1]) not_in [' ', null])) 100 | case 0, 1 -> (nxt[1] = grid[nxt[0]] locate \x -> x not_in [' ', null]) 101 | case 0, -1 -> (nxt[1] = grid[nxt[0]] locate_last \x -> x not_in [' ', null]); 102 | ) 103 | case 2 -> (nxt, delta' = warp(loc, delta)); 104 | 105 | switch (grid !!! nxt) case '#' -> break case '.' -> (loc, delta = nxt, delta') 106 | ) 107 | ); 108 | print(instr, loc, delta) 109 | ); 110 | 111 | submit! part, 1000 * (loc[0] + 1) + 4 * (loc[1] + 1) + switch (delta) 112 | case 0, 1 -> 0 113 | case 0, -1 -> 2 114 | case 1, 0 -> 1 115 | case -1, 0 -> 3; 116 | ) 117 | -------------------------------------------------------------------------------- /p23.noul: -------------------------------------------------------------------------------- 1 | day := 23; 2 | import "advent-prelude.noul"; 3 | 4 | puzzle_input := advent_input(); 5 | 6 | locs := for (i, line <<- puzzle_input.lines; j, c <<- line; if c == '#') yield V(i, j) into set; 7 | 8 | dirs := [V(-1, 0), V(1, 0), V(0, -1), V(0, 1)]; 9 | splay := memoize \dir -> [-1, 0, 1] map (\d -> dir map (\switch case 0 -> d case x -> x) to vector); 10 | splayed := \p, dir -> splay(dir) map (+p); 11 | 12 | # part 2 is really slow... I think the constant factors from a tree-walk 13 | # interpreter doing lots of nested functions just add up. the weird memoize 14 | # above helps 15 | 16 | for (i <- iota 1) ( 17 | propose := \loc -> ( 18 | if (eight_adjacencies(loc) all (not_in locs)) null 19 | else for (dir <- dirs) if (splayed(loc, dir) all (not_in locs)) return loc + dir 20 | ); 21 | proposals := locs map propose then frequencies; 22 | locs' := {}; 23 | moved := false; 24 | for (loc <- locs) ( 25 | p := propose(loc); 26 | if (p == null or proposals[p] > 1) (locs' |.= loc) 27 | else (locs' |.= p; moved = true); 28 | ); 29 | 30 | dirs = dirs[1:] +. dirs[0]; 31 | locs = locs'; 32 | if (i == 10) submit! 1, 33 | (max(locs map first) - min(locs map first) + 1) * 34 | (max(locs map second) - min(locs map second) + 1) - len(locs); 35 | if (not moved) submit! 2, i; 36 | print(i, len(locs)); 37 | ) 38 | -------------------------------------------------------------------------------- /p24.noul: -------------------------------------------------------------------------------- 1 | day := 24; 2 | import "advent-prelude.noul"; 3 | 4 | puzzle_input := advent_input(); 5 | 6 | grid := puzzle_input.lines; 7 | 8 | transitions := five_adjacencies; 9 | start := V(0, grid[0] locate "."); 10 | end := V(len(grid) - 1, grid[-1] locate "."); 11 | 12 | winds := for (i, row <<- grid; j, c <<- row; d := switch (c) 13 | case '<' -> V(0, -1) 14 | case '>' -> V(0, 1) 15 | case '^' -> V(-1, 0) 16 | case 'v' -> V( 1, 0) 17 | case _ -> null; if d) yield [V(i, j), d]; 18 | 19 | dist := 0; 20 | batch := {start: 0}; 21 | next_batch := {}; 22 | allowed := {'^', '<', '>', 'v', '.'}; 23 | submitted1 := 0; 24 | 25 | try while (batch) ( 26 | print(dist); 27 | # tick winds 28 | winds map= \[p, d] -> ( 29 | out := p + d; 30 | # print(out, grid !!! out); 31 | if (grid !!? out == '#') switch (d) 32 | case 0, -1 -> (out[1] = len(grid[0]) - 2) 33 | case 0, 1 -> (out[1] = 1) 34 | case -1, 0 -> (out[0] = len(grid) - 2) 35 | case 1, 0 -> (out[0] = 1); 36 | # print(out, grid !!! out); 37 | assert! (grid !!! out) in allowed; 38 | [out, d] 39 | ); 40 | occupied := set(winds map first); 41 | 42 | for (st, visits <<- batch; 43 | t <- transitions(st); 44 | if t not_in occupied and grid !!? t in allowed) ( 45 | 46 | if (even visits and t == end) visits += 1 47 | else if (odd visits and t == start) visits += 1; 48 | if (visits == 1 and not submitted1) ( 49 | submit! 1, dist + 1; 50 | submitted1 = true; 51 | ); 52 | if (visits == 3) ( 53 | submit! 2, dist + 1; 54 | throw "done"; 55 | ); 56 | # if (t == end) submit! 1, dist + 1; 57 | next_batch[t] = (next_batch !? t or 0) max visits; 58 | ); 59 | batch = next_batch; 60 | next_batch = {}; 61 | dist += 1; 62 | ) catch "done" -> null; 63 | -------------------------------------------------------------------------------- /p25.noul: -------------------------------------------------------------------------------- 1 | day := 25; 2 | import "advent-prelude.noul"; 3 | 4 | puzzle_input := advent_input(); 5 | 6 | to_digs := {"0": 0, "1": 1, "2": 2, "-": -1, "=": -2}; 7 | from_digs := to_digs.items map reverse then dict; 8 | 9 | from_snafu := \s -> s fold (\acc, d -> acc * 5 + to_digs[d]) from 0; 10 | to_snafu := \n -> 11 | if (not n) "" 12 | else switch (n %% 5) 13 | case 0 or 1 or 2 -> to_snafu(n // 5) $ from_digs[n %% 5] 14 | case 3 or 4 -> to_snafu(n // 5 + 1) $ from_digs[n %% 5 - 5]; 15 | 16 | submit! 1, puzzle_input.lines map from_snafu then sum then to_snafu; 17 | -------------------------------------------------------------------------------- /p3.noul: -------------------------------------------------------------------------------- 1 | day := 3; 2 | import "advent-prelude.noul"; 3 | 4 | puzzle_input := advent_input(); 5 | 6 | # considerably cleaned up 7 | score := \c -> switch (c) 8 | case 'a' <= c <= 'z' -> ord(c) - ord('a') + 1 9 | case 'A' <= c <= 'Z' -> ord(c) - ord('A') + 27; 10 | 11 | submit! 1, sum (for (line <- puzzle_input.lines) yield ( 12 | [c] := line group (len(line) // 2) map set apply &&; 13 | score(c) 14 | )); 15 | 16 | submit! 2, sum (for (lns <- puzzle_input.lines group 3) yield ( 17 | [c] := lns map set fold &&; 18 | score(c) 19 | )); 20 | 21 | # one-liner variant: 22 | submit! 2, puzzle_input.lines group 3 map (_ map set fold && apply score) then sum 23 | -------------------------------------------------------------------------------- /p4.noul: -------------------------------------------------------------------------------- 1 | day := 4; 2 | import "advent-prelude.noul"; 3 | 4 | puzzle_input := advent_input(); 5 | 6 | submit! 1, puzzle_input.lines count \line -> ( 7 | a, b, c, d := nn_ints(line); 8 | a <= c <= d <= b or c <= a <= b <= d 9 | ); 10 | 11 | submit! 2, puzzle_input.lines count \line -> ( 12 | a, b, c, d := nn_ints(line); 13 | not (b < c) and not (d < a) 14 | ); 15 | -------------------------------------------------------------------------------- /p5-handsfree.noul: -------------------------------------------------------------------------------- 1 | day := 5; 2 | import "advent-prelude.noul"; 3 | 4 | puzzle_input := read_file("p5-handsfree.in"); 5 | 6 | diag, moves := puzzle_input split "\n\n"; 7 | 8 | stacks := [] .* 10; 9 | 10 | for (part <- [1, 2]) ( 11 | for (line <- diag.lines.reverse) ( 12 | for (letter, pos <- line group 4 map second zip (iota 1)) ( 13 | if (letter != " ") stacks[pos] append= letter; 14 | ) 15 | ); 16 | 17 | # (variables cleaned up a bunch here) 18 | for (line <- moves.lines) ( 19 | cnt, src, dst := ints(line); 20 | switch (part) 21 | case 1 -> for (_ <- 1 to cnt) ( 22 | stacks[dst] append= pop stacks[src]; 23 | ) 24 | case 2 -> ( 25 | stacks[dst] ++= stacks[src][-cnt:]; 26 | stacks[src] = stacks[src][:-cnt] 27 | ) 28 | ); 29 | 30 | submit! part, stacks[1:] map last join "" 31 | ) 32 | -------------------------------------------------------------------------------- /p5.noul: -------------------------------------------------------------------------------- 1 | day := 5; 2 | import "advent-prelude.noul"; 3 | 4 | # REQUIRES MANUAL PREPROCESSING: add a single "." in each blank spot above a stack. 5 | # Sample input becomes this: 6 | # . [D] . 7 | # [N] [C] . 8 | # [Z] [M] [P] 9 | # 1 2 3 10 | # Coding under time pressure is hard, ok. 11 | # See p5-handsfree.noul to see a version that doesn't involve that. 12 | 13 | puzzle_input := advent_input(); 14 | 15 | diag, moves := puzzle_input split "\n\n"; 16 | 17 | stacks := [] .* 10; 18 | 19 | for (part <- [1, 2]) ( 20 | for (line <- diag.lines.reverse) ( 21 | # Observe that this puts the numbers 1 to 9 on the bottom on each stack. 22 | # There was absolutely no reason for me to do this, but it doesn't 23 | # hurt... 24 | for (letter, pos <- line filter (\a -> "1" <= a <= "9" or "A" <= a <= "Z" or a == ".") zip (1 to 9)) ( 25 | if (letter != ".") stacks[pos] append= letter; 26 | ) 27 | ); 28 | 29 | # (variables cleaned up a bunch here) 30 | for (line <- moves.lines) ( 31 | cnt, src, dst := ints(line); 32 | switch (part) 33 | case 1 -> for (_ <- 1 to cnt) ( 34 | stacks[dst] append= pop stacks[src]; 35 | ) 36 | case 2 -> ( 37 | stacks[dst] ++= stacks[src][-cnt:]; 38 | stacks[src] = stacks[src][:-cnt] 39 | ) 40 | ); 41 | 42 | submit! part, stacks[1:] map last join "" 43 | ) 44 | -------------------------------------------------------------------------------- /p6.noul: -------------------------------------------------------------------------------- 1 | day := 6; 2 | import "advent-prelude.noul"; 3 | 4 | puzzle_input := advent_input(); 5 | 6 | # (loop extracted after the fact) 7 | for (part, mlen <- [[1, 4], [2, 14]]) 8 | submit! part, mlen + (puzzle_input.strip window mlen locate \x -> len(x) == len(set(x))) 9 | -------------------------------------------------------------------------------- /p7.noul: -------------------------------------------------------------------------------- 1 | day := 7; 2 | import "advent-prelude.noul"; 3 | 4 | puzzle_input := advent_input(); 5 | 6 | # We assume every directory is ls'd exactly once, which just means we can 7 | # ignore ls commands and, whenever we see a file, put it in the directory we 8 | # most recently cd'd to. 9 | pwd := []; 10 | csize := {:0}; 11 | for (line <- puzzle_input.lines) ( 12 | switch (line.words) 13 | case "$", "cd", "/" -> (pwd = []) 14 | case "$", "cd", ".." -> pop pwd 15 | case "$", "cd", x -> (pwd append= x) 16 | case "$", "ls" -> null 17 | case "dir", _ -> null 18 | case size, _name -> ( 19 | for (p <- prefixes(pwd)) csize[p] += int(size) 20 | ) 21 | ); 22 | 23 | submit! 1, csize.values filter (<= 100000) then sum; 24 | 25 | # I was too paranoid that the number starting 7 would have more 0s than the 26 | # number starting 3 so I copied these constants in. Definitely nothing to do 27 | # with all mental arithmetic faculties shutting down under time pressure 28 | needed := 70000000 - 30000000; 29 | cur := csize[[]]; 30 | 31 | submit! 2, csize.values filter (>= (cur - needed)) then min 32 | -------------------------------------------------------------------------------- /p8.noul: -------------------------------------------------------------------------------- 1 | day := 8; 2 | import "advent-prelude.noul"; 3 | 4 | puzzle_input := advent_input(); 5 | 6 | grid := puzzle_input.lines map (map int); 7 | 8 | # Today's lesson in interpreter ergonomics: I need warnings when I shadow my 9 | # own variables 10 | 11 | # This is cleaned up a lot from my submission solution... 12 | 13 | rays := \i, j -> [V(0, 1), V(0, -1), V(1, 0), V(-1, 0)] map \delta -> 14 | V(i, j) iterate (+delta) drop 1 15 | # there's redundancy here but we can't map first because `map` is eager... 16 | take (\[i', j'] -> grid !? i' !? j' != null) 17 | map (\[i', j'] -> grid[i'][j']); 18 | 19 | submit! 1, sum! for (i, row <<- grid; j, here <<- row) yield 20 | # why doesn't `all` partially apply on functions...? 21 | rays(i, j) any (_ all (< here)); 22 | 23 | submit! 2, max! for (i, row <<- grid; j, here <<- row) yield 24 | product! rays(i, j) map \ray -> ( 25 | seen := 0; 26 | for (h <- ray) (seen += 1; if (h >= here) break); 27 | seen 28 | ) 29 | -------------------------------------------------------------------------------- /p9-post.noul: -------------------------------------------------------------------------------- 1 | day := 9; 2 | import "advent-prelude.noul"; 3 | 4 | puzzle_input := advent_input(); 5 | 6 | directions := { 7 | "U": V(-1,0), 8 | "R": V(0,1), 9 | "D": V(1,0), 10 | "L": V(0,-1), 11 | }; 12 | 13 | snap := \v -> if (max(abs(v)) > 1) v - signum(v) else v; 14 | 15 | for (part, knot_count <- [[1, 2], [2, 10]]) ( 16 | knots := V(0, 0) .* knot_count; 17 | seen := {last knots}; 18 | 19 | for (dir, cnt <- puzzle_input.lines map words; _ <- 1 to int(cnt)) ( 20 | knots[0] += directions[dir]; 21 | for (i <- 1 til knot_count) knots[i] = knots[i-1] + snap(knots[i] - knots[i-1]); 22 | seen |.= last knots; 23 | ); 24 | 25 | submit! part, len(seen); 26 | ) 27 | -------------------------------------------------------------------------------- /p9.noul: -------------------------------------------------------------------------------- 1 | day := 9; 2 | import "advent-prelude.noul"; 3 | 4 | puzzle_input := advent_input(); 5 | 6 | directions := { 7 | "U": V(-1,0), 8 | "R": V(0,1), 9 | "D": V(1,0), 10 | "L": V(0,-1), 11 | }; 12 | 13 | # Part 1 14 | 15 | h := V(0, 0); 16 | t := V(0, 0); 17 | seen := {t}; 18 | 19 | for (line <- puzzle_input.lines; dir, cnt' := words(line); cnt := int(cnt'); _ <- 1 to cnt) ( 20 | h += directions[dir]; 21 | td := switch (t - h) 22 | case 2, _ -> V( 1, 0) 23 | case -2, _ -> V(-1, 0) 24 | case _, 2 -> V( 0, 1) 25 | case _, -2 -> V( 0, -1) 26 | case x -> x; 27 | t = h + td; 28 | seen |.= t; 29 | ); 30 | 31 | submit! 1, len(seen); 32 | 33 | # Part 2 34 | 35 | h = V(0, 0); 36 | ts := V(0, 0) .* 9; 37 | seen = {ts[0]}; 38 | 39 | for (line <- puzzle_input.lines; dir, cnt' := words(line); cnt := int(cnt'); _ <- 1 to cnt) ( 40 | h += directions[dir]; 41 | prev := h; 42 | for (ti <- 0 to 8) ( 43 | td := switch (ts[ti] - prev) 44 | case 2, 2 -> V( 1, 1) 45 | case 2, -2 -> V( 1, -1) 46 | case -2, 2 -> V(-1, 1) 47 | case -2, -2 -> V(-1, -1) 48 | case 2, _ -> V( 1, 0) 49 | case -2, _ -> V(-1, 0) 50 | case _, 2 -> V( 0, 1) 51 | case _, -2 -> V( 0, -1) 52 | case x -> x; 53 | ts[ti] = prev + td; 54 | prev = ts[ti]; 55 | ); 56 | seen |.= last ts; 57 | ); 58 | 59 | submit! 2, len(seen); 60 | 61 | # this could look much simpler; see p9-post.noul 62 | --------------------------------------------------------------------------------