├── .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 | 
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 |
2 |
--------------------------------------------------------------------------------