├── .gitignore ├── 7-scratch ├── main.sb3 └── README.md ├── 11-v ├── Dockerfile └── main.v ├── 14-erlang ├── Dockerfile └── main.erl ├── 15-swift ├── Dockerfile └── main.swift ├── 17-hack ├── Dockerfile └── main.hack ├── 24-ocaml ├── Dockerfile └── main.ml ├── 19-pony ├── Dockerfile └── main.pony ├── 18-inko ├── Dockerfile └── main.inko ├── 20-haxe ├── Dockerfile └── Main.hx ├── 21-gleam ├── gleam.toml ├── Dockerfile └── main.gleam ├── 25-crystal ├── Dockerfile └── main.cr ├── 5-fish ├── Dockerfile └── main.fish ├── 6-perl ├── Dockerfile └── main.perl ├── 9-fsharp ├── Dockerfile └── main.fsx ├── 13-pascal ├── Dockerfile └── main.pas ├── 23-nim ├── Dockerfile └── main.nim ├── 16-d ├── Dockerfile └── main.d ├── 10-clojure ├── Dockerfile └── main.clj ├── 22-haskell ├── Dockerfile └── main.hs ├── 3-nasm ├── Dockerfile └── main.asm ├── run ├── 8-zig ├── Dockerfile └── main.zig ├── 2-sass ├── Dockerfile └── main.scss ├── 4-psql ├── Dockerfile └── main.sql ├── 1-odin ├── Dockerfile └── main.odin ├── 12-smalltalk ├── support-musl.patch ├── Dockerfile └── main.st ├── README.md └── languages.svg /.gitignore: -------------------------------------------------------------------------------- 1 | *.txt 2 | -------------------------------------------------------------------------------- /7-scratch/main.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RuyiLi/aoc2024/HEAD/7-scratch/main.sb3 -------------------------------------------------------------------------------- /11-v/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM thevlang/vlang:alpine 2 | WORKDIR /prog 3 | 4 | COPY . . 5 | CMD ["v", "run", "main.v"] 6 | -------------------------------------------------------------------------------- /14-erlang/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM erlang:27-alpine 2 | WORKDIR /prog 3 | 4 | COPY . . 5 | CMD ["escript", "main.erl"] 6 | -------------------------------------------------------------------------------- /15-swift/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM swift:6.0.3-noble 2 | WORKDIR /prog 3 | 4 | COPY . . 5 | CMD ["swift", "main.swift"] 6 | -------------------------------------------------------------------------------- /17-hack/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM hhvm/hhvm:2023.02.17 2 | WORKDIR /prog 3 | 4 | COPY . . 5 | CMD ["hhvm", "main.hack"] 6 | -------------------------------------------------------------------------------- /24-ocaml/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ocaml/opam:alpine-ocaml-4.14 2 | WORKDIR /prog 3 | 4 | COPY . . 5 | CMD ["ocaml", "main.ml"] 6 | -------------------------------------------------------------------------------- /19-pony/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ponylang/ponyc:0.58.0-alpine 2 | WORKDIR /prog 3 | 4 | COPY . . 5 | RUN ponyc 6 | CMD ["./prog"] 7 | -------------------------------------------------------------------------------- /18-inko/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ghcr.io/inko-lang/inko:0.17.1 2 | WORKDIR /prog 3 | 4 | COPY . . 5 | CMD ["inko", "run", "main.inko"] 6 | -------------------------------------------------------------------------------- /20-haxe/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM haxe:4.3-alpine3.20 2 | WORKDIR /prog 3 | 4 | COPY . . 5 | CMD ["haxe", "-main", "Main", "--interp"] 6 | -------------------------------------------------------------------------------- /21-gleam/gleam.toml: -------------------------------------------------------------------------------- 1 | name = "main" 2 | 3 | [dependencies] 4 | gleam_stdlib = ">= 0.34.0 and < 2.0.0" 5 | gleam_erlang = "~> 0.25" 6 | -------------------------------------------------------------------------------- /25-crystal/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM crystallang/crystal:1.14-alpine 2 | WORKDIR /prog 3 | 4 | COPY . . 5 | CMD ["crystal", "main.cr"] 6 | -------------------------------------------------------------------------------- /5-fish/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.20 2 | WORKDIR /prog 3 | 4 | RUN apk add --no-cache fish 5 | 6 | COPY . . 7 | CMD ["fish", "main.fish"] 8 | -------------------------------------------------------------------------------- /6-perl/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.20 2 | WORKDIR /prog 3 | 4 | RUN apk add --no-cache perl 5 | 6 | COPY . . 7 | CMD ["perl", "main.perl"] 8 | -------------------------------------------------------------------------------- /9-fsharp/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine 2 | WORKDIR /prog 3 | 4 | COPY . . 5 | CMD ["dotnet", "fsi", "main.fsx"] 6 | -------------------------------------------------------------------------------- /13-pascal/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM freepascal/fpc:3.2.2-alpine-3.19-minimal 2 | WORKDIR /prog 3 | 4 | COPY . . 5 | RUN fpc main.pas 6 | CMD ["./main"] 7 | -------------------------------------------------------------------------------- /23-nim/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nimlang/nim:2.2.0-alpine 2 | WORKDIR /prog 3 | 4 | COPY . . 5 | CMD ["nim", "compile", "--hints:off", "--run", "main.nim"] 6 | -------------------------------------------------------------------------------- /16-d/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.20 2 | WORKDIR /prog 3 | 4 | RUN apk add --no-cache dmd build-base 5 | 6 | COPY . . 7 | RUN dmd main.d 8 | CMD ["./main"] 9 | -------------------------------------------------------------------------------- /10-clojure/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM clojure:temurin-23-alpine 2 | WORKDIR /prog 3 | 4 | RUN apk add --no-cache rlwrap 5 | 6 | COPY . . 7 | CMD ["clj", "-M", "main.clj"] 8 | -------------------------------------------------------------------------------- /22-haskell/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM haskell:9.8.4-slim-bullseye 2 | WORKDIR /prog 3 | 4 | RUN stack install unordered-containers 5 | 6 | COPY . . 7 | RUN stack ghc main.hs 8 | CMD ["./main"] 9 | -------------------------------------------------------------------------------- /21-gleam/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.21 2 | WORKDIR /prog 3 | 4 | RUN apk add --no-cache erlang gleam 5 | 6 | COPY . . 7 | RUN mkdir src 8 | RUN mv main.gleam src/ 9 | CMD ["gleam", "run", "--no-print-progress"] 10 | -------------------------------------------------------------------------------- /3-nasm/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.20 2 | WORKDIR /prog 3 | 4 | # install nasm 5 | RUN apk add --no-cache nasm binutils 6 | 7 | # copy and build program files 8 | COPY . . 9 | RUN nasm -f elf64 main.asm 10 | RUN ld main.o -o main 11 | CMD ["./main"] 12 | -------------------------------------------------------------------------------- /run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd $(dirname $0) 4 | 5 | if [ ! -d "$1" ]; then 6 | echo "invalid problem: $1" 7 | exit 1 8 | fi 9 | 10 | cd $1 11 | docker build -t $1 . 12 | echo "build complete, running..." 13 | 14 | input_file=${2:-input.txt} 15 | echo "reading from $input_file" 16 | cat $input_file | docker run -i --rm $1 17 | -------------------------------------------------------------------------------- /8-zig/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.20 2 | WORKDIR /prog 3 | 4 | RUN apk add --no-cache tar xz 5 | RUN wget -O zig.tar.xz https://ziglang.org/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz 6 | RUN tar xf zig.tar.xz zig-linux-x86_64-0.13.0 7 | RUN mv zig-linux-x86_64-0.13.0 zig/ 8 | 9 | COPY . . 10 | CMD ["zig/zig", "run", "main.zig"] 11 | -------------------------------------------------------------------------------- /2-sass/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:23-alpine 2 | WORKDIR /prog 3 | 4 | # install sass globally 5 | RUN npm i -g sass 6 | 7 | # "run" the program, this bash oneliner is just to convert newlines to escape characters 8 | COPY . . 9 | CMD cat <(sed 's/$/\\n/g' | tr -d '\n' | sed -r 's/^(.*)\\n$/\$input\: '"'"'\1'"'"'/') <(cat main.scss) | sass --stdin 10 | -------------------------------------------------------------------------------- /7-scratch/README.md: -------------------------------------------------------------------------------- 1 | To run this day's program, download `main.sb3`, head over to [the Scratch editor](https://scratch.mit.edu/projects/editor/), and press File > Load. 2 | Copy-paste the input as a single line when prompted by the cat. 3 | The answers for both puzzles should be stored in a list called "copyablesum". 4 | When the program has finished running, the cat will say how long the program took. 5 | -------------------------------------------------------------------------------- /4-psql/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM postgres:17.2-alpine 2 | WORKDIR /prog 3 | 4 | ENV POSTGRES_HOST_AUTH_METHOD=trust 5 | 6 | # If we use `-f main.sql`, then we'll have no stdin since psql originally takes sql as stdin. 7 | # Thus, we pass the contents of the program to `-c` instead, which doesn't compete for stdin. 8 | COPY . . 9 | CMD ["bash", "-c", "docker-entrypoint.sh postgres >/dev/null 2>&1 & sleep 5 && pg_isready && psql -q -U postgres -d postgres -c \"$(cat main.sql)\""] 10 | -------------------------------------------------------------------------------- /1-odin/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.20 2 | WORKDIR /prog 3 | 4 | # install odin 5 | RUN apk add --no-cache tar clang 6 | RUN wget -O odin.zip https://github.com/odin-lang/Odin/releases/download/dev-2024-11/odin-linux-amd64-dev-2024-11.zip 7 | RUN unzip odin.zip 8 | RUN tar xzf dist.tar.gz odin-linux-amd64-nightly+2024-11-04 9 | RUN mv odin-linux-amd64-nightly+2024-11-04 odin/ 10 | 11 | # copy and build program files 12 | COPY . . 13 | CMD ["odin/odin", "run", "main.odin", "-file"] 14 | -------------------------------------------------------------------------------- /12-smalltalk/support-musl.patch: -------------------------------------------------------------------------------- 1 | --- smalltalk-3.2/libgst/callin.c 2010-04-21 03:25:01.000000000 -0600 2 | +++ smalltalk-3.2/libgst/callin.c 2023-11-27 20:48:15.236308417 -0700 3 | @@ -99,11 +99,7 @@ 4 | if (!_gst_smalltalk_initialized) 5 | _gst_initialize (NULL, NULL, GST_NO_TTY); 6 | 7 | -#ifdef __va_copy 8 | - __va_copy (save, ap); 9 | -#else 10 | - save = ap; 11 | -#endif 12 | + va_copy(save, ap); 13 | 14 | for (numArgs = 0; (anArg = va_arg (ap, OOP)) != NULL; numArgs++); 15 | -------------------------------------------------------------------------------- /12-smalltalk/Dockerfile: -------------------------------------------------------------------------------- 1 | # adapted from https://github.com/frison/100hellos/tree/main/smalltalk 2 | 3 | FROM alpine:3.20 4 | WORKDIR /prog 5 | 6 | RUN apk add --no-cache zip gawk patch linux-headers gcc musl-dev make 7 | RUN wget https://ftp.gnu.org/gnu/smalltalk/smalltalk-3.2.tar.xz 8 | RUN tar -xvf smalltalk-3.2.tar.xz 9 | RUN rm smalltalk-3.2.tar.xz 10 | 11 | COPY support-musl.patch . 12 | RUN patch -p0 < support-musl.patch 13 | 14 | WORKDIR /prog/smalltalk-3.2 15 | RUN CFLAGS=-std=gnu89 ./configure --prefix=/prog/st --disable-generational-gc --without-emacs 16 | RUN make 17 | RUN make install 18 | 19 | WORKDIR /prog 20 | COPY . . 21 | CMD ["st/bin/gst", "-f", "main.st"] 22 | -------------------------------------------------------------------------------- /25-crystal/main.cr: -------------------------------------------------------------------------------- 1 | WIDTH = 5 2 | HEIGHT = 7 3 | 4 | locks = Array(Array(Int32)).new 5 | keys = Array(Array(Int32)).new 6 | 7 | def to_heights(lines) 8 | heights = Array(Int32).new(WIDTH, -1) 9 | lines.each do |line| 10 | line.chars.each.with_index do |c, i| 11 | heights[i] += (c == '#').to_unsafe 12 | end 13 | end 14 | heights 15 | end 16 | 17 | until STDIN.peek.empty? 18 | lines = Array(String).new(HEIGHT) { STDIN.gets.not_nil! } 19 | heights = to_heights(lines) 20 | if lines[0].count("#") == WIDTH 21 | locks.push(heights) 22 | else 23 | keys.push(heights) 24 | end 25 | STDIN.gets 26 | end 27 | 28 | total = 0 29 | locks.each do |lock| 30 | keys.each do |key| 31 | res = lock.zip(key).all? { |l, k| l + k < HEIGHT - 1 } 32 | total += res.to_unsafe 33 | end 34 | end 35 | puts "Answer: #{total}" 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Solving AOC 2024 using a different language each day. 2 | Docker is used to run each program so I don't have to make a mess out of my host environment. 3 | 4 | Each program will read from stdin and display the answers in stdout. Ideally, the program handles the entirety of the IO cycle; however, for some languages like Sass, some preprocessing is mandatory. 5 | 6 | Day 7: Scratch is not runnable through the usual run script. See the README in the Scratch folder for details. 7 | 8 | ![Languages](languages.svg) 9 | Full language breakdown, minus Inko and Scratch 10 | 11 | ## Roadmap 12 | 13 | **Languages that didn't make the cut:** Ring, Hylo, Vale, Groovy, PHP, Ruby, Oberon, Jai, Wren, Lua 14 | 15 | 1. Odin 16 | 2. Sass (SCSS) 17 | 3. NASM (x64) 18 | 4. PostgreSQL 19 | 5. Fish 20 | 6. Perl 21 | 7. Scratch 22 | 8. Zig 23 | 9. F# 24 | 10. Clojure 25 | 11. V 26 | 12. GNU Smalltalk 27 | 13. Pascal (FPC) 28 | 14. Erlang 29 | 15. Swift 30 | 16. D (DMD) 31 | 17. Hack 32 | 18. Inko 33 | 19. Pony 34 | 20. Haxe 35 | 21. Gleam 36 | 22. Haskell 37 | 23. Nim 38 | 24. OCaml 39 | 25. Crystal 40 | -------------------------------------------------------------------------------- /1-odin/main.odin: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "core:io" 4 | import "core:os" 5 | import "core:fmt" 6 | import "core:slice" 7 | import "core:bufio" 8 | import "core:strconv" 9 | import "core:strings" 10 | 11 | main :: proc() { 12 | stream := os.stream_from_handle(os.stdin) 13 | 14 | // https://odin-lang.org/docs/overview/#allocators 15 | scanner: bufio.Scanner 16 | bufio.scanner_init(&scanner, stream, context.temp_allocator) 17 | defer bufio.scanner_destroy(&scanner) 18 | 19 | left: [dynamic]int 20 | right: [dynamic]int 21 | defer delete(left) 22 | defer delete(right) 23 | 24 | for bufio.scanner_scan(&scanner) { 25 | line := bufio.scanner_text(&scanner) 26 | tokens := strings.split(line, " ") 27 | l := strconv.atoi(tokens[0]) 28 | r := strconv.atoi(tokens[len(tokens) - 1]) 29 | append(&left, l) 30 | append(&right, r) 31 | } 32 | 33 | puzzle_1(left[:], right[:]) 34 | puzzle_2(left[:], right[:]) 35 | } 36 | 37 | puzzle_1 :: proc(left: []int, right: []int) { 38 | slice.sort(left) 39 | slice.sort(right) 40 | 41 | total := 0 42 | for v in soa_zip(l=left, r=right) { 43 | total += abs(v.l - v.r) 44 | } 45 | 46 | fmt.println(total) 47 | } 48 | 49 | puzzle_2 :: proc(left: []int, right: []int) { 50 | freq: map[int]int 51 | defer delete(freq) 52 | 53 | for r in right { 54 | freq[r] += 1 55 | } 56 | 57 | score := 0 58 | for l in left { 59 | score += l * freq[l] 60 | } 61 | 62 | fmt.println(score) 63 | } 64 | -------------------------------------------------------------------------------- /22-haskell/main.hs: -------------------------------------------------------------------------------- 1 | import System.IO (isEOF) 2 | import Control.Monad (replicateM) 3 | import Data.Bits (xor, shiftL, shiftR) 4 | import Data.List (tails) 5 | import qualified Data.HashMap.Strict as HashMap 6 | -- no hashmap in std??? 7 | 8 | numChanges = 2000 9 | 10 | windows :: Int -> [a] -> [[a]] 11 | windows m = foldr (zipWith (:)) (repeat []) . take m . tails 12 | 13 | mixPrune :: Int -> Int -> Int 14 | mixPrune = xor . (\n -> n `mod` 16777216) 15 | 16 | nextSecret :: Int -> Int 17 | nextSecret n = 18 | let n' = mixPrune (n `shiftL` 6) n 19 | n'' = mixPrune (n' `shiftR` 5) n' 20 | in mixPrune (n'' `shiftL` 11) n'' 21 | 22 | seqPrices :: Int -> HashMap.HashMap (Int, Int, Int, Int) Int 23 | seqPrices secret = 24 | let secrets = take (numChanges + 1) (iterate nextSecret secret) 25 | prices = map (\n -> n `mod` 10) secrets 26 | groups = windows 5 prices 27 | in foldr (\ps acc -> 28 | let diffs = zipWith (-) (tail ps) ps 29 | dkey = (diffs !! 0, diffs !! 1, diffs !! 2, diffs !! 3) 30 | in HashMap.insert dkey (last ps) acc 31 | ) HashMap.empty groups 32 | 33 | main :: IO() 34 | main = do 35 | secrets <- map read . lines <$> getContents :: IO [Int] 36 | 37 | let newSecrets = map (\s -> iterate nextSecret s !! numChanges) secrets 38 | putStrLn $ "Puzzle 1: " ++ show (foldl (+) 0 newSecrets) 39 | 40 | let secretSeqPrices = foldl (HashMap.unionWith (+)) HashMap.empty (map seqPrices secrets) 41 | putStrLn $ "Puzzle 2: " ++ show (maximum $ HashMap.elems secretSeqPrices) 42 | -------------------------------------------------------------------------------- /10-clojure/main.clj: -------------------------------------------------------------------------------- 1 | (require '[clojure.set]) 2 | 3 | (defn read-grid [] 4 | (map 5 | (partial map (fn [c] (Character/digit c 10))) 6 | (clojure.string/split-lines (slurp *in*)))) 7 | 8 | ;; just assume rows == cols 9 | (defn at [grid r c] 10 | (let [size (count grid)] 11 | (if (and (<= 0 r (- size 1)) (<= 0 c (- size 1))) 12 | (nth (nth grid r) c) 13 | -1))) 14 | 15 | (defn zeroes [grid] 16 | (let [size (count grid)] 17 | (for [r (range size) 18 | c (range size) 19 | :when (= 0 (at grid r c))] 20 | [r c]))) 21 | 22 | (defn trail-reduce [base coll] 23 | (letfn [(rec [grid [r c]] 24 | (if (== 9 (at grid r c)) 25 | (base [r c]) 26 | (coll 27 | (for [[dr dc] [[-1 0] [0 1] [1 0] [0 -1]] 28 | :let [tr (+ r dr) tc (+ c dc)] 29 | :when (= (+ 1 (at grid r c)) (at grid tr tc))] 30 | (rec grid [tr tc])))))] 31 | (memoize rec))) 32 | 33 | ;; puzzle 1 34 | (def trail-ends (trail-reduce (partial hash-set) (partial apply clojure.set/union))) 35 | (defn puzzle1 [grid] 36 | (reduce + (map (comp count (partial trail-ends grid)) (zeroes grid)))) 37 | 38 | ;; puzzle 2 39 | (def trail-rating (trail-reduce (fn [_] 1) (partial reduce +))) 40 | (defn puzzle2 [grid] 41 | (reduce + (map (partial trail-rating grid) (zeroes grid)))) 42 | 43 | (let [grid (read-grid)] 44 | (printf "Puzzle 1: %d\n" (time (puzzle1 grid))) 45 | (printf "Puzzle 2: %d\n" (time (puzzle2 grid)))) 46 | -------------------------------------------------------------------------------- /11-v/main.v: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import arrays 4 | import strconv 5 | 6 | fn atou(s string) !u64 { 7 | return strconv.parse_uint(s, 10, 0)! 8 | } 9 | 10 | fn blink(n u64, mut memo map[u64]map[int]u64, times int) u64 { 11 | if n in memo && times in memo[n] { 12 | return memo[n][times] 13 | } 14 | 15 | if times == 0 { 16 | return 1 17 | } 18 | 19 | amount := match true { 20 | n == 0 { blink(1, mut memo, times - 1) } 21 | n.str().len % 2 == 0 { 22 | s := n.str() 23 | m := int(s.len / 2) 24 | l := atou(s[..m]) or { panic("Failed to convert string $s") } 25 | r := atou(s[m..]) or { panic("Failed to convert string $s") } 26 | blink(l, mut memo, times - 1) + blink(r, mut memo, times - 1) 27 | } 28 | else { blink(n * 2024, mut memo, times - 1) } 29 | } 30 | 31 | memo[n][times] = amount 32 | return amount 33 | } 34 | 35 | fn main() { 36 | sw := time.new_stopwatch() 37 | 38 | stones := os.get_line().split(' ').map(atou(it)!) 39 | mut memo := map[u64]map[int]u64 {} 40 | 41 | puzzle_1_stones := stones.map(blink(it, mut memo, 25)) 42 | total_1 := arrays.reduce( 43 | puzzle_1_stones, 44 | fn (acc u64, n u64) u64 { return acc + n } 45 | )! 46 | println("Puzzle 1: $total_1 (took ${sw.elapsed().milliseconds()} ms)") 47 | 48 | puzzle_2_stones := stones.map(blink(it, mut memo, 75)) 49 | total_2 := arrays.reduce( 50 | puzzle_2_stones, 51 | fn (acc u64, n u64) u64 { return acc + n } 52 | )! 53 | println("Puzzle 2: $total_2 (took ${sw.elapsed().milliseconds()} ms)") 54 | } 55 | -------------------------------------------------------------------------------- /23-nim/main.nim: -------------------------------------------------------------------------------- 1 | import std/sets 2 | import std/tables 3 | import std/sequtils 4 | import std/strutils 5 | import std/algorithm 6 | 7 | var line: string 8 | var connections = initTable[string, HashSet[string]]() 9 | while readLine(stdin, line): 10 | let u = line[0..1] 11 | let v = line[3..4] 12 | connections.mgetOrPut(u, initHashSet[string]()).incl(v) 13 | connections.mgetOrPut(v, initHashSet[string]()).incl(u) 14 | 15 | var total = 0 16 | for u, vs in connections.mpairs(): 17 | for v in vs: 18 | if u > v: 19 | continue 20 | for w in connections[v]: 21 | if v > w or not (u in connections[w]): 22 | continue 23 | if 't' in [u[0], v[0], w[0]]: 24 | total += 1 25 | echo "Puzzle 1: ", total 26 | 27 | # bron-kerbosch 28 | var cliques: seq[HashSet[string]] = @[] 29 | proc findMaximalCliques(r: HashSet[string], p: HashSet[string], x: HashSet[string]) = 30 | if len(p) == 0 and len(x) == 0: 31 | cliques.add(r) 32 | return 33 | 34 | var currP = p.toSeq().toHashSet() 35 | var currX = x.toSeq().toHashSet() 36 | for v in p.items(): 37 | findMaximalCliques(r + [v].toHashSet(), currP * connections[v], currX * connections[v]) 38 | currP.excl(v) 39 | currX.incl(v) 40 | 41 | let keys = connections.keys().toSeq().toHashSet() 42 | findMaximalCliques(initHashSet[string](), keys, initHashSet[string]()) 43 | 44 | var maxLen = 0 45 | var maxClique: HashSet[string] 46 | for clique in cliques: 47 | if len(clique) > maxLen: 48 | maxLen = len(clique) 49 | maxClique = clique 50 | 51 | var res = maxClique.toSeq() 52 | res.sort() 53 | echo "Puzzle 2: ", res.join(",") 54 | -------------------------------------------------------------------------------- /13-pascal/main.pas: -------------------------------------------------------------------------------- 1 | program Main; 2 | uses 3 | Classes, SysUtils, Math; 4 | 5 | const 6 | TOLERANCE = 0.0001; 7 | SHIFT = 10000000000000; 8 | 9 | procedure SplitText(const delim, s: String; parts: TStringList); 10 | begin 11 | parts.LineBreak := delim; 12 | parts.Text := s; 13 | end; 14 | 15 | procedure ReadMachine(const subdelim: String; var rx, ry: Int64); 16 | var 17 | line: String; 18 | parts: TStringList; 19 | subparts: TStringList; 20 | begin 21 | parts := TStringList.Create; 22 | subparts := TStringList.Create; 23 | 24 | ReadLn(line); 25 | SplitText(',', line, parts); 26 | SplitText(subdelim, parts[0], subparts); 27 | rx := StrToInt64(subparts[1]); 28 | SplitText(subdelim, parts[1], subparts); 29 | ry := StrToInt64(subparts[1]); 30 | end; 31 | 32 | procedure SolveMachine(const ax, ay, bx, by, tx, ty: Int64; var total: Int64); 33 | var 34 | a, b, num, den: Int64; 35 | begin 36 | num := ax * ty - ay * tx; 37 | den := ax * by - ay * bx; 38 | b := num div den; 39 | if (num mod den = 0) and ((tx - bx * b) mod ax = 0) then 40 | begin 41 | a := (tx - bx * b) div ax; 42 | total := total + 3 * a + b; 43 | end; 44 | end; 45 | 46 | var 47 | puzzle1, puzzle2, ax, ay, bx, by, tx, ty: Int64; 48 | begin 49 | puzzle1 := 0; 50 | puzzle2 := 0; 51 | 52 | while not eof do 53 | begin 54 | ReadMachine('+', ax, ay); 55 | ReadMachine('+', bx, by); 56 | ReadMachine('=', tx, ty); 57 | ReadLn(); 58 | SolveMachine(ax, ay, bx, by, tx, ty, puzzle1); 59 | SolveMachine(ax, ay, bx, by, tx + SHIFT, ty + SHIFT, puzzle2); 60 | end; 61 | 62 | WriteLn('Puzzle 1: ', puzzle1); 63 | WriteLn('Puzzle 2: ', puzzle2); 64 | end. 65 | -------------------------------------------------------------------------------- /6-perl/main.perl: -------------------------------------------------------------------------------- 1 | @grid = (); 2 | $gr = 0; 3 | $gc = 0; 4 | $rownum = 0; 5 | while (my $line = ) { 6 | chomp($line); 7 | push(@grid, $line); 8 | my $guardpos = index($line, "^"); 9 | if ($guardpos != -1) { 10 | $gr = $rownum; 11 | $gc = $guardpos; 12 | } 13 | $rownum++; 14 | } 15 | 16 | $rows = scalar @grid; 17 | $cols = length(@grid[0]); 18 | @adj = ( 19 | [-1, 0], 20 | [0, 1], 21 | [1, 0], 22 | [0, -1] 23 | ); 24 | 25 | sub bfs { 26 | my $r = $gr; 27 | my $c = $gc; 28 | my %visited = (); 29 | my $dir = 0; 30 | while (1) { 31 | my $vkey = "$r,$c"; 32 | $visited{$vkey} = ($visited{$vkey} // 0) + 1; 33 | my $tmp = $visited{$vkey}; 34 | if ($visited{$vkey} >= 6) { 35 | # infinite loop 36 | return -1; 37 | } 38 | 39 | my ($dr, $dc) = @{$adj[$dir]}; 40 | my $tr = $r + $dr; 41 | my $tc = $c + $dc; 42 | if (not (0 <= $tr < $rows and 0 <= $tc < $cols)) { 43 | my $numvis = scalar keys %visited; 44 | return $numvis; 45 | } 46 | 47 | my $cell = substr($grid[$tr], $tc, 1); 48 | if ($cell eq "#") { 49 | $dir = ($dir + 1) % 4; 50 | } else { 51 | $r = $tr; 52 | $c = $tc; 53 | } 54 | } 55 | } 56 | 57 | # Puzzle 1 58 | printf "Puzzle 1: " . bfs() . "\n"; 59 | 60 | # Puzzle 2 61 | my $count = 0; 62 | foreach my $or (0..$rows - 1) { 63 | foreach my $oc (0..$cols - 1) { 64 | my $cell = substr($grid[$or], $oc, 1); 65 | if ($cell ne ".") { 66 | next; 67 | } 68 | substr($grid[$or], $oc, 1) = "#"; 69 | if (bfs() == -1) { 70 | $count++; 71 | } 72 | substr($grid[$or], $oc, 1) = $cell; 73 | } 74 | } 75 | printf "Puzzle 2: $count\n"; 76 | -------------------------------------------------------------------------------- /2-sass/main.scss: -------------------------------------------------------------------------------- 1 | ; 2 | 3 | @use "sass:string"; 4 | @use "sass:list"; 5 | 6 | $strings: '0' '1' '2' '3' '4' '5' '6' '7' '8' '9'; 7 | $numbers: 0 1 2 3 4 5 6 7 8 9; 8 | 9 | @function int($str) { 10 | $res: 0; 11 | $str: string.split($str, ""); 12 | @each $c in $str { 13 | $i: list.index($strings, $c); 14 | $n: list.nth($numbers, $i); 15 | $res: $res * 10 + $n; 16 | } 17 | @return $res; 18 | } 19 | 20 | $lines: string.split($input, "\n"); 21 | 22 | @function is_report_safe($levels) { 23 | $len: list.length($levels); 24 | $is_asc: int(list.nth($levels, 2)) > int(list.nth($levels, 1)); 25 | $is_safe: true; 26 | 27 | @for $i from 2 through $len { 28 | $diff: int(list.nth($levels, $i)) - int(list.nth($levels, $i - 1)); 29 | @if $is_asc { 30 | @if $diff < 1 or $diff > 3 { 31 | $is_safe: false; 32 | } 33 | } @else { 34 | @if $diff < -3 or $diff > -1 { 35 | $is_safe: false; 36 | } 37 | } 38 | } 39 | 40 | @return $is_safe; 41 | } 42 | 43 | // part 1 44 | $safe_1: 0; 45 | @each $line in $lines { 46 | $levels: string.split($line, " "); 47 | @if is_report_safe($levels) { 48 | $safe_1: $safe_1 + 1; 49 | } 50 | } 51 | 52 | // part 2 53 | $safe_2: 0; 54 | @each $line in $lines { 55 | $levels: string.split($line, " "); 56 | $is_passable: is_report_safe($levels); 57 | 58 | $len: list.length($levels); 59 | @for $i from 1 through $len { 60 | $new_levels: (); 61 | @for $j from 1 through $len { 62 | @if $i != $j { 63 | $new_levels: list.append($new_levels, list.nth($levels, $j)); 64 | } 65 | } 66 | $is_passable: $is_passable or is_report_safe($new_levels); 67 | } 68 | 69 | @if $is_passable { 70 | $safe_2: $safe_2 + 1; 71 | } 72 | } 73 | 74 | @debug $safe_1 $safe_2; 75 | -------------------------------------------------------------------------------- /4-psql/main.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE input (rowidx SERIAL PRIMARY KEY, data TEXT); 2 | COPY input (data) FROM STDIN; 3 | SELECT * FROM input; 4 | 5 | CREATE FUNCTION chr_at(r INT, c INT) RETURNS CHAR 6 | AS $$ 7 | BEGIN 8 | RETURN (SELECT SUBSTR(data, c, 1) FROM input WHERE rowidx = r); 9 | END 10 | $$ LANGUAGE plpgsql; 11 | 12 | CREATE FUNCTION build_str(r INT, c INT, dr INT, dc INT, len INT DEFAULT 4) RETURNS TEXT 13 | AS $$ 14 | DECLARE res_str TEXT; 15 | BEGIN 16 | SELECT '' INTO res_str; 17 | FOR m IN 0..(len - 1) LOOP 18 | SELECT res_str || chr_at(r + m * dr, c + m * dc) INTO res_str; 19 | END LOOP; 20 | RETURN res_str; 21 | END 22 | $$ LANGUAGE plpgsql; 23 | 24 | DO $$ 25 | DECLARE numrows INT; 26 | DECLARE numcols INT; 27 | BEGIN 28 | SELECT COUNT(*) FROM input INTO numrows; 29 | SELECT LENGTH(data) FROM input LIMIT 1 INTO numcols; 30 | 31 | -- puzzle 1 32 | CREATE TABLE search_xmas (data TEXT); 33 | FOR r IN 1..numrows LOOP 34 | FOR c IN 1..numcols LOOP 35 | INSERT INTO search_xmas (data) 36 | VALUES 37 | (build_str(r, c, 1, 0)), 38 | (build_str(r, c, 1, 1)), 39 | (build_str(r, c, 0, 1)), 40 | (build_str(r, c, -1, 1)), 41 | (build_str(r, c, -1, 0)), 42 | (build_str(r, c, -1, -1)), 43 | (build_str(r, c, 0, -1)), 44 | (build_str(r, c, 1, -1)); 45 | END LOOP; 46 | END LOOP; 47 | RAISE NOTICE 'Puzzle 1: %', (SELECT COUNT(*) FROM search_xmas WHERE data = 'XMAS'); 48 | 49 | -- puzzle 2 50 | CREATE TABLE search_x (data TEXT); 51 | FOR r IN 1..numrows LOOP 52 | FOR c IN 1..numcols LOOP 53 | INSERT INTO search_x (data) 54 | VALUES (build_str(r - 1, c - 1, 1, 1, 3) || build_str(r + 1, c - 1, -1, 1, 3)); 55 | END LOOP; 56 | END LOOP; 57 | RAISE NOTICE 'Puzzle 2: %', ( 58 | SELECT COUNT(*) FROM search_x 59 | WHERE data IN ('SAMSAM', 'SAMMAS', 'MASMAS', 'MASSAM') 60 | ); 61 | END 62 | $$; 63 | -------------------------------------------------------------------------------- /5-fish/main.fish: -------------------------------------------------------------------------------- 1 | while read -d '|' l r 2 | if test -z "$l" 3 | break 4 | end 5 | set -a "k_$l" $r 6 | set -a "d_$r" $l 7 | end 8 | 9 | set i 0 10 | set total 0 11 | while read line 12 | set pages (string split ',' $line) 13 | set seen 14 | set good 1 15 | for page in $pages 16 | set -a seen $page 17 | set pagekey "k_$page" 18 | for dep in $$pagekey 19 | if contains $dep $seen 20 | set good 0 21 | end 22 | end 23 | end 24 | 25 | if test $good -eq 1 26 | set len (count $pages) 27 | set mid (math (math -s0 $len / 2) + 1) 28 | set total (math $total + $pages[$mid]) 29 | else 30 | set -a badupdates $i 31 | set "badupdate_$i" $pages 32 | end 33 | 34 | set i (math $i + 1) 35 | end 36 | echo "Puzzle 1: $total" 37 | 38 | set total 0 39 | for i in $badupdates 40 | set badupdatekey "badupdate_$i" 41 | set pages $$badupdatekey 42 | 43 | # assume only one possible ordering 44 | set node $pages 45 | for page in $pages 46 | set pagekey "k_$page" 47 | set "tempk_$page" $$pagekey # copy page so we can mutate 48 | for dep in $$pagekey 49 | set node (string match -v $dep $node) 50 | end 51 | end 52 | 53 | set res 54 | for _i in $pages 55 | set -a res $node 56 | set pagekey "tempk_$node" 57 | set newnode 58 | for dep in $$pagekey 59 | if contains $dep $pages 60 | set "tempk_$node" (string match -v $dep $$pagekey) 61 | set noinc 1 62 | for u in $pages 63 | set pagekeyu "tempk_$u" 64 | if contains $dep $$pagekeyu 65 | set noinc 0 66 | end 67 | end 68 | if test $noinc -eq 1 69 | set newnode $dep 70 | end 71 | end 72 | end 73 | set node $newnode 74 | end 75 | 76 | set len (count $res) 77 | set mid (math (math -s0 $len / 2) + 1) 78 | set total (math $total + $res[$mid]) 79 | end 80 | echo "Puzzle 2: $total" 81 | -------------------------------------------------------------------------------- /18-inko/main.inko: -------------------------------------------------------------------------------- 1 | import std.set (Set) 2 | import std.int (Format) 3 | import std.array (Array) 4 | import std.deque (Deque) 5 | import std.stdio (Stdout, Stdin) 6 | 7 | # let SIZE = 7 8 | # let SIMTIME = 12 9 | let SIZE = 71 10 | let SIMTIME = 1024 11 | 12 | class async Main { 13 | fn simulate(n: Int, corruptions: ref Array[(Int, Int)]) -> Int { 14 | let corrupted = Set.new 15 | corruptions.iter.take(n).each(fn (coord) { corrupted.insert(coord) }) 16 | 17 | let q = Deque.new 18 | let visited = Set.new 19 | let directions = [(1, 0), (0, 1), (-1, 0), (0, -1)] 20 | 21 | q.push_back((0, 0, 0)) 22 | visited.insert((0, 0)) 23 | while q.size > 0 { 24 | let front = q.pop_front.get 25 | let x = front.0 26 | let y = front.1 27 | let steps = front.2 28 | 29 | if x == (SIZE - 1) and y == (SIZE - 1) { 30 | return steps 31 | } 32 | 33 | directions.iter.each(fn (adj) { 34 | let tx = x + adj.0 35 | let ty = y + adj.1 36 | if 37 | 0 <= tx and tx < SIZE and 0 <= ty and ty < SIZE and 38 | corrupted.contains?((tx, ty)).false? and 39 | visited.contains?((tx, ty)).false? 40 | { 41 | q.push_back((tx, ty, steps + 1)) 42 | visited.insert((tx, ty)) 43 | } 44 | }) 45 | } 46 | 47 | -1 48 | } 49 | 50 | fn async main { 51 | let stdout = Stdout.new 52 | 53 | let bytes = ByteArray.new 54 | Stdin.new.read_all(bytes) 55 | let input = bytes.drain_to_string.trim 56 | let corruptions = input.split('\n').map(fn (coord) { 57 | let parts = coord.split(',') 58 | let x = Int.parse(parts.next.get, Format.Decimal).get 59 | let y = Int.parse(parts.next.get, Format.Decimal).get 60 | (x, y) 61 | }).to_array 62 | 63 | let puzzle1 = simulate(SIMTIME, ref corruptions) 64 | stdout.print('Puzzle 1: ${puzzle1}') 65 | 66 | let mut lo = SIMTIME + 1 67 | let mut hi = corruptions.size 68 | while lo < hi { 69 | let m = (lo + hi) / 2 70 | let res = simulate(m, ref corruptions) 71 | if res == -1 { 72 | hi = m 73 | } else { 74 | lo = m + 1 75 | } 76 | } 77 | let coord = corruptions.get(lo - 1) 78 | stdout.print('Puzzle 2: ${coord.0},${coord.1}') 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /14-erlang/main.erl: -------------------------------------------------------------------------------- 1 | -module(main). 2 | -define(ROWS, 103). 3 | -define(COLS, 101). 4 | -define(SECONDS, 100). 5 | 6 | % 82 is the appearance of the first "tree". 101 is the cycle time to see the next "tree". 7 | % puzzle 2 starts an infinite loop, user must manually terminate!! 8 | main(_) -> 9 | Robots = read_robots([]), 10 | puzzle_1(Robots), 11 | puzzle_2(Robots, 82). 12 | 13 | mod_pos(A, B) -> 14 | Rem = A rem B, 15 | if 16 | Rem < 0 -> Rem + B; 17 | true -> Rem 18 | end. 19 | 20 | puzzle_1(Robots) -> 21 | Locations = lists:map( 22 | fun ({{Px, Py}, {Vx, Vy}}) -> 23 | {mod_pos(Px + ?SECONDS * Vx, ?COLS), mod_pos(Py + ?SECONDS * Vy, ?ROWS)} 24 | end, 25 | Robots 26 | ), 27 | Mx = ?COLS div 2, 28 | My = ?ROWS div 2, 29 | Q1 = lists:filter(fun ({X, Y}) -> (X < Mx) and (Y < My) end, Locations), 30 | Q2 = lists:filter(fun ({X, Y}) -> (X > Mx) and (Y < My) end, Locations), 31 | Q3 = lists:filter(fun ({X, Y}) -> (X < Mx) and (Y > My) end, Locations), 32 | Q4 = lists:filter(fun ({X, Y}) -> (X > Mx) and (Y > My) end, Locations), 33 | Total = lists:foldl(fun (Ps, Acc) -> Acc * erlang:length(Ps) end, 1, [Q1, Q2, Q3, Q4]), 34 | io:format("Puzzle 1: ~w~n", [Total]). 35 | 36 | puzzle_2(Robots, Seconds) -> 37 | Locations = lists:map( 38 | fun ({{Px, Py}, {Vx, Vy}}) -> 39 | {mod_pos(Px + Seconds * Vx, ?COLS), mod_pos(Py + Seconds * Vy, ?ROWS)} 40 | end, 41 | Robots 42 | ), 43 | io:format("~n~n~w~n", [Seconds]), 44 | print_grid(Locations), 45 | puzzle_2(Robots, Seconds + 101). 46 | 47 | print_grid(Locations) -> 48 | SLocations = sets:from_list(Locations), 49 | lists:foreach( 50 | fun (Y) -> 51 | Row = lists:map( 52 | fun (X) -> 53 | case sets:is_element({X, Y}, SLocations) of 54 | true -> "D"; 55 | false -> "." 56 | end 57 | end, 58 | lists:seq(1, ?COLS) 59 | ), 60 | io:format("~s~n", [Row]) 61 | end, 62 | lists:seq(1, ?ROWS) 63 | ). 64 | 65 | read_coords(St) -> 66 | Trimmed = string:trim(St, both, "\r\n"), 67 | [_, Coord] = string:lexemes(Trimmed, "="), 68 | [Xst, Yst] = string:lexemes(Coord, ","), 69 | {erlang:list_to_integer(Xst), erlang:list_to_integer(Yst)}. 70 | 71 | read_robots(Robots) -> 72 | case io:get_line("") of 73 | eof -> Robots; 74 | Line -> 75 | [Pst, Vst] = string:lexemes(Line, " "), 76 | {Px, Py} = read_coords(Pst), 77 | {Vx, Vy} = read_coords(Vst), 78 | read_robots([{{Px, Py}, {Vx, Vy}} | Robots]) 79 | end. 80 | -------------------------------------------------------------------------------- /9-fsharp/main.fsx: -------------------------------------------------------------------------------- 1 | open System 2 | 3 | let stopWatch = System.Diagnostics.Stopwatch.StartNew() 4 | 5 | let idNum idx = 6 | if idx % 2 = 0 then idx / 2 else 0 7 | 8 | let disk = 9 | Console.ReadLine() 10 | |> Seq.map (fun c -> uint64 c - uint64 '0') 11 | |> Seq.indexed 12 | |> Seq.map (fun (i, x) -> (uint64 (idNum i), x)) 13 | |> Seq.toList 14 | 15 | // sum [rangeStart, rangeEnd) 16 | let sumRange (rangeStart: uint64) (rangeEnd: uint64) = 17 | (rangeEnd - rangeStart) * (rangeStart + rangeEnd - 1UL) / 2UL 18 | 19 | // puzzle1 expandedIndex totalChecksum diskDefinition isCurrEmpty 20 | let rec puzzle1 (idx: uint64) (total: uint64) (empty: bool) = function 21 | | (lId, lBlocks) :: tail -> 22 | if empty then 23 | match List.rev tail with 24 | | (rId, rBlocks) :: mids -> 25 | let endIdx = idx + min rBlocks lBlocks 26 | let remaining = 27 | if rBlocks > lBlocks then List.rev ((rId, rBlocks - lBlocks) :: mids) 28 | elif rBlocks = lBlocks then List.tail mids |> List.rev 29 | else (0UL, lBlocks - rBlocks) :: (List.tail mids |> List.rev) 30 | puzzle1 endIdx (total + rId * sumRange idx endIdx) (rBlocks < lBlocks) remaining 31 | | _ -> total 32 | else 33 | let endIdx = idx + lBlocks 34 | puzzle1 endIdx (total + lId * sumRange idx endIdx) true tail 35 | | _ -> total 36 | 37 | // puzzle 2 38 | 39 | type File = (uint64 * uint64 * uint64) 40 | 41 | let rec disksWithIdxHelper disk idx acc = 42 | match disk with 43 | | (i, x) :: tail -> disksWithIdxHelper tail (idx + x) ((idx, i, x) :: acc) 44 | | _ -> List.rev acc 45 | 46 | // (expandedIdx, fileId, numBlocks) 47 | let disksWithIdx = disksWithIdxHelper disk 0UL [ ] 48 | 49 | let collectAlternating lst = 50 | lst 51 | |> List.indexed 52 | |> List.filter (fun (i, _) -> i % 2 = 0) 53 | |> List.map snd 54 | 55 | let diskEmpties = 56 | disksWithIdx 57 | |> List.tail 58 | |> collectAlternating 59 | 60 | let diskFiles = 61 | disksWithIdx 62 | |> List.rev 63 | |> collectAlternating 64 | 65 | let findEmpty (empties: File list) ((bIdx, _, tBlocks): File) = 66 | empties 67 | |> List.tryFindIndex (fun (idx, _, blocks) -> tBlocks <= blocks && bIdx >= idx) 68 | 69 | let rec puzzle2 (files: File list) (added: File list) (skipped: File list) (empties: File list) = 70 | match files with 71 | | file :: tail -> 72 | let (idx, id, blocks) = file 73 | match findEmpty empties file with 74 | | Some(i) -> 75 | let (eIdx, _, eBlocks) = List.item i empties 76 | let step = puzzle2 tail ((eIdx, id, blocks) :: added) skipped 77 | if blocks = eBlocks then 78 | step (List.removeAt i empties) 79 | else 80 | step (List.updateAt i (eIdx + blocks, 0UL, eBlocks - blocks) empties) 81 | | None -> puzzle2 tail added (file :: skipped) empties 82 | | _ -> 83 | (0UL, skipped @ added) 84 | ||> List.fold (fun acc (idx, id, blocks) -> acc + id * sumRange idx (idx + blocks)) 85 | 86 | printfn "Puzzle 1: %d" (puzzle1 0UL 0UL false disk) 87 | printfn "Puzzle 2: %d" (puzzle2 diskFiles [ ] [ ] diskEmpties) 88 | 89 | stopWatch.Stop() 90 | printfn "Took %fms" stopWatch.Elapsed.TotalMilliseconds 91 | -------------------------------------------------------------------------------- /20-haxe/Main.hx: -------------------------------------------------------------------------------- 1 | import haxe.iterators.StringIterator; 2 | 3 | typedef Point = {r:Int, c:Int} 4 | typedef WeightedPoint = Point & {d: Int} 5 | 6 | final THRESHOLD = 100; 7 | 8 | final directions: Array = [ 9 | {r: 1, c: 0}, 10 | {r: 0, c: 1}, 11 | {r: -1, c: 0}, 12 | {r: 0, c: -1}, 13 | ]; 14 | 15 | class Main { 16 | static function countReachable( 17 | grid:Array, 18 | dist:Array, 19 | from:Point, 20 | cheatLen:Int, 21 | ): Int { 22 | final size = grid.length; 23 | final sr = from.r, sc = from.c; 24 | final sd = dist[sr * size + sc]; 25 | 26 | // find exact manhat diamond because getting the square and filtering after is boring 27 | var count = 0; 28 | for (mdist in 1...(cheatLen + 1)) { 29 | // diamond border for each manhattan distance in [1, cheatLen] 30 | for (offset in 0...mdist) { 31 | var dr = mdist - offset; 32 | var dc = offset; 33 | for (_quadrant in 0...4) { 34 | final r = sr + dr; 35 | final c = sc + dc; 36 | final cell = grid[r]?.charAt(c); 37 | final timeSaved = (dist[r * size + c] ?? 0) - sd - mdist; 38 | if (timeSaved >= THRESHOLD && (cell == '.' || cell == 'E')) { 39 | count++; 40 | } 41 | 42 | // rotate 43 | final dx = dc; 44 | dc = dr; 45 | dr = -dx; 46 | } 47 | } 48 | } 49 | return count; 50 | } 51 | 52 | static public function main():Void { 53 | var line:String; 54 | var grid = new Array(); 55 | try { 56 | while (true) { 57 | line = Sys.stdin().readLine(); 58 | grid.push(line); 59 | } 60 | } catch (e:haxe.io.Eof) { 61 | trace("done!"); 62 | } 63 | 64 | final size = grid.length; 65 | var sr = 0, sc = 0; 66 | var er = 0, ec = 0; 67 | 68 | for (r => row in grid.keyValueIterator()) { 69 | for (c in 0...size) { 70 | switch (row.charAt(c)) { 71 | case 'S': 72 | sr = r; 73 | sc = c; 74 | case 'E': 75 | er = r; 76 | ec = c; 77 | } 78 | } 79 | } 80 | 81 | final dist = [for (i in 0...(size * size)) -1]; 82 | final path = new Array(); 83 | 84 | // first pass to find path 85 | var r = sr, c = sc; 86 | var d = 0; 87 | while (true) { 88 | path.push({r: r, c: c}); 89 | dist[r * size + c] = d; 90 | if (r == er && c == ec) { 91 | break; 92 | } 93 | for (dir in directions) { 94 | final tr = r + dir.r; 95 | final tc = c + dir.c; 96 | final cell = grid[tr].charAt(tc); 97 | if (dist[tr * size + tc] == -1 && (cell == '.' || cell == 'E')) { 98 | r = tr; 99 | c = tc; 100 | d++; 101 | break; 102 | } 103 | } 104 | } 105 | 106 | var puzzle1 = 0; 107 | for (from in path) { 108 | puzzle1 += countReachable(grid, dist, from, 2); 109 | } 110 | Sys.println('Puzzle 1: $puzzle1'); 111 | 112 | var puzzle2 = 0; 113 | for (from in path) { 114 | puzzle2 += countReachable(grid, dist, from, 20); 115 | } 116 | Sys.println('Puzzle 2: $puzzle2'); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /8-zig/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const Row = std.BoundedArray(u8, 64); 4 | const Location = struct { r: isize, c: isize }; 5 | const Locations = std.ArrayList(Location); 6 | const Frequencies = std.AutoHashMap(u8, Locations); 7 | 8 | pub fn main() !void { 9 | const writer = std.io.getStdOut().writer(); 10 | const reader = std.io.getStdIn().reader(); 11 | 12 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 13 | defer _ = gpa.deinit(); 14 | 15 | var arena = std.heap.ArenaAllocator.init(gpa.allocator()); 16 | const allocator = arena.allocator(); 17 | defer arena.deinit(); 18 | 19 | var grid = std.ArrayList(Row).init(allocator); 20 | var frequencies = Frequencies.init(allocator); 21 | 22 | var buf = try Row.init(64); 23 | while (try reader.readUntilDelimiterOrEof(&buf.buffer, '\n')) |line| { 24 | const row = try Row.fromSlice(line); 25 | const r = grid.items.len; 26 | for (line, 0..) |cell, c| { 27 | if (cell == '.') { 28 | continue; 29 | } 30 | const locations = try frequencies.getOrPut(cell); 31 | if (!locations.found_existing) { 32 | locations.value_ptr.* = Locations.init(allocator); 33 | } 34 | try locations.value_ptr.*.append(Location{ 35 | .r = @intCast(r), 36 | .c = @intCast(c), 37 | }); 38 | } 39 | try grid.append(row); 40 | } 41 | 42 | const rows = grid.items.len; 43 | const cols = grid.items[0].len; 44 | 45 | const ans1 = try puzzle1(rows, cols, frequencies, allocator); 46 | const ans2 = try puzzle2(rows, cols, frequencies, allocator); 47 | 48 | try writer.print("Puzzle 1: {}\nPuzzle 2: {}\n", .{ ans1, ans2 }); 49 | } 50 | 51 | fn puzzle1(rows: usize, cols: usize, frequencies: Frequencies, allocator: std.mem.Allocator) !u32 { 52 | var seen = std.AutoHashMap(Location, bool).init(allocator); 53 | defer seen.deinit(); 54 | 55 | var fiterator = frequencies.valueIterator(); 56 | while (fiterator.next()) |locs| { 57 | for (locs.items) |loc1| { 58 | for (locs.items) |loc2| { 59 | if (loc1.r == loc2.r and loc1.c == loc2.c) { 60 | continue; 61 | } 62 | 63 | const target = Location{ 64 | .r = loc1.r + 2 * (loc2.r - loc1.r), 65 | .c = loc1.c + 2 * (loc2.c - loc1.c), 66 | }; 67 | 68 | if (0 <= target.r and target.r < rows and 0 <= target.c and target.c < cols) { 69 | try seen.put(target, true); 70 | } 71 | } 72 | } 73 | } 74 | 75 | return seen.count(); 76 | } 77 | 78 | fn puzzle2(rows: usize, cols: usize, frequencies: Frequencies, allocator: std.mem.Allocator) !u32 { 79 | var seen = std.AutoHashMap(Location, bool).init(allocator); 80 | defer seen.deinit(); 81 | 82 | var fiterator = frequencies.valueIterator(); 83 | while (fiterator.next()) |locs| { 84 | for (locs.items) |loc1| { 85 | for (locs.items) |loc2| { 86 | if (loc1.r == loc2.r and loc1.c == loc2.c) { 87 | continue; 88 | } 89 | 90 | const dr = loc2.r - loc1.r; 91 | const dc = loc2.c - loc1.c; 92 | var tr = loc1.r; 93 | var tc = loc1.c; 94 | while (0 <= tr and tr < rows and 0 <= tc and tc < cols) : ({ 95 | tr += dr; 96 | tc += dc; 97 | }) { 98 | const target = Location{ .r = tr, .c = tc }; 99 | try seen.put(target, true); 100 | } 101 | } 102 | } 103 | } 104 | 105 | return seen.count(); 106 | } 107 | -------------------------------------------------------------------------------- /19-pony/main.pony: -------------------------------------------------------------------------------- 1 | use "buffered" 2 | use "collections" 3 | 4 | actor Tracker 5 | let _env: Env 6 | let _total: USize 7 | var _bad: USize = 0 8 | var _good: USize = 0 9 | var _ways: USize = 0 10 | 11 | new create(env: Env, total: USize) => 12 | _env = env 13 | _total = total 14 | 15 | be track(count: USize) => 16 | _ways = _ways + count 17 | if count > 0 then 18 | _good = _good + 1 19 | else 20 | _bad = _bad + 1 21 | end 22 | if (_good + _bad) == _total then 23 | _env.out.print("Puzzle 1: " + _good.string()) 24 | _env.out.print("Puzzle 2: " + _ways.string()) 25 | end 26 | 27 | actor Arranger 28 | let _tracker: Tracker 29 | let _patterns: HashSet[String, HashByteSeq] val 30 | let _design: String 31 | let _env: Env 32 | var _n: ISize = 0 33 | 34 | // _memo[K] = starting from offset K, how many ways to reach the end of the string 35 | var _memo: HashMap[ISize, USize, HashEq[ISize]] = _memo.create() 36 | 37 | new create( 38 | tracker: Tracker, 39 | patterns: HashSet[String, HashByteSeq] val, 40 | design: String val, 41 | env: Env 42 | ) => 43 | _tracker = tracker 44 | _patterns = patterns 45 | _design = design 46 | _env = env 47 | 48 | // directly using _design.isize() fails for some reason 49 | _n = _design.size().isize() 50 | 51 | // note: if this isn't fast enough, the cycle detector will assume it's blocked and kill it 52 | // meaning we'll have at least one missing actor and the tracker will never complete. 53 | // there is almost definitely a better way of doing this that i don't have the time to figure out 54 | // maybe by making work recursive? 55 | // or by having an active variable here and sending a periodic ping from the main actor? 56 | be work() => 57 | _memo = HashMap[ISize, USize, HashEq[ISize]] 58 | _attempt_design(0) 59 | _tracker.track(_memo.get_or_else(0, 0)) 60 | 61 | fun ref _attempt_design(offset: ISize) => 62 | if (offset == _n) or _memo.contains(offset) then 63 | return 64 | end 65 | 66 | var count: USize = 0 67 | for right in Range[ISize](offset + 1, _n.min(offset + 9) + 1) do 68 | let substr: String val = _design.substring(offset, right) 69 | if _patterns.contains(substr) then 70 | _attempt_design(right) 71 | let right_count = _memo.get_or_else(right, 1) 72 | count = count + right_count 73 | end 74 | end 75 | 76 | _memo.update(offset, count) 77 | 78 | class Notify is InputNotify 79 | let _env: Env 80 | var _raw: String iso = _raw.create() 81 | 82 | new create(env: Env) => 83 | _env = env 84 | 85 | fun ref apply(data: Array[U8] iso) => 86 | // can't use the reader here because the chunk might end in the middle of a design 87 | _raw.append(consume data) 88 | 89 | fun ref dispose() => 90 | // destructive read 91 | let tmp: String iso = tmp.create() 92 | let raw: String val = _raw = consume tmp 93 | 94 | let patterns: HashSet[String, HashByteSeq] iso = patterns.create() 95 | let designs: Array[String] = designs.create() 96 | 97 | let reader = Reader 98 | reader.append(raw) 99 | try 100 | for pattern in reader.line()?.split(", ").values() do 101 | patterns.set(pattern) 102 | end 103 | reader.line()? 104 | while true do 105 | let line = reader.line()? 106 | designs.push(consume line) 107 | end 108 | end 109 | 110 | let patterns_read: HashSet[String, HashByteSeq] val = consume patterns 111 | let tracker = Tracker(_env, designs.size()) 112 | for design in designs.values() do 113 | Arranger(tracker, patterns_read, design, _env).work() 114 | end 115 | 116 | actor Main 117 | new create(env: Env) => 118 | // need to read patterns in a single chunk 119 | env.input(recover Notify(env) end, 2048) 120 | -------------------------------------------------------------------------------- /24-ocaml/main.ml: -------------------------------------------------------------------------------- 1 | #load "str.cma" ;; 2 | 3 | type wiremap = (string, bool) Hashtbl.t 4 | type gatedef = { op: string; a: string; b: string; out: string } 5 | 6 | exception Invalid_gate ;; 7 | exception Invalid_operation ;; 8 | 9 | (* Mutates wires *) 10 | let rec read_wires (wires : wiremap) : wiremap = 11 | let line = read_line() in 12 | match Str.split (Str.regexp ": ") line with 13 | | [w; v] -> 14 | Hashtbl.add wires w (String.equal v "1") ; 15 | read_wires wires 16 | | _ -> wires ;; 17 | 18 | let rec read_gates (gates : gatedef list) : gatedef list = 19 | try 20 | let line = read_line() in 21 | match String.split_on_char ' ' line with 22 | | [a; op; b; _arrow; out] -> 23 | read_gates ({ op = op; a = a; b = b; out = out } :: gates) 24 | | _ -> raise Invalid_gate ; 25 | with End_of_file -> gates ;; 26 | 27 | let do_op (op : string) (a : bool) (b : bool) = 28 | match op with 29 | | "AND" -> a && b 30 | | "OR" -> a || b 31 | | "XOR" -> a <> b 32 | | _ -> raise Invalid_operation ;; 33 | 34 | (* Mutates wires. Returns whether to keep the gate. *) 35 | let maybe_evaluate_gate (wires : wiremap) (gate : gatedef) : bool = 36 | match (Hashtbl.find_opt wires gate.a, Hashtbl.find_opt wires gate.b) with 37 | | (Some a, Some b) -> 38 | let res = do_op gate.op a b in 39 | Hashtbl.add wires gate.out res ; 40 | false 41 | | _ -> true ;; 42 | 43 | (* Mutates wires *) 44 | let rec evaluate_gates (wires : wiremap) (gates : gatedef list) : unit = 45 | match gates with 46 | | [] -> () 47 | | _ -> 48 | let new_gates = List.filter (maybe_evaluate_gate wires) gates in 49 | evaluate_gates wires new_gates ;; 50 | 51 | let wire_num (prefix : string) (wire : string) (value : bool) (acc : int) : int = 52 | if value && (String.starts_with ~prefix:prefix wire) then 53 | let exp = int_of_string (String.sub wire 1 2) in 54 | acc + Int.shift_left 1 exp 55 | else acc ;; 56 | 57 | let full_wire_num (prefix : string) (wires : wiremap) : int = 58 | Hashtbl.fold (wire_num prefix) wires 0 ;; 59 | 60 | let rec indent (level : int) (acc : string) : string = 61 | if level == 0 then acc 62 | else indent (level - 1) (acc ^ " ") 63 | 64 | (* 65 | All wires (excepting 1, 2, and 45) have a evaluation tree with the below form: 66 | 67 | zYY 68 | / \ 69 | AAA XOR BBB 70 | / \ / \ 71 | CCC OR DDD xYY XOR yYY 72 | / \ / \ 73 | EEE AND FFF xXX AND yXX 74 | 75 | where - YY is the current output wire number, and XX is the previous one (YY - 1). 76 | - EEE/FFF are the AAA/BBB of the previous wire (zXX = EEE XOR FFF). 77 | Our goal is to find trees that do not follow this form. 78 | This makes the assumption that the system of input gates is formed optimally (i.e. they all have this form). 79 | Since there are only 4 pairs of gates, it's faster to just inspect/fix manually. 80 | Hence, this program won't actually print out the answer. 81 | *) 82 | let rec print_gate_deps (gates : gatedef list) (level : int) (target : string) : unit = 83 | if level <= 3 then ( 84 | Printf.printf "%s%s" (indent level "") target ; 85 | match List.find_opt (fun g -> String.equal g.out target) gates with 86 | | Some el -> 87 | Printf.printf " %s\n" el.op ; 88 | print_gate_deps gates (level + 1) el.a ; 89 | print_gate_deps gates (level + 1) el.b 90 | | None -> Printf.printf "\n" 91 | ) ;; 92 | 93 | let () = 94 | let wires = read_wires (Hashtbl.create 128) in 95 | let gates = read_gates [] in 96 | evaluate_gates wires gates ; 97 | let puzzle1 = full_wire_num "z" wires in 98 | Printf.printf "Puzzle 1: %d\n" puzzle1 ; 99 | Printf.printf "Puzzle 2: \n" ; 100 | gates 101 | |> List.map (fun g -> g.out) 102 | |> List.filter (String.starts_with ~prefix:"z") 103 | |> List.sort String.compare 104 | |> List.iter (print_gate_deps gates 0) ; 105 | let x = full_wire_num "x" wires in 106 | let y = full_wire_num "y" wires in 107 | Printf.printf " x: %d\n" x ; 108 | Printf.printf " y: %d\n" y ; 109 | Printf.printf "Expect: %d\n" (x + y) ; 110 | Printf.printf "Actual: %d\n" puzzle1; 111 | Printf.printf "Match?: %b\n" (x + y == puzzle1) ;; 112 | -------------------------------------------------------------------------------- /21-gleam/main.gleam: -------------------------------------------------------------------------------- 1 | import gleam/io 2 | import gleam/int 3 | import gleam/bool 4 | import gleam/pair 5 | import gleam/list 6 | import gleam/dict.{type Dict} 7 | import gleam/string 8 | import gleam/erlang 9 | import gleam/result 10 | 11 | type Coord = #(Int, Int) 12 | type Keypad = List(#(String, Coord)) 13 | type Cache = Dict(#(Int, Coord, Coord), Int) 14 | 15 | const int_max: Int = 9223372036854775807 16 | const num_keypad: Keypad = [ 17 | #("7", #(0, 0)), 18 | #("8", #(1, 0)), 19 | #("9", #(2, 0)), 20 | #("4", #(0, 1)), 21 | #("5", #(1, 1)), 22 | #("6", #(2, 1)), 23 | #("1", #(0, 2)), 24 | #("2", #(1, 2)), 25 | #("3", #(2, 2)), 26 | #("0", #(1, 3)), 27 | #("A", #(2, 3)), 28 | ] 29 | const arrow_keypad: Keypad = [ 30 | #("^", #(1, 0)), 31 | #("A", #(2, 0)), 32 | #("<", #(0, 1)), 33 | #("v", #(1, 1)), 34 | #(">", #(2, 1)), 35 | ] 36 | 37 | fn seq_cost( 38 | cache: Cache, 39 | code: String, 40 | code_keypad: Keypad, 41 | robot: Int, 42 | num_robots: Int, 43 | ) -> #(Int, Cache) { 44 | use <- bool.guard(code == "", #(1, cache)) 45 | 46 | let keypad = dict.from_list(code_keypad) 47 | let coords_res = 48 | string.append("A", code) 49 | |> string.to_graphemes() 50 | |> list.map(fn (c) { dict.get(keypad, c) }) 51 | |> result.all() 52 | 53 | case coords_res { 54 | Ok(coords) -> { 55 | let #(cache, costs) = 56 | coords 57 | |> list.window_by_2() 58 | |> list.map_fold(cache, fn (cache, pair) { 59 | let #(cost, cache) = move_cost(cache, robot - 1, pair.0, pair.1, num_robots) 60 | #(cache, cost) 61 | }) 62 | #(costs |> int.sum(), cache) 63 | } 64 | Error(_) -> { 65 | io.debug("Failed to parse coords") 66 | #(0, cache) 67 | } 68 | } 69 | } 70 | 71 | fn move_cost(cache: Cache, robot: Int, from: Coord, to: Coord, num_robots: Int) -> #(Int, Cache) { 72 | let cache_key = #(robot, from, to) 73 | use <- bool.guard( 74 | dict.has_key(cache, cache_key), 75 | #(dict.get(cache, cache_key) |> result.unwrap(0), cache), 76 | ) 77 | 78 | let #(sx, sy) = from 79 | let #(ex, ey) = to 80 | let dx = ex - sx 81 | let dy = ey - sy 82 | 83 | let adx = int.absolute_value(dx) 84 | let ady = int.absolute_value(dy) 85 | use <- bool.guard( 86 | robot == 0, 87 | #(adx + ady + 1, dict.insert(cache, cache_key, adx + ady + 1)) 88 | ) 89 | 90 | let h_seq = case dx > 0 { 91 | True -> ">" 92 | False -> "<" 93 | } |> string.repeat(adx) 94 | 95 | let v_seq = case dy > 0 { 96 | True -> "v" 97 | False -> "^" 98 | } |> string.repeat(ady) 99 | 100 | let edge_y = case robot == num_robots - 1 { 101 | True -> 3 102 | False -> 0 103 | } 104 | 105 | let h_prio_seq = string.concat([h_seq, v_seq, "A"]) 106 | let v_prio_seq = string.concat([v_seq, h_seq, "A"]) 107 | 108 | let #(h_cost, h_cache) = case #(ex, sy) == #(0, edge_y) { 109 | True -> #(int_max, cache) 110 | False -> seq_cost(cache, h_prio_seq, arrow_keypad, robot, num_robots) 111 | } 112 | 113 | let #(v_cost, v_cache) = case #(sx, ey) == #(0, edge_y) { 114 | True -> #(int_max, cache) 115 | False -> seq_cost(cache, v_prio_seq, arrow_keypad, robot, num_robots) 116 | } 117 | 118 | case h_cost < v_cost { 119 | True -> #(h_cost, dict.insert(h_cache, cache_key, h_cost)) 120 | False -> #(v_cost, dict.insert(v_cache, cache_key, v_cost)) 121 | } 122 | } 123 | 124 | fn complexity(codes: List(String), num_robots: Int) -> Int { 125 | codes 126 | |> list.map_fold(dict.new(), fn (cache, code) { 127 | let code_num = 128 | string.drop_end(code, 1) 129 | |> int.parse() 130 | |> result.unwrap(0) 131 | 132 | let #(cost, cache) = seq_cost(cache, code, num_keypad, num_robots, num_robots) 133 | #(cache, cost * code_num) 134 | }) 135 | |> pair.second() 136 | |> int.sum() 137 | } 138 | 139 | pub fn main() { 140 | let codes = 141 | list.range(1, 5) 142 | |> list.map(fn (_) { 143 | erlang.get_line("") 144 | |> result.unwrap("") 145 | |> string.trim() 146 | }) 147 | 148 | let puzzle1 = complexity(codes, 3) |> int.to_string() 149 | string.append("Puzzle 1: ", puzzle1) 150 | |> io.println() 151 | 152 | let puzzle2 = complexity(codes, 26) |> int.to_string() 153 | string.append("Puzzle 2: ", puzzle2) 154 | |> io.println() 155 | } 156 | -------------------------------------------------------------------------------- /17-hack/main.hack: -------------------------------------------------------------------------------- 1 | use namespace HH\Lib\C; 2 | use namespace HH\Lib\Str; 3 | use namespace HH\Lib\Vec; 4 | use namespace HH\Lib\Math; 5 | 6 | use HH\Lib\Ref; 7 | 8 | <<__EntryPoint>> 9 | function main(): void { 10 | $A = new Ref(read_register()); 11 | $B = new Ref(read_register()); 12 | $C = new Ref(read_register()); 13 | fgets(STDIN); 14 | 15 | $raw_program = Str\trim(fgets(STDIN)); 16 | $raw_program = Str\split($raw_program, ": ")[1]; 17 | $program = Str\split($raw_program, ","); 18 | $program = Vec\Map($program, $v ==> Str\to_int($v)); 19 | 20 | $b = $B->get(); 21 | $c = $C->get(); 22 | 23 | $puzzle1 = interpret($A, $B, $C, $program); 24 | echo "Puzzle 1: " . $puzzle1 . "\n"; 25 | 26 | // bsearch approx lower/upper bounds based on output length. 27 | // => (35_000_000_000_000, 290_000_000_000_000) 28 | // outputs are "grouped" by suffix; look for groups in increments of 1e9 29 | // that cause the output to share a suffix with my input program. 30 | // repeat with longer suffix/smaller increment to find new 31 | // lower/upper bounds until the machine can reasonably brute force. 32 | // => (164376000000000, 164927000000000), 1e8, taillen 3 33 | // => (164376950000000, 164926750000000), 5e7, taillen 3 34 | // => (164514420000000, 164583150000000), 1e7, taillen 4 35 | // eventually the suffixes split 36 | // => (164514425000000, 164540200000000), 5e6, taillen 5 37 | // => (164514427000000, 164516575000000), 5e5, taillen 6 38 | // => (164515366800000, 164516457350000), 5e4, taillen 8 39 | // => (164515377315000, 164515379415000), 5e3, taillen 9 40 | // => (164515378364400, 164515378626600), 100, taillen 10 41 | // => DEAD 42 | // => (164516451055000, 164516455255000), 5e3, taillen 9 43 | // => (164516454346700, 164516454465600), 100, taillen 12 44 | // => FOUND 45 | // => (164515366830000, 164515501050000), 1e4, taillen 7 46 | // => (164515377315000, 164515379413000), 1e3, taillen 9 47 | // => (164515378364400, 164515378626600), 100, taillen 10 48 | // => DEAD 49 | // => (164557375000000, 164565970000000), 5e6, taillen 5 50 | // => (164558450000000, 164559525000000), 1e6, taillen 6 51 | // => DEAD (no results for taillen 7) 52 | 53 | $a = 164516454346700; 54 | while ($a <= 164516454465600) { 55 | $A->set($a); 56 | $B->set($b); 57 | $C->set($c); 58 | $output = interpret($A, $B, $C, $program); 59 | // echo "$a: $output\n"; 60 | if ($output == $raw_program) { 61 | break; 62 | } 63 | $a += 1; 64 | } 65 | echo "Puzzle 2: " . $a . "\n"; 66 | } 67 | 68 | function read_register(): int { 69 | $line = Str\trim(fgets(STDIN)); 70 | $value = Str\split($line, ": ")[1]; 71 | return Str\to_int($value); 72 | } 73 | 74 | // mutates $A, $B, $C 75 | function interpret(Ref $A, Ref $B, Ref $C, HH\vec $program): string { 76 | $combo = (int $op) ==> { 77 | switch ($op) { 78 | case 0: 79 | case 1: 80 | case 2: 81 | case 3: 82 | return $op; 83 | case 4: 84 | return $A->get(); 85 | case 5: 86 | return $B->get(); 87 | case 6: 88 | return $C->get(); 89 | default: 90 | echo "bad op $op\n"; 91 | return; 92 | } 93 | }; 94 | 95 | $outputs = vec[]; 96 | $i = 0; 97 | $N = C\count($program); 98 | while ($i < $N) { 99 | $inst = $program[$i]; 100 | $op = $program[$i + 1]; 101 | 102 | if ($inst == 0) { 103 | // adv 104 | $num = $A->get(); 105 | $den = 2 ** $combo($op); 106 | $A->set(Math\int_div($num, $den)); 107 | } else if ($inst == 1) { 108 | // bxl 109 | $B->set($B->get() ^ $op); 110 | } else if ($inst == 2) { 111 | // bst 112 | $B->set($combo($op) % 8); 113 | } else if ($inst == 3) { 114 | // jnz 115 | if ($A->get() != 0) { 116 | $i = $op; 117 | continue; 118 | } 119 | } else if ($inst == 4) { 120 | // bxc 121 | $B->set($B->get() ^ $C->get()); 122 | } else if ($inst == 5) { 123 | // out 124 | $outputs[] = $combo($op) % 8; 125 | } else if ($inst == 6) { 126 | // bdv 127 | $num = $A->get(); 128 | $den = 2 ** $combo($op); 129 | $B->set(Math\int_div($num, $den)); 130 | } else { 131 | // cdv 132 | $num = $A->get(); 133 | $den = 2 ** $combo($op); 134 | $C->set(Math\int_div($num, $den)); 135 | } 136 | $i += 2; 137 | } 138 | 139 | return Str\join($outputs, ","); 140 | } 141 | -------------------------------------------------------------------------------- /16-d/main.d: -------------------------------------------------------------------------------- 1 | import std.string; 2 | import std.typecons; 3 | import std.container.binaryheap; 4 | import std.stdio : writeln, write, readln; 5 | import std.algorithm.comparison : min; 6 | import std.algorithm : canFind; 7 | import std.container : DList; 8 | 9 | alias Point = Tuple!(int, int); // (r, c) 10 | alias DirectedPoint = Tuple!(int, int, int); // (r, c, dir) 11 | alias WeightedDirectedPoint = Tuple!(int, int, int, int); // (r, c, dir, weight) 12 | 13 | int posmod(int a, int b) { 14 | int res = a % b; 15 | return (res < 0) ? (res + b) : res; 16 | } 17 | 18 | void main() { 19 | string line; 20 | string[] grid; 21 | while ((line = readln.chomp) !is null) { 22 | grid ~= line; 23 | } 24 | 25 | int sr, sc; 26 | int er, ec; 27 | foreach (r, string row; grid) { 28 | foreach (c, char cell; row) { 29 | if (cell == 'S') { 30 | sr = cast(int) r; 31 | sc = cast(int) c; 32 | } else if (cell == 'E') { 33 | er = cast(int) r; 34 | ec = cast(int) c; 35 | } 36 | } 37 | } 38 | 39 | int rows = cast(int) grid.length; 40 | int cols = cast(int) grid[0].length; 41 | 42 | Tuple!(int, int)[4] directions = [tuple(0, 1), tuple(1, 0), tuple(0, -1), tuple(-1, 0)]; 43 | WeightedDirectedPoint[][DirectedPoint] adj; 44 | 45 | foreach (ri, string row; grid) { 46 | int r = cast(int) ri; 47 | foreach (ci, char cell; row) { 48 | int c = cast(int) ci; 49 | if (cell == '#') { 50 | continue; 51 | } 52 | 53 | foreach (int d; 0 .. 4) { 54 | // get possible directions and weights, given current direction 55 | foreach (dw; [tuple(d, 1), tuple(posmod(d + 1, 4), 1001), tuple(posmod(d - 1, 4), 1001)]) { 56 | int ad = dw[0]; 57 | int weight = dw[1]; 58 | 59 | auto deltas = directions[ad]; 60 | int tr = r + deltas[0]; 61 | int tc = c + deltas[1]; 62 | if (0 <= tr && tr < rows && 0 <= tc && tc < cols && grid[tr][tc] != '#') { 63 | adj.require(tuple(r, c, d)) ~= tuple(tr, tc, ad, weight); 64 | } 65 | } 66 | } 67 | } 68 | } 69 | 70 | int[DirectedPoint] dist; 71 | shortestDistances(sr, sc, 0, dist, adj); 72 | 73 | int endDist = int.max; 74 | int endDirection = 0; 75 | foreach (int d; 0 .. 4) { 76 | int currDist = dist.get(tuple(er, ec, d), int.max); 77 | if (currDist < endDist) { 78 | endDist = currDist; 79 | endDirection = (d + 2) % 4; 80 | } 81 | } 82 | writeln("Puzzle 1: ", endDist); 83 | 84 | int[DirectedPoint] revDist; 85 | shortestDistances(er, ec, endDirection, revDist, adj); 86 | 87 | int[200][200] merged = int.max; 88 | foreach (ri, string row; grid) { 89 | int r = cast(int) ri; 90 | foreach (ci, char cell; row) { 91 | int c = cast(int) ci; 92 | if (cell == '#') { 93 | continue; 94 | } 95 | 96 | int minForward = int.max, minReverse = int.max; 97 | foreach (int d; 0 .. 4) { 98 | minForward = min(minForward, dist.get(tuple(r, c, d), int.max)); 99 | minReverse = min(minReverse, revDist.get(tuple(r, c, d), int.max)); 100 | } 101 | merged[r][c] = minForward + minReverse; 102 | } 103 | } 104 | 105 | bool[Point] bestCells; 106 | auto q = DList!(Point[])([[tuple(sr, sc)]]); 107 | while (!q.empty()) { 108 | auto path = q.front(); 109 | q.removeFront(); 110 | int r = path[path.length - 1][0]; 111 | int c = path[path.length - 1][1]; 112 | if (r == er && c == ec) { 113 | foreach (Point p; path) { 114 | bestCells[p] = true; 115 | } 116 | continue; 117 | } 118 | 119 | foreach (Tuple!(int, int) dir; directions) { 120 | int tr = r + dir[0]; 121 | int tc = c + dir[1]; 122 | if ( 123 | 0 <= tr && tr < rows && 0 <= tc && tc < cols && 124 | grid[tr][tc] != '#' && merged[tr][tc] <= endDist && !path.canFind(tuple(tr, tc)) 125 | ) { 126 | q.insertBack(path ~ tuple(tr, tc)); 127 | } 128 | } 129 | } 130 | 131 | writeln("Puzzle 2: ", bestCells.length); 132 | } 133 | 134 | 135 | void shortestDistances( 136 | int sr, int sc, int sd, 137 | ref int[DirectedPoint] dist, 138 | ref WeightedDirectedPoint[][DirectedPoint] adj 139 | ) { 140 | dist[tuple(sr, sc, sd)] = 0; 141 | 142 | auto pq = heapify!"a > b"([tuple(0, sr, sc, sd)]); 143 | while (!pq.empty()) { 144 | auto front = pq.front(); 145 | pq.removeFront(); 146 | 147 | int score = front[0], r = front[1], c = front[2], d = front[3]; 148 | foreach (WeightedDirectedPoint adjPoint; adj.get(tuple(r, c, d), [])) { 149 | int tr = adjPoint[0], tc = adjPoint[1], ad = adjPoint[2], weight = adjPoint[3]; 150 | if (score + weight <= dist.get(tuple(tr, tc, ad), int.max)) { 151 | dist[tuple(tr, tc, ad)] = score + weight; 152 | pq.insert(tuple(score + weight, tr, tc, ad)); 153 | } 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /15-swift/main.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | typealias Grid = Array> 3 | 4 | var origGrid = Grid() 5 | while let line = readLine() { 6 | if line.isEmpty { 7 | break 8 | } 9 | let row = Array(line) 10 | origGrid.append(row) 11 | } 12 | 13 | var moves = "" 14 | while let line = readLine() { 15 | moves += line 16 | } 17 | 18 | let directions = [ 19 | Character("<"): (0, -1), 20 | Character("^"): (-1, 0), 21 | Character(">"): (0, 1), 22 | Character("v"): (1, 0), 23 | ] 24 | 25 | func startPos(grid: inout Grid) -> (Int, Int) { 26 | for (r, row) in grid.enumerated() { 27 | if let idx = row.firstIndex(of: "@") { 28 | return (r, row.distance(from: row.startIndex, to: idx)) 29 | } 30 | } 31 | return (-1, -1) 32 | } 33 | 34 | func freeDist(grid: inout Grid, _ r: Int, _ c: Int, _ dr: Int, _ dc: Int) -> Int { 35 | var dist = 1 36 | while true { 37 | let cell = grid[r + dr * dist][c + dc * dist] 38 | if cell == "#" { 39 | return 0 40 | } 41 | if cell == "." { 42 | return dist 43 | } 44 | dist += 1 45 | } 46 | } 47 | 48 | func puzzle1() { 49 | var grid = origGrid 50 | var (ar, ac) = startPos(grid: &grid) 51 | 52 | func makeMove(move: String.Element) { 53 | let (dr, dc) = directions[move]! 54 | let dist = freeDist(grid: &grid, ar, ac, dr, dc) 55 | if dist == 0 { 56 | return 57 | } 58 | 59 | grid[ar][ac] = "." 60 | grid[ar + dr][ac + dc] = "@" 61 | if dist > 1 { 62 | grid[ar + dr * dist][ac + dc * dist] = "O" 63 | } 64 | ar += dr 65 | ac += dc 66 | } 67 | 68 | moves.forEach(makeMove) 69 | 70 | var total = 0 71 | for (r, row) in grid.enumerated() { 72 | for (c, cell) in row.enumerated() { 73 | if cell == "O" { 74 | total += 100 * r + c 75 | } 76 | } 77 | } 78 | print("Puzzle 1: ", total) 79 | } 80 | 81 | func puzzle2() { 82 | var grid = Grid() 83 | for origRow in origGrid { 84 | var row = Array() 85 | for cell in origRow { 86 | if cell == "O" { 87 | row.append(contentsOf: "[]") 88 | } else if cell == "@" { 89 | row.append(contentsOf: "@.") 90 | } else { 91 | row.append(contentsOf: [cell, cell]) 92 | } 93 | } 94 | grid.append(row) 95 | } 96 | var (ar, ac) = startPos(grid: &grid) 97 | let cols = grid[0].count 98 | 99 | // there's a decent amount of duplicated code but in the interest of time, they will remain duplicated 100 | func makeMove(move: String.Element) { 101 | let (dr, dc) = directions[move]! 102 | let dist = freeDist(grid: &grid, ar, ac, dr, dc) 103 | if dist == 0 { 104 | return 105 | } 106 | 107 | var didPush = true 108 | if dist > 1 { 109 | if dr == 0 { 110 | // horizontal movement 111 | for i in (2 ..< dist + 1).reversed() { 112 | grid[ar][ac + i * dc] = grid[ar][ac + i * dc - dc] 113 | } 114 | } else { 115 | // vertical movement 116 | var stack = Array<(Int, Int)>() 117 | if "[]".contains(grid[ar + dr][ac]) { 118 | stack.append((ar + dr, ac)) 119 | } 120 | 121 | var seen = Set() 122 | var toPush = Array<(Int, Int)>() 123 | while !stack.isEmpty { 124 | let (r, c) = stack.popLast()! 125 | 126 | // enqueue vertically adjacent boxes 127 | let left = if grid[r][c] == "[" { c } else { c - 1 } 128 | let vAdjLeft = grid[r + dr][left] 129 | let vAdjRight = grid[r + dr][left + 1] 130 | 131 | if "[]".contains(vAdjLeft) { stack.append((r + dr, left)) } 132 | if vAdjRight == "[" { stack.append((r + dr, left + 1)) } 133 | if vAdjLeft == "#" || vAdjRight == "#" { 134 | toPush.removeAll() 135 | didPush = false 136 | break 137 | } 138 | 139 | // hash coordinate (flattened index) 140 | if !seen.contains(r * cols + c) { 141 | seen.insert(r * cols + c) 142 | toPush.append((r, c)) 143 | } 144 | } 145 | 146 | // sort ascending (top down) if pushing up, else descending (bottom up) 147 | toPush.sort(by: { dr * $0.0 < dr * $1.0 }) 148 | while !toPush.isEmpty { 149 | let (r, c) = toPush.popLast()! 150 | let left = if grid[r][c] == "[" { c } else { c - 1 } 151 | grid[r + dr][left] = "[" 152 | grid[r + dr][left + 1] = "]" 153 | grid[r][left] = "." 154 | grid[r][left + 1] = "." 155 | } 156 | } 157 | } 158 | 159 | if didPush { 160 | grid[ar][ac] = "." 161 | grid[ar + dr][ac + dc] = "@" 162 | ar += dr 163 | ac += dc 164 | } 165 | } 166 | 167 | moves.forEach(makeMove) 168 | 169 | var total = 0 170 | for (r, row) in grid.enumerated() { 171 | for (c, cell) in row.enumerated() { 172 | if cell == "[" { 173 | total += 100 * r + c 174 | } 175 | } 176 | } 177 | print("Puzzle 2: ", total) 178 | } 179 | 180 | puzzle1() 181 | puzzle2() 182 | -------------------------------------------------------------------------------- /3-nasm/main.asm: -------------------------------------------------------------------------------- 1 | ; nasm reference http://home.myfairpoint.net/fbkotler/nasmdocc.html 2 | ; registers https://www.cs.uaf.edu/2017/fall/cs301/reference/x86_64.html 3 | ; syscall opcodes https://filippo.io/linux-syscall-table/ 4 | 5 | section .data 6 | donttxt: db "don't()" 7 | 8 | section .bss 9 | input: resb 32 * 1024 10 | 11 | section .text 12 | global _start 13 | 14 | ; rdi = long to print 15 | ; noreturn 16 | lngprint: 17 | push rax 18 | push rbx 19 | push rdx 20 | push rsi 21 | push rdi 22 | 23 | mov rax, rdi 24 | mov rdi, rsp 25 | mov rbx, 10 26 | 27 | dec rdi 28 | mov rdx, 10 ; newline 29 | mov [rdi], dl 30 | .nextdigit 31 | cmp rax, 0 32 | je .end 33 | 34 | dec rdi 35 | mov rdx, 0 36 | div rbx ; rax = rax // 10, rdx = rax % 10 37 | add dl, "0" ; dl is byte of rdx 38 | mov [rdi], dl 39 | 40 | jmp .nextdigit 41 | .end: 42 | mov rdx, rsp 43 | sub rdx, rdi 44 | mov rsi, rdi 45 | mov rdi, 1 46 | mov rax, 1 47 | syscall 48 | 49 | pop rdi 50 | pop rsi 51 | pop rdx 52 | pop rbx 53 | pop rax 54 | ret 55 | 56 | ; rdi = ptr to string 57 | ; rsi = char to end on 58 | ; if successful, rdi is moved to the character after the end character 59 | ; otherwise, rdi is moved to the first invalid character 60 | ; returns the parsed number or -1 61 | parsenum: 62 | push rdx 63 | push rbx 64 | mov rbx, 0 ; resultant number 65 | .nextchr: 66 | cmp byte [rdi], sil ; sil is byte of rsi 67 | je .end 68 | 69 | cmp byte [rdi], "0" 70 | jl .badchr 71 | cmp byte [rdi], "9" 72 | jg .badchr 73 | 74 | mov rax, 10 75 | mul rbx ; rax = rbx * 10 76 | mov rbx, rax ; rbx = ^ 77 | 78 | mov al, byte [rdi] ; al is byte of rax 79 | sub al, "0" 80 | movzx rdx, al 81 | add rbx, rdx 82 | 83 | inc rdi 84 | jmp .nextchr 85 | .badchr: 86 | mov rax, -1 87 | pop rbx 88 | pop rdx 89 | ret 90 | .end: 91 | mov rax, rbx 92 | inc rdi 93 | pop rbx 94 | pop rdx 95 | ret 96 | 97 | ; program entry point 98 | _start: 99 | mov rdx, 32 * 1024 100 | mov rsi, input 101 | mov rdi, 0 102 | mov rax, 0 103 | syscall 104 | 105 | puzzle1: 106 | mov rbx, 0 ; rbx holds the sum of the valid mul operations 107 | mov rcx, input ; rcx holds our current position in the string 108 | .nextstate: 109 | cmp byte [rcx], 0 ; check end of input 110 | je .printresult 111 | 112 | cmp dword [rcx], "mul(" 113 | je .parsenums 114 | 115 | inc rcx ; move onto next character 116 | jmp .nextstate 117 | .parsenums: 118 | add rcx, 4 ; move forward 4 characters -- the length of "mul(" 119 | 120 | mov rdi, rcx 121 | mov rsi, "," 122 | call parsenum 123 | mov rcx, rdi 124 | 125 | cmp rax, -1 126 | je .nextstate 127 | 128 | mov rdx, rax ; store the first number in rdx 129 | 130 | mov rdi, rcx 131 | mov rsi, ")" 132 | call parsenum 133 | mov rcx, rdi ; move ptr to location returned by parsenum 134 | 135 | cmp rax, -1 136 | je .nextstate 137 | 138 | mul rdx 139 | add rbx, rax 140 | jmp .nextstate 141 | .printresult: 142 | mov rdi, rbx 143 | call lngprint 144 | 145 | puzzle2: ; most of this code is the same as the first part 146 | mov rbx, 0 ; rbx holds the sum of the enabled and valid mul operations 147 | mov rcx, input ; rcx holds our current position in the string 148 | mov r8, 1 ; r8 is a boolean of whether muls are enabled 149 | .nextstate: 150 | cmp byte [rcx], 0 ; check end of input 151 | je .printresult 152 | 153 | cmp r8, 1 154 | je .mulenabled 155 | 156 | cmp dword [rcx], "do()" 157 | jne .incnext ; if muldisabled and we don't see do(), go to next character 158 | 159 | add rcx, 4 ; if we see do(), jump 4 and toggle rdi 160 | mov r8, 1 161 | .mulenabled 162 | push rcx 163 | mov rdi, rcx 164 | mov rsi, donttxt 165 | mov rcx, 6 166 | repe cmpsb 167 | pop rcx 168 | je .disablemul 169 | 170 | cmp dword [rcx], "mul(" 171 | je .parsenums 172 | .incnext 173 | inc rcx ; move onto next character 174 | jmp .nextstate 175 | .disablemul 176 | mov r8, 0 177 | jmp .incnext 178 | .parsenums: 179 | add rcx, 4 ; move forward 4 characters -- the length of "mul(" 180 | 181 | mov rdi, rcx 182 | mov rsi, "," 183 | call parsenum 184 | mov rcx, rdi 185 | 186 | cmp rax, -1 187 | je .nextstate 188 | 189 | mov rdx, rax ; store the first number in rdx 190 | 191 | mov rdi, rcx 192 | mov rsi, ")" 193 | call parsenum 194 | mov rcx, rdi ; move ptr to location returned by parsenum 195 | 196 | cmp rax, -1 197 | je .nextstate 198 | 199 | mul rdx 200 | add rbx, rax 201 | jmp .nextstate 202 | .printresult: 203 | mov rdi, rbx 204 | call lngprint 205 | 206 | -------------------------------------------------------------------------------- /12-smalltalk/main.st: -------------------------------------------------------------------------------- 1 | "This solution is pretty cool, you should read it :)" 2 | 3 | rawGrid := stdin contents substrings '\n'. 4 | size := rawGrid size. 5 | 6 | grid := OrderedCollection new. 7 | rawGrid do: [ :rawRow | 8 | | row | 9 | row := OrderedCollection new. 10 | row addAll: rawRow. 11 | grid add: row. 12 | ]. 13 | 14 | at := [ :x :y | 15 | ( (1 <= x) & (x <= size) & (1 <= y) & (y <= size) ) 16 | ifTrue: [ (grid at: y) at: x ] 17 | ifFalse: [ -1 ]. 18 | ]. 19 | 20 | "Puzzle 1" 21 | "Straightforward BFS. Precomputes areas for puzzle 2." 22 | 23 | visited := Set new. 24 | areas := Dictionary new. 25 | adj := Array with: (0 @ 1) with: (0 @ -1) with: (1 @ 0) with: (-1 @ 0). 26 | 27 | "Separate regions are not guaranteed to have separate identifiers." 28 | "We maintain an autoincrementing ID to a duplicate region," 29 | "so we can uniquely identify a region with its identifier." 30 | aid := 0. 31 | 32 | visit := [ :start | 33 | | queue paint target area perimeter | 34 | target := at value: (start x) value: (start y). 35 | 36 | "If we haven't visited this cell before, but the region ID already exists..." 37 | paint := target. 38 | ( areas includesKey: target ) 39 | ifTrue: [ 40 | paint := aid. 41 | aid := aid + 1. 42 | ]. 43 | 44 | visited add: start. 45 | perimeter := 0. 46 | area := 1. 47 | 48 | queue := OrderedCollection new. 49 | queue addLast: start. 50 | [ queue size > 0 ] 51 | whileTrue: [ 52 | | head | 53 | head := queue removeFirst. 54 | x := head x. 55 | y := head y. 56 | 57 | "Replace after finishing a visit." 58 | (grid at: y) at: x put: paint. 59 | 60 | adj do: [ :delta | 61 | | tx ty | 62 | tx := x + (delta x). 63 | ty := y + (delta y). 64 | ( ((at value: tx value: ty) == target) ) 65 | ifTrue: [ 66 | ( (visited includes: (tx @ ty)) not ) 67 | ifTrue: [ 68 | visited add: (tx @ ty). 69 | queue addLast: (tx @ ty). 70 | area := area + 1. 71 | ] 72 | ] 73 | ifFalse: [ perimeter := perimeter + 1 ]. 74 | ]. 75 | ]. 76 | 77 | areas at: paint put: area. 78 | area * perimeter. 79 | ]. 80 | 81 | puzzle1 := 0. 82 | 1 to: size do: [ :y | 83 | 1 to: size do: [ :x | 84 | ( (visited includes: (x @ y)) ) 85 | ifFalse: [ puzzle1 := puzzle1 + (visit value: (x @ y)) ]. 86 | ]. 87 | ]. 88 | 'Puzzle 1: ' display. 89 | puzzle1 displayNl. 90 | 91 | "Puzzle 2" 92 | "We perform row-wise and column-wise scans, checking for existing edges by looking at a 2 by 2 box." 93 | "Code for horizontal and vertical scans are mostly the same." 94 | "XX" 95 | "XY" 96 | 97 | puzzle2 := 0. 98 | edges := Dictionary new. 99 | areas keys do: [ :k | edges at: k put: 0 ]. 100 | 101 | "Horizontal scans -- vertical edges" 102 | 1 to: size do: [ :y | 103 | | first last aboveFirst aboveLast | 104 | first := at value: 1 value: y. 105 | aboveFirst := at value: 1 value: (y - 1). 106 | ( first ~~ aboveFirst ) 107 | ifTrue: [ edges at: first put: (edges at: first) + 1 ]. 108 | 109 | last := at value: size value: y. 110 | aboveLast := at value: size value: (y - 1). 111 | ( last ~~ aboveLast ) 112 | ifTrue: [ edges at: last put: (edges at: last) + 1 ]. 113 | 114 | 2 to: size do: [ :x | 115 | | curr prev | 116 | curr := at value: x value: y. 117 | prev := at value: (x - 1) value: y. 118 | 119 | ( curr ~~ prev ) 120 | ifTrue: [ 121 | | aboveCurr abovePrev | 122 | aboveCurr := at value: x value: (y - 1). 123 | abovePrev := at value: (x - 1) value: (y - 1). 124 | ( (abovePrev == prev) & (aboveCurr ~~ prev) ) 125 | ifFalse: [ edges at: prev put: (edges at: prev) + 1 ]. 126 | ( (aboveCurr == curr) & (abovePrev ~~ curr) ) 127 | ifFalse: [ edges at: curr put: (edges at: curr) + 1 ]. 128 | ]. 129 | ]. 130 | ]. 131 | 132 | "Vertical scans -- horizontal edges. Same logic as above." 133 | 1 to: size do: [ :x | 134 | | first last aleftFirst aleftLast | 135 | first := at value: x value: 1. 136 | aleftFirst := at value: (x - 1) value: 1. 137 | ( first ~~ aleftFirst ) 138 | ifTrue: [ edges at: first put: (edges at: first) + 1 ]. 139 | 140 | last := at value: x value: size. 141 | aleftLast := at value: (x - 1) value: size. 142 | ( last ~~ aleftLast ) 143 | ifTrue: [ edges at: last put: (edges at: last) + 1 ]. 144 | 145 | 2 to: size do: [ :y | 146 | | curr prev | 147 | curr := at value: x value: y. 148 | prev := at value: x value: (y - 1). 149 | 150 | ( curr ~~ prev ) 151 | ifTrue: [ 152 | | aleftCurr aleftPrev | 153 | aleftCurr := at value: (x - 1) value: y. 154 | aleftPrev := at value: (x - 1) value: (y - 1). 155 | ( (aleftPrev == prev) & (aleftCurr ~~ prev) ) 156 | ifFalse: [ edges at: prev put: (edges at: prev) + 1 ]. 157 | ( (aleftCurr == curr) & (aleftPrev ~~ curr) ) 158 | ifFalse: [ edges at: curr put: (edges at: curr) + 1 ]. 159 | ]. 160 | ]. 161 | ]. 162 | 163 | puzzle2 := 0. 164 | areas keysDo: [ :rid | puzzle2 := puzzle2 + ((areas at: rid) * (edges at: rid)) ]. 165 | 'Puzzle 2: ' display. 166 | puzzle2 displayNl. 167 | -------------------------------------------------------------------------------- /languages.svg: -------------------------------------------------------------------------------- 1 | Created with Raphaël 2.1.2Smalltalk7.3%Assembly6.9%D6.9%Swift6.8%Dockerfile6.4%Hack6.3%OCaml6.1%Gleam5.9%Zig5.5%Pony5.4%F#4.5%Haxe4.4%Erlang3.5%Shell3.0%PLpgSQL2.7%SCSS2.3%Pascal2.3%Nim2.2%Perl2.2%Haskell2.2%V2.2%Clojure2.2%Odin1.9%Crystal1.1% 2 | --------------------------------------------------------------------------------