diff options
Diffstat (limited to 'racket')
179 files changed, 10903 insertions, 0 deletions
diff --git a/racket/aoc2015/day-01/day-01.rkt b/racket/aoc2015/day-01/day-01.rkt new file mode 100644 index 0000000..efbd02a --- /dev/null +++ b/racket/aoc2015/day-01/day-01.rkt @@ -0,0 +1,16 @@ +#lang racket +(require "../../jj-aoc.rkt") + +;; part 1 +(for/fold ([current-floor 0]) ([l (in-input-port-chars (open-day 1 2015))] [i (in-naturals)]) + (match l + [#\( (add1 current-floor)] + [#\) (sub1 current-floor)])) + +;; part 2 +(for/fold ([current-floor 0] [last-index 0] #:result (add1 last-index)) + ([l (in-input-port-chars (open-day 1 2015))] [i (in-naturals)]) + #:break (= current-floor -1) + (match l + [#\( (values (add1 current-floor) i)] + [#\) (values (sub1 current-floor) i)])) diff --git a/racket/aoc2015/day-02/day-02.rkt b/racket/aoc2015/day-02/day-02.rkt new file mode 100644 index 0000000..579fd00 --- /dev/null +++ b/racket/aoc2015/day-02/day-02.rkt @@ -0,0 +1,26 @@ +#lang racket +(require "../../jj-aoc.rkt" + threading + racket/struct) + +(struct present (l w h) #:transparent) + +(define presents + (for/list ([size-string (in-lines (open-day 2 2015))]) + (~> size-string (string-split "x") (map string->number _) (apply present _)))) + +;; part 1 +(define (paper-area p) + (define main-area (~> p struct->list (combinations 2) (map (λ~> (apply * 2 _)) _) (apply + _))) + (define slack-area (~> p struct->list (sort <) (take 2) (apply * _))) + (+ main-area slack-area)) + +(for/sum ([p (in-list presents)]) (paper-area p)) + +;; part 2 +(define (ribbon-length p) + (define ribbon-around-box (~> p struct->list (sort <) (take 2) (map (λ~> (* 2)) _) (apply + _))) + (define ribbon-for-bow (~> p struct->list (apply * _))) + (+ ribbon-around-box ribbon-for-bow)) + +(for/sum ([p (in-list presents)]) (ribbon-length p)) diff --git a/racket/aoc2015/day-03/day-03.rkt b/racket/aoc2015/day-03/day-03.rkt new file mode 100644 index 0000000..1d44955 --- /dev/null +++ b/racket/aoc2015/day-03/day-03.rkt @@ -0,0 +1,28 @@ +#lang racket +(require "../../jj-aoc.rkt" + threading + (only-in algorithms chunks-of) + racket/hash) + +(define directions + (for/list ([l (in-input-port-chars (open-day 3 2015))]) + (string->symbol (string l)))) + +(define (trace-santa dirs) + (define visits (make-hash)) + (for/fold ([x 0] [y 0] #:result visits) ([dir (in-list dirs)]) + (hash-set! visits `(,x ,y) #true) + (match dir + ['^ (values x (add1 y))] + ['v (values x (sub1 y))] + ['< (values (add1 x) y)] + ['> (values (sub1 x) y)]))) + +;; part 1 +(~> directions trace-santa hash-values length) + +;; part 2 +(~> directions (chunks-of 2) (apply map list _) (map trace-santa _) (match-define (list real robo) _)) + +(hash-union! real robo #:combine (λ _ #true)) +(~> real hash-values length) diff --git a/racket/aoc2015/day-04/day-04.rkt b/racket/aoc2015/day-04/day-04.rkt new file mode 100644 index 0000000..2c16043 --- /dev/null +++ b/racket/aoc2015/day-04/day-04.rkt @@ -0,0 +1,18 @@ +#lang racket +(require "../../jj-aoc.rkt" + threading + file/md5) + +(define secret-key (~> (open-day 4 2015) port->string string-trim)) + +(define (find-n-zeroes n) + (for/first ([i (in-naturals)] + #:when + (~>> i (~a secret-key) md5 bytes->string/utf-8 (string-prefix? _ (make-string n #\0)))) + i)) + +;; part 1 +(time (find-n-zeroes 5)) + +;; part 2 +(time (find-n-zeroes 6)) diff --git a/racket/aoc2015/day-05/day-05.rkt b/racket/aoc2015/day-05/day-05.rkt new file mode 100644 index 0000000..3449adc --- /dev/null +++ b/racket/aoc2015/day-05/day-05.rkt @@ -0,0 +1,32 @@ +#lang racket +(require "../../jj-aoc.rkt" + threading) + +(define strs (port->lines (open-day 5 2015))) + +;; part 1 +(define (at-least-three-vowels? str) + (~>> str (regexp-replace* #px"[^aeiou]" _ "") string-length (<= 3))) + +(define (at-least-one-pair? str) + (regexp-match? #px"(.)\\1{1,}" str)) + +(define (no-forbidden-pairs? str) + (~>> (list "ab" "cd" "pq" "xy") (ormap (λ~>> (string-contains? str))) not)) + +(define (nice? str) + (~>> (list at-least-three-vowels? at-least-one-pair? no-forbidden-pairs?) (andmap (λ (f) (f str))))) + +(count nice? strs) + +;; part 2 +(define (repeating-pair? str) + (regexp-match? #px"(..).*\\1" str)) + +(define (symmetry? str) + (regexp-match? #px"(.).\\1" str)) + +(define (new-nice? str) + (~>> (list repeating-pair? symmetry?) (andmap (λ (f) (f str))))) + +(count new-nice? strs) diff --git a/racket/aoc2015/day-06/day-06.rkt b/racket/aoc2015/day-06/day-06.rkt new file mode 100644 index 0000000..d2eed08 --- /dev/null +++ b/racket/aoc2015/day-06/day-06.rkt @@ -0,0 +1,49 @@ +#lang racket +(require "../../jj-aoc.rkt" + threading) + +(struct instruction (todo x1 y1 x2 y2) #:transparent) + +(define (make-instruction lst) + (apply instruction (string->symbol (first lst)) (map string->number (rest lst)))) + +(define instructions + (for/list ([l (in-lines (open-day 6 2015))]) + (~>> l + (regexp-match + #px"(turn on|toggle|turn off) (\\d{1,3}),(\\d{1,3}) through (\\d{1,3}),(\\d{1,3})") + rest + make-instruction))) + +(define (vector2d-modify! vec x y f) + (define pos (+ x (* 1000 y))) + (vector-set! vec pos (f (vector-ref vec pos)))) + +;; part 1 +(define (todo inst) + (match (instruction-todo inst) + ['|turn on| (λ _ #true)] + ['|turn off| (λ _ #false)] + ['|toggle| not])) + +(define (modify-light-grid inst light-grid using) + (for ([x (inclusive-range (instruction-x1 inst) (instruction-x2 inst))]) + (for ([y (inclusive-range (instruction-y1 inst) (instruction-y2 inst))]) + (vector2d-modify! light-grid x y (using inst))))) + +(define light-grid (make-vector (* 1000 1000) #false)) +(for ([i (in-list instructions)]) + (modify-light-grid i light-grid todo)) +(vector-count identity light-grid) + +;; part 2 +(define (todo-dimmer inst) + (match (instruction-todo inst) + ['|turn on| add1] + ['|turn off| (λ (x) (max 0 (sub1 x)))] + ['|toggle| (curry + 2)])) + +(define dimmable-grid (make-vector (* 1000 1000) 0)) +(for ([i (in-list instructions)]) + (modify-light-grid i dimmable-grid todo-dimmer)) +(apply + (vector->list dimmable-grid)) diff --git a/racket/aoc2015/day-25/day-25.rkt b/racket/aoc2015/day-25/day-25.rkt new file mode 100644 index 0000000..975f4c3 --- /dev/null +++ b/racket/aoc2015/day-25/day-25.rkt @@ -0,0 +1,8 @@ +#lang racket + +(define max-r 2978) +(define max-c 3083) + +(for/fold ([code 20151125] [r 1] [c 1]) ([i (in-naturals)] #:break (and (= max-r r) (= max-c c))) + (define new-code (modulo (* code 252533) 33554393)) + (if (= r 1) (values new-code (add1 c) 1) (values new-code (sub1 r) (add1 c)))) diff --git a/racket/aoc2018/day-01/day-01.rkt b/racket/aoc2018/day-01/day-01.rkt new file mode 100644 index 0000000..b18f7c9 --- /dev/null +++ b/racket/aoc2018/day-01/day-01.rkt @@ -0,0 +1,16 @@ +#lang racket + +(require advent-of-code + threading) + +(define deltas + (~>> (open-aoc-input (find-session) 2018 1 #:cache #true) port->lines (map string->number))) + +;; part 1 +(for/sum ([delta deltas]) delta) + +;; part 2 +(for/fold ([seen (set)] [current-frequency 0] #:result current-frequency) ([delta (in-cycle deltas)]) + (define new-frequency (+ current-frequency delta)) + #:final (set-member? seen new-frequency) + (values (set-add seen new-frequency) new-frequency)) diff --git a/racket/aoc2018/day-02/day-02.rkt b/racket/aoc2018/day-02/day-02.rkt new file mode 100644 index 0000000..38155fb --- /dev/null +++ b/racket/aoc2018/day-02/day-02.rkt @@ -0,0 +1,27 @@ +#lang racket + +(require advent-of-code + threading) + +(define ids (port->lines (open-aoc-input (find-session) 2018 2 #:cache #true))) + +;; part 1 +(define (make-baskets str) + (for/fold ([baskets (hash)]) ([ch (in-string str)]) + (hash-update baskets ch add1 0))) + +(define (has-count n ht) + (member n (hash-values ht))) + +(for/fold ([two 0] [three 0] #:result (* two three)) ([id (in-list ids)]) + (define baskets (make-baskets id)) + (values (if (has-count 2 baskets) (add1 two) two) (if (has-count 3 baskets) (add1 three) three))) + +;; part 2 +(define (string-difference str1 str2) + (for/sum ([ch1 (in-string str1)] [ch2 (in-string str2)]) (if (equal? ch1 ch2) 0 1))) + +(for*/first ([id1 (in-list ids)] [id2 (in-list ids)] #:when (= 1 (string-difference id1 id2))) + (~>> (for/list ([ch1 (in-string id1)] [ch2 (in-string id2)] #:when (equal? ch1 ch2)) + ch1) + (apply string))) diff --git a/racket/aoc2018/day-03/day-03.rkt b/racket/aoc2018/day-03/day-03.rkt new file mode 100644 index 0000000..b486361 --- /dev/null +++ b/racket/aoc2018/day-03/day-03.rkt @@ -0,0 +1,51 @@ +#lang racket + +(require advent-of-code + threading + data/applicative + data/monad + megaparsack + megaparsack/text) + +(struct claim (number start-x start-y size-x size-y) #:transparent) + +(define claim/p + (do (char/p #\#) + [number <- integer/p] + (string/p " @ ") + [start-x <- integer/p] + (char/p #\,) + [start-y <- integer/p] + (string/p ": ") + [size-x <- integer/p] + (char/p #\x) + [size-y <- integer/p] + (pure (claim number start-x start-y size-x size-y)))) + +(define (parse-claim str) + (parse-result! (parse-string claim/p str))) + +(define (make-claim ht cl) + (for*/fold ([fabric ht]) + ([x (in-range (claim-start-x cl) (+ (claim-start-x cl) (claim-size-x cl)))] + [y (in-range (claim-start-y cl) (+ (claim-start-y cl) (claim-size-y cl)))]) + (hash-update fabric (cons x y) add1 0))) + +(define claims + (~> (port->lines (open-aoc-input (find-session) 2018 3 #:cache #true)) (map parse-claim _))) + +(define claimed-fabric + (for/fold ([fabric (hash)]) ([cl (in-list claims)]) + (make-claim fabric cl))) + +;; part 1 +(for/sum ([claim-count (in-list (hash-values claimed-fabric))] #:when (< 1 claim-count)) 1) + +;; part 2 +(define (uncontested-claim? fabric cl) + (for*/and ([x (in-range (claim-start-x cl) (+ (claim-start-x cl) (claim-size-x cl)))] + [y (in-range (claim-start-y cl) (+ (claim-start-y cl) (claim-size-y cl)))]) + (= 1 (hash-ref fabric (cons x y))))) + +(for/first ([cl (in-list claims)] #:when (uncontested-claim? claimed-fabric cl)) + (claim-number cl)) diff --git a/racket/aoc2018/day-04/day-04.rkt b/racket/aoc2018/day-04/day-04.rkt new file mode 100644 index 0000000..3660099 --- /dev/null +++ b/racket/aoc2018/day-04/day-04.rkt @@ -0,0 +1,43 @@ +#lang racket + +(require advent-of-code + data/applicative + data/monad + megaparsack + megaparsack/text + threading) + +(struct entry (month day hour minute message) #:transparent) + +(define (parse-message chrs) + (define str (apply string chrs)) + (match str + ["wakes up" 'awake] + ["falls asleep" 'asleep] + [shift (~> shift (string-trim "Guard #") (string-trim " begins shift") string->number)])) + +(define entry/p + (do (string/p "[1518-") + [month <- integer/p] + (char/p #\-) + [day <- integer/p] + space/p + [hour <- integer/p] + (char/p #\:) + [minute <- integer/p] + (string/p "] ") + [message <- (many/p any-char/p)] + (pure (entry month day hour minute (parse-message message))))) + +(define (parse-entry str) + (parse-result! (parse-string entry/p str))) + +(define entries + (~> (port->lines (open-aoc-input (find-session) 2018 4 #:cache #true)) (map parse-entry _))) + +(define sorted-entries + (~> entries + (sort < #:key entry-minute) + (sort < #:key entry-hour) + (sort < #:key entry-day) + (sort < #:key entry-month))) diff --git a/racket/aoc2018/day-05/day-05.rkt b/racket/aoc2018/day-05/day-05.rkt new file mode 100644 index 0000000..a78f5b5 --- /dev/null +++ b/racket/aoc2018/day-05/day-05.rkt @@ -0,0 +1,31 @@ +#lang racket + +(require advent-of-code + threading) + +(define starting-chain + (~> (fetch-aoc-input (find-session) 2018 5 #:cache #true) string-trim string->list)) + +(define (reactive-pair? ch1 ch2) + (and (equal? (char-downcase ch1) (char-downcase ch2)) (not (equal? ch1 ch2)))) + +(define (remove-reactive-pairs chs [acc '()]) + (match chs + [(list* ch1 ch2 rest-chs) + #:when (reactive-pair? ch1 ch2) + (remove-reactive-pairs rest-chs acc)] + [(list* ch rest-chs) (remove-reactive-pairs rest-chs (cons ch acc))] + [(list) (reverse acc)])) + +(define (keep-removing-reactive-pairs chs) + (define chs* (remove-reactive-pairs chs)) + (if (equal? chs chs*) (length chs) (keep-removing-reactive-pairs chs*))) + +;; part 1 +(keep-removing-reactive-pairs starting-chain) + +;; part 2 +(~>> (for/list ([letter (in-string "abcdefghijklmnopqrstuvwxyz")]) + (define tweaked-chain (filter (λ (c) (not (equal? (char-downcase c) letter))) starting-chain)) + (keep-removing-reactive-pairs tweaked-chain)) + (apply min)) diff --git a/racket/aoc2018/day-06/day-06.rkt b/racket/aoc2018/day-06/day-06.rkt new file mode 100644 index 0000000..6f1f7b4 --- /dev/null +++ b/racket/aoc2018/day-06/day-06.rkt @@ -0,0 +1 @@ +#lang racket diff --git a/racket/aoc2019/day-02/day-02.rkt b/racket/aoc2019/day-02/day-02.rkt new file mode 100644 index 0000000..56019e8 --- /dev/null +++ b/racket/aoc2019/day-02/day-02.rkt @@ -0,0 +1,32 @@ +#lang racket + +(require advent-of-code + threading) + +(define initial-opcodes + (parameterize ([current-readtable (make-readtable #f #\, #\space #f)]) + (port->list read (open-aoc-input (find-session) 2019 2 #:cache #true)))) + +;; part 1 +(define (edit-opcode ocs oc-1 oc-2) + (~> ocs (list-set 1 oc-1) (list-set 2 oc-2))) + +(define (run-intcode ocs) + (for/fold ([ocs ocs] #:result (car ocs)) + ([pointer (in-range 0 (length initial-opcodes) 4)] #:break (= 99 (list-ref ocs pointer))) + (define op + (match (list-ref ocs pointer) + [1 +] + [2 *])) + (list-set ocs + (list-ref ocs (+ 3 pointer)) + (op (list-ref ocs (list-ref ocs (+ 1 pointer))) + (list-ref ocs (list-ref ocs (+ 2 pointer))))))) + +(~> initial-opcodes (edit-opcode 12 2) run-intcode) + +;; part 2 +(for*/first ([noun (inclusive-range 0 99)] + [verb (inclusive-range 0 99)] + #:when (~> initial-opcodes (edit-opcode noun verb) run-intcode (= 19690720))) + (+ (* 100 noun) verb)) diff --git a/racket/aoc2019/day-03/day-03.rkt b/racket/aoc2019/day-03/day-03.rkt new file mode 100644 index 0000000..6da3a07 --- /dev/null +++ b/racket/aoc2019/day-03/day-03.rkt @@ -0,0 +1,52 @@ +#lang racket + +(require advent-of-code + threading + fancy-app + racket/hash) + +(define/match (instruction-parse _str) + [((regexp #px"(.)(.+)" (list _ (app string->symbol dir) (app string->number amt)))) (cons dir amt)]) + +(define (wire-parse str) + (~> str (string-split ",") (map instruction-parse _))) + +(define wires + (~>> (fetch-aoc-input (find-session) 2019 3 #:cache #true) string-split (map wire-parse))) + +(define (manhattan-distance-from-origin p) + (+ (abs (car p)) (abs (cdr p)))) + +(define (trace-wire-path wire) + (for/fold ([path (hash)] [x 0] [y 0] [len 0] #:result path) ([inst (in-list wire)]) + (define-values (x* y*) + (match inst + [(cons 'U dy) (values x (+ y dy))] + [(cons 'D dy) (values x (- y dy))] + [(cons 'R dx) (values (+ x dx) y)] + [(cons 'L dx) (values (- x dx) y)])) + (define next-segment + (for*/list ([new-x (inclusive-range x x* (if (< x x*) 1 -1))] + [new-y (inclusive-range y y* (if (< y y*) 1 -1))]) + (cons new-x new-y))) + (define numbered-segments + (for/hash ([segment (in-list next-segment)] [new-len (in-naturals len)]) + (values segment new-len))) + (values (hash-union path numbered-segments #:combine (λ (v0 _) v0)) x* y* (+ len (cdr inst))))) + +;; part 1 +(define wire-paths (map trace-wire-path wires)) + +(define intersections + (~>> wire-paths + (map (λ~> hash-keys list->set)) + (apply set-intersect) + (set-remove _ '(0 . 0)) + set->list)) + +(~>> intersections (map manhattan-distance-from-origin) (apply min)) + +;; part 2 +(~>> (for/list ([intersection (in-list intersections)]) + (~>> wire-paths (map (hash-ref _ intersection)) (apply +))) + (apply min)) diff --git a/racket/aoc2019/day-04/day-04.rkt b/racket/aoc2019/day-04/day-04.rkt new file mode 100644 index 0000000..9518779 --- /dev/null +++ b/racket/aoc2019/day-04/day-04.rkt @@ -0,0 +1,39 @@ +#lang racket + +(define (number->digits n [acc '()]) + (cond + [(< n 10) (cons n acc)] + [else (number->digits (quotient n 10) (cons (remainder n 10) acc))])) + +(define (always-increasing? xs) + (match xs + [(list _) #t] + [(list* a b _) #:when (<= a b) (always-increasing? (cdr xs))] + [_ #f])) + +(define (adjacent-pair? xs) + (match xs + [(list _) #f] + [(list* a a _) a] + [_ (adjacent-pair? (cdr xs))])) + +;; part 1 +(for/sum ([password (inclusive-range 125730 579381)] + #:do [(define digits (number->digits password))] + #:when ((conjoin always-increasing? adjacent-pair?) digits)) + 1) + +;; part 2 +(define (not-in-adjacent-triplet? xs) + (match xs + [(list a a c _ _ _) #:when (not (= a c)) #t] + [(list b a a c _ _) #:when (not (or (= a b) (= a c))) #t] + [(list _ b a a c _) #:when (not (or (= a b) (= a c))) #t] + [(list _ _ b a a c) #:when (not (or (= a b) (= a c))) #t] + [(list _ _ _ b a a) #:when (not (= a b)) #t] + [_ #f])) + +(for/sum ([password (inclusive-range 125730 579381)] + #:do [(define digits (number->digits password))] + #:when ((conjoin always-increasing? adjacent-pair? not-in-adjacent-triplet?) digits)) + 1)
\ No newline at end of file diff --git a/racket/aoc2019/day-05/day-05.rkt b/racket/aoc2019/day-05/day-05.rkt new file mode 100644 index 0000000..6f1f7b4 --- /dev/null +++ b/racket/aoc2019/day-05/day-05.rkt @@ -0,0 +1 @@ +#lang racket diff --git a/racket/aoc2020/day-01/day-01.rkt b/racket/aoc2020/day-01/day-01.rkt new file mode 100644 index 0000000..e31c45c --- /dev/null +++ b/racket/aoc2020/day-01/day-01.rkt @@ -0,0 +1,20 @@ +#lang racket +(require "../../jj-aoc.rkt" + threading) + +(define entries (~>> (open-day 01 2020) (port->list read) list->set)) + +;; part 1 +(define (look-for-complement xs) + (define x (set-first xs)) + (cond + [(set-member? xs (- 2020 x)) (* x (- 2020 x))] + [else (look-for-complement (set-rest xs))])) + +(time (look-for-complement entries)) + +;; part 2 +(time (for*/first ([x (in-set entries)] + [y (in-set (set-subtract entries (set x)))] + #:when (set-member? (set-subtract entries (set x y)) (- 2020 x y))) + (* x y (- 2020 x y)))) diff --git a/racket/aoc2020/day-02/day-02.rkt b/racket/aoc2020/day-02/day-02.rkt new file mode 100644 index 0000000..9e22a1a --- /dev/null +++ b/racket/aoc2020/day-02/day-02.rkt @@ -0,0 +1,33 @@ +#lang racket +(require "../../jj-aoc.rkt" + threading) + +(struct policy (least most char pwd) #:transparent) + +(define (make-policy least-str most-str char-str pwd) + (policy (string->number least-str) (string->number most-str) (string-ref char-str 0) pwd)) + +(define policies + (for/list ([l (in-lines (open-day 02 2020))]) + (~>> l (regexp-match #px"(\\d+)-(\\d+) (\\w): (.*)") rest (apply make-policy)))) + +;; part 1 +(define (valid-policy? p) + (~>> p + policy-pwd + string->list + (count (curry char=? (policy-char p))) + ((λ (n) (and (>= n (policy-least p)) (<= n (policy-most p))))))) + +(for/sum ([p (in-list policies)] #:when (valid-policy? p)) 1) + +;; part 2 +(define (valid-revised-policy? p) + (~>> p + policy-pwd + string->list + ((λ (lst) (list (list-ref lst (sub1 (policy-most p))) (list-ref lst (sub1 (policy-least p)))))) + (count (curry char=? (policy-char p))) + (= 1))) + +(for/sum ([p (in-list policies)] #:when (valid-revised-policy? p)) 1) diff --git a/racket/aoc2020/day-03/day-03.rkt b/racket/aoc2020/day-03/day-03.rkt new file mode 100644 index 0000000..ee9edcf --- /dev/null +++ b/racket/aoc2020/day-03/day-03.rkt @@ -0,0 +1,16 @@ +#lang racket +(require "../../jj-aoc.rkt" + threading) + +(define (check-for-trees run rise) + (for*/sum ([(row i) (in-indexed (port->lines (open-day 3 2020)))] + #:when (= 0 (modulo i rise)) + [possible-tree (in-value (sequence-ref (in-cycle row) (* (/ run rise) i)))] + #:when (and (char=? possible-tree #\#))) + 1)) + +;; part 1 +(check-for-trees 3 1) + +;; part 2 +(~>> '((1 1) (3 1) (5 1) (7 1) (1 2)) (map (curry apply check-for-trees)) (apply *)) diff --git a/racket/aoc2020/day-04/day-04.rkt b/racket/aoc2020/day-04/day-04.rkt new file mode 100644 index 0000000..54d50f8 --- /dev/null +++ b/racket/aoc2020/day-04/day-04.rkt @@ -0,0 +1,64 @@ +#lang racket +(require "../../jj-aoc.rkt" + threading) + +(define passports + (~> (open-day 4 2020) (port->string) (string-split "\n\n") (map (λ~> (string-replace "\n" " ")) _))) + +;; part 1 +(define required-fields (list "byr:" "iyr:" "eyr:" "hgt:" "hcl:" "ecl:" "pid:")) + +(define (valid-passport? p) + (andmap (λ (s) (string-contains? p s)) required-fields)) + +(count valid-passport? passports) + +;; part 2 +(define passport-fields + (for/list ([p (in-list passports)] #:when (valid-passport? p)) + (~> p string-split (map (curryr string-split ":") _) flatten (apply hash _)))) + +(define (between x low high) + (and (x . >= . low) (x . <= . high))) + +(define (valid-byr? p) + (define year (string->number (hash-ref p "byr"))) + (between year 1920 2002)) + +(define (valid-iyr? p) + (define year (string->number (hash-ref p "iyr"))) + (between year 2010 2020)) + +(define (valid-eyr? p) + (define year (string->number (hash-ref p "eyr"))) + (between year 2020 2030)) + +(define (valid-hgt? p) + (define height (hash-ref p "hgt")) + (cond + [(string-suffix? height "cm") + (let ([h (string->number (string-trim height "cm"))]) (between h 150 193))] + [(string-suffix? height "in") + (let ([h (string->number (string-trim height "in"))]) (between h 59 76))] + [else #false])) + +(define (valid-hcl? p) + (define color (hash-ref p "hcl")) + (regexp-match #px"^#[0-9a-f]{6}$" color)) + +(define (valid-ecl? p) + (member (hash-ref p "ecl") (list "amb" "blu" "brn" "gry" "grn" "hzl" "oth"))) + +(define (valid-pid? p) + (regexp-match #px"^\\d{9}$" (hash-ref p "pid"))) + +(define (valid-stricter-passport? p) + (and (valid-byr? p) + (valid-iyr? p) + (valid-eyr? p) + (valid-hgt? p) + (valid-hcl? p) + (valid-ecl? p) + (valid-pid? p))) + +(count valid-stricter-passport? passport-fields) diff --git a/racket/aoc2020/day-05/day-05.rkt b/racket/aoc2020/day-05/day-05.rkt new file mode 100644 index 0000000..bd89ede --- /dev/null +++ b/racket/aoc2020/day-05/day-05.rkt @@ -0,0 +1,35 @@ +#lang racket +(require "../../jj-aoc.rkt" + threading) + +(define tickets + (for/list ([l (in-lines (open-day 5 2020))]) + (~>> l (regexp-match #px"(.{7})(.{3})") rest))) + +(define (find-place str min-p max-p l r) + (if (string=? "" str) + min-p + (let ([p-range (/ (add1 (- max-p min-p)) 2)] [c (substring str 0 1)]) + (cond + [(string=? c l) (find-place (substring str 1) min-p (- max-p p-range) l r)] + [(string=? c r) (find-place (substring str 1) (+ min-p p-range) max-p l r)])))) + +(define (find-row str) + (find-place str 0 127 "F" "B")) +(define (find-col str) + (find-place str 0 7 "L" "R")) + +(define (ticket-id t) + (let ([row (find-row (first t))] [col (find-col (second t))]) (+ col (* 8 row)))) + +;; part 1 +(define occupied-seats + (~>> (for/list ([t (in-list tickets)]) + (ticket-id t)))) + +(apply max occupied-seats) + +;; part 2 +(set-first (set-subtract + (list->set (inclusive-range (apply min occupied-seats) (apply max occupied-seats))) + (list->set occupied-seats))) diff --git a/racket/aoc2020/day-06/day-06.rkt b/racket/aoc2020/day-06/day-06.rkt new file mode 100644 index 0000000..b0e2af9 --- /dev/null +++ b/racket/aoc2020/day-06/day-06.rkt @@ -0,0 +1,22 @@ +#lang racket +(require "../../jj-aoc.rkt" + threading) + +(define responses (~> (open-day 6 2020) (port->string) (string-split "\n\n"))) + +;; part 1 +(define (response-count-total rs) + (for/sum ([r (in-list rs)]) (~> r (string-replace _ "\n" "") string->list list->set set-count))) + +(response-count-total responses) + +;; part 2 +(define (response-consensus-total rs) + (for/sum ([r (in-list rs)]) + (~> r + (string-split _ "\n") + (map (λ~> string->list list->set) _) + (apply set-intersect _) + set-count))) + +(response-consensus-total responses)
\ No newline at end of file diff --git a/racket/aoc2020/day-07/day-07.rkt b/racket/aoc2020/day-07/day-07.rkt new file mode 100644 index 0000000..f2a1ffe --- /dev/null +++ b/racket/aoc2020/day-07/day-07.rkt @@ -0,0 +1,46 @@ +#lang racket +(require advent-of-code + threading + rebellion/collection/entry + rebellion/collection/multidict) + +(define raw-rules (~> (open-aoc-input (find-session) 2020 7) (port->string) (string-split "\n"))) + +(define (split-rule r) + (match-define (list head tail) (string-split (string-trim r #px" bags?.") " bags contain ")) + (~>> tail + (regexp-split #px"( bags?,\\s)" _) + (map (λ~> (regexp-match* #px"(\\d+) (\\w+ \\w+)" _ #:match-select rest))) + append* + (map (λ~> (match _ + [(list n c) (list (string->symbol c) (string->number n))] + ['() '()]))) + (cons (string->symbol head) _))) + +(define rules-multidict + (for*/multidict ([ln (in-list raw-rules)] #:do [(match-define (list* from tos) (split-rule ln))] + [to (in-list tos)]) + (entry from to))) + +;; part 1 +(define (bags-that-eventually-contain target) + (for/fold ([holders (set)]) ([rule (in-multidict-entries rules-multidict)]) + (match rule + [(entry outside (list (== target) _)) + (set-union (set-add holders outside) (bags-that-eventually-contain outside))] + [_ holders]))) + +(define part-1 (set-count (bags-that-eventually-contain '|shiny gold|))) +(~a "Part 1: " part-1) +;; (aoc-submit (find-session) 2020 7 1 part-1) + +;; part 2 +(define (bags-that-are-contained-by target) + (for/sum ([holding (in-multidict-entries rules-multidict)]) + (match holding + [(entry (== target) (list held n)) (+ n (* n (bags-that-are-contained-by held)))] + [_ 0]))) + +(define part-2 (bags-that-are-contained-by '|shiny gold|)) +(~a "Part 2: " part-2) +;; (aoc-submit (find-session) 2020 7 1 part-2) diff --git a/racket/aoc2020/day-08/day-08.ipynb b/racket/aoc2020/day-08/day-08.ipynb new file mode 100644 index 0000000..1cb060b --- /dev/null +++ b/racket/aoc2020/day-08/day-08.ipynb @@ -0,0 +1,220 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Advent of Code 2020\n", + "#### Day 8: Handheld Halting\n", + "\n", + "A series of instructions consisting of jumps, accumulator increments and no-ops has an infinite loop, but changing one no-op to a jump or vice versa will allow it to run to completion.\n", + "\n", + "1. What's the value of the accumulator immediately before the instructions begin to loop?\n", + "2. After fixing the wrong instruction, what's the value of the accumulator at the end of execution?\n", + "\n", + "No surprises in the preamble, just the usual AOC utility functions and the threading macros." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "#lang iracket/lang #:require racket\n", + "(require advent-of-code\n", + " threading)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The instructions are in a text file that looks like\n", + "```\n", + "nop +0\n", + "acc +1\n", + "jmp +4\n", + "acc +3\n", + "jmp -3\n", + "acc -99\n", + "acc +1\n", + "jmp -4\n", + "acc +6\n", + "```\n", + "Since we need to keep track of which instructions to jump to, I've turned it into a hash table so each instruction is indexed starting at 0." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "<code>'((0 acc . 49) (1 jmp . 274) (2 acc . 49) (3 acc . 49) (4 jmp . 476) (5 jmp . 409) (6 jmp . 269) (7 jmp . 1) (8 acc . -11) (9 acc . 5))</code>" + ], + "text/plain": [ + "'((0 acc . 49) (1 jmp . 274) (2 acc . 49) (3 acc . 49) (4 jmp . 476) (5 jmp . 409) (6 jmp . 269) (7 jmp . 1) (8 acc . -11) (9 acc . 5))" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(define raw-instructions (~> (fetch-aoc-input (find-session) 2020 8) (string-split \"\\n\")))\n", + "\n", + "(define instruction-set\n", + " (for/hash ([instruction (in-list raw-instructions)] [i (in-naturals)])\n", + " (match-define (list op val) (string-split instruction))\n", + " (values i (cons (string->symbol op) (string->number val)))))\n", + "\n", + "(for/list ([i (in-range 10)])\n", + " (cons i (hash-ref instruction-set i)))\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Part 1\n", + "\n", + "Now I write a little interpreter using structural pattern matching and recursion to execute the code.\n", + "* If the program tried to execute an instruction on the line immediately after the last instruction, the program terminates normally. (This won't happen in part 1, but it's important for part 2.)\n", + "* We track the line numbers that have been visited in each step. If a number comes up a second time, that means we're about to start looping, so we break execution here.\n", + "* `acc n` instructions increment the accumulator by `n` and go to the next line.\n", + "* `jmp n` instructions skip up or down `n` instructions.\n", + "* `nop n` instructions don't do anything besides go to the next line." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "<code>'(looping . 1949)</code>" + ], + "text/plain": [ + "'(looping . 1949)" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(define (execute code [acc 0] [line 0] [visited (set)])\n", + " (match (hash-ref code line 'terminated)\n", + " ['terminated (cons 'terminated acc)]\n", + " [_\n", + " #:when (set-member? visited line)\n", + " (cons 'looping acc)]\n", + " [(cons 'acc n) (execute code (+ acc n) (add1 line) (set-add visited line))]\n", + " [(cons 'jmp n) (execute code acc (+ n line) (set-add visited line))]\n", + " [(cons 'nop _) (execute code acc (add1 line) (set-add visited line))]))\n", + "\n", + "(execute instruction-set)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Part 2\n", + "\n", + "So far so good. Now we're told that flipping exactly one `jmp` to a `nop` or vice versa will fix the code, so let's write a utility function to perform that flip, identify the potential candidates for the fix and get an idea of what our workload will be for this problem." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "<code>305</code>" + ], + "text/plain": [ + "305" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(define (flip-op op)\n", + " (match op\n", + " [(cons 'jmp n) (cons 'nop n)]\n", + " [(cons 'nop n) (cons 'jmp n)]))\n", + "\n", + "(define instruction-count (hash-count instruction-set))\n", + "(define flippable-bits\n", + " (filter (λ (i) (member (car (hash-ref instruction-set i)) '(jmp nop))) (range instruction-count)))\n", + "\n", + "(length flippable-bits)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There's only 305 cases to check, so just starting from the top and trying each possible swap in sequence should work fine, rather than trying to come up with some fancier backtracking algorithm. `for/or` stops at the first non-falsy result, so we just have to wait for the first result that matches the `(cons 'terminated _)` pattern." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "<code>'(terminated . 2092)</code>" + ], + "text/plain": [ + "'(terminated . 2092)" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(for/or ([i (in-list flippable-bits)])\n", + " (define flipped-instruction-set (hash-update instruction-set i flip-op))\n", + " (match (execute flipped-instruction-set)\n", + " [(cons 'looping _) #f]\n", + " [(and success (cons 'terminated _)) success]))\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Racket (trusted)", + "language": "racket", + "name": "racket-trusted" + }, + "language_info": { + "codemirror_mode": "scheme", + "file_extension": ".rkt", + "mimetype": "text/x-racket", + "name": "racket", + "pygments_lexer": "racket", + "version": "8.7" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/racket/aoc2020/day-09/day-09.ipynb b/racket/aoc2020/day-09/day-09.ipynb new file mode 100644 index 0000000..e6f712b --- /dev/null +++ b/racket/aoc2020/day-09/day-09.ipynb @@ -0,0 +1,166 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Advent of Code 2020\n", + "#### Day 9: Encoding Error\n", + "\n", + "In a list of integers, each number after the 25th should be the sum of two of the previous 25 numbers.\n", + "\n", + "1. What's the first number in the list that does not have this property?\n", + "2. The \"encryption weakness\" is the sum of the extrema in a contiguous range of numbers that sums up to the invalid number in part 1. Find the encryption weakness.\n", + "\n", + "I'm using structural pattern matching for this solution, so no extra packages are required beyond the usual." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "vscode": { + "languageId": "racket" + } + }, + "outputs": [], + "source": [ + "#lang iracket/lang #:require racket\n", + "\n", + "(require advent-of-code\n", + " threading)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The data is just a list of integers, so it's straightforward to process." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "vscode": { + "languageId": "racket" + } + }, + "outputs": [], + "source": [ + "(define preamble\n", + " (~> (fetch-aoc-input (find-session) 2020 9) (string-split \"\\n\") (map string->number _)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Part 1\n", + "\n", + "First, we bind the first 25 values to `head` and the 26th to `x`.\n", + "\n", + "In the `match` syntax, `list-no-order` binds `a` and `b` to the first pair of numbers from anywhere in the first 25 values that satisfies the `#:when` guard. The exact pair doesn't matter; all we need to know is if it's valid and we can move on to the next test.\n", + "\n", + "If nothing satisfies the first clause, we've found our invalid number. We're guaranteed to have an invalid number in the set, so we don't need to guard against `match-define-values` failing when there's fewer than 26 values to work with." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "vscode": { + "languageId": "racket" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "<code>1038347917</code>" + ], + "text/plain": [ + "1038347917" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(define (find-invalid-number xs)\n", + " (match-define-values (head (list x _ ...)) (split-at xs 25))\n", + " (match head\n", + " [(list-no-order a b _ ...)\n", + " #:when (= x (+ a b))\n", + " (find-invalid-number (rest xs))]\n", + " [_ x]))\n", + "\n", + "(define target-sum (find-invalid-number preamble))\n", + "target-sum" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Part 2\n", + "\n", + "We can find the range with another match statement, this time looking for a sub-list that's at least two elements long and that satisfies the guard. Everything after this is just arithmetic." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "vscode": { + "languageId": "racket" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "<code>137394018</code>" + ], + "text/plain": [ + "137394018" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(define (find-contiguous-range xs)\n", + " (match xs\n", + " [(list _ ... x ..2 _ ...)\n", + " #:when (= (apply + x) target-sum)\n", + " x]))\n", + "\n", + "(define target-range (find-contiguous-range preamble))\n", + "(+ (apply max target-range) (apply min target-range))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Racket (trusted)", + "language": "racket", + "name": "racket-trusted" + }, + "language_info": { + "codemirror_mode": "scheme", + "file_extension": ".rkt", + "mimetype": "text/x-racket", + "name": "Racket", + "pygments_lexer": "racket", + "version": "8.7" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/racket/aoc2020/day-10/day-10.rkt b/racket/aoc2020/day-10/day-10.rkt new file mode 100644 index 0000000..77d9bb7 --- /dev/null +++ b/racket/aoc2020/day-10/day-10.rkt @@ -0,0 +1,37 @@ +#lang racket + +(require advent-of-code + threading + algorithms + memoize) + +;; part 1 +(define adapters + (~> (fetch-aoc-input (find-session) 2020 10) + (string-split "\n") + (map string->number _) + (sort <) + ((λ (xs) (flatten (list 0 xs (+ 3 (last xs)))))))) + +(~>> adapters + (sliding _ 2) + (map (match-lambda + [(list b a) (- a b)])) + (group-by identity) + (map length) + (apply *)) + +;; part 2 +(define subpaths + (for*/hash ([adapter (in-list adapters)]) + (define predecessor-candidates (inclusive-range (+ 1 adapter) (+ 3 adapter))) + (values adapter (filter (λ (p) (member p adapters)) predecessor-candidates)))) + +(define/memo (find-paths from to) + (define paths (hash-ref subpaths from 'failed)) + (match paths + ['failed 0] + [(list-no-order (== to) _ ...) 1] + [ts (for/sum ([t (in-list ts)]) (find-paths t to))])) + +(find-paths (first adapters) (last adapters)) diff --git a/racket/aoc2020/day-11/day-11.rkt b/racket/aoc2020/day-11/day-11.rkt new file mode 100644 index 0000000..e2fe052 --- /dev/null +++ b/racket/aoc2020/day-11/day-11.rkt @@ -0,0 +1,60 @@ +#lang racket + +(require advent-of-code) + +(define raw-grid (fetch-aoc-input (find-session) 2020 11)) + +(define/match (parse _) + [(#\L) 'empty] + [(#\#) 'occupied] + [(#\.) 'floor]) + +(define seat-grid + (for*/hash ([(row r) (in-indexed (in-list (string-split raw-grid)))] + [(col c) (in-indexed (in-string row))]) + (values (cons r c) (parse col)))) + +(define (next-seat-state seat state h rule [max-occupy 4]) + (define neighbor-states (rule seat h)) + (match* (state (count (curry eq? 'occupied) neighbor-states)) + [('empty 0) 'occupied] + [('occupied n) + #:when (>= n max-occupy) + 'empty] + [(_ _) state])) + +(define (stabilize h [i 1] #:rule rule #:max-occupy [max-occupy 4]) + (define h* + (for/hash ([(seat state) (in-hash h)]) + (cond + [(eq? state 'floor) (values seat state)] + [else (values seat (next-seat-state seat state h rule max-occupy))]))) + (if (equal? h h*) + (count (curry equal? 'occupied) (hash-values h)) + (stabilize h* (add1 i) #:rule rule #:max-occupy max-occupy))) + +;; part 1 +(define (find-nearest-neighbors p h) + (match-define (cons r c) p) + (for*/list ([r* (in-inclusive-range (sub1 r) (add1 r))] + [c* (in-inclusive-range (sub1 c) (add1 c))] + [p* (in-value (cons r* c*))] + #:unless (equal? p p*)) + (hash-ref h p* 'out-of-bounds))) + +(stabilize seat-grid #:rule find-nearest-neighbors) + +;; part 2 +(define (find-visible-neighbors p h) + (match-define (cons r c) p) + (define directions + (for*/list ([dr '(-1 0 1)] [dc '(-1 0 1)] #:unless (= 0 dr dc)) + (cons dr dc))) + (for/list ([dir (in-list directions)] #:do [(match-define (cons dr dc) dir)]) + (for*/first ([i (in-naturals 1)] + #:do [(define p* (cons (+ r (* i dr)) (+ c (* i dc)))) + (define state (hash-ref h p* 'out-of-bounds))] + #:unless (equal? state 'floor)) + state))) + +(stabilize seat-grid #:rule find-visible-neighbors #:max-occupy 5) diff --git a/racket/aoc2020/day-12/day-12.rkt b/racket/aoc2020/day-12/day-12.rkt new file mode 100644 index 0000000..e4bbd32 --- /dev/null +++ b/racket/aoc2020/day-12/day-12.rkt @@ -0,0 +1,56 @@ +#lang rackjure + +(require advent-of-code + threading + (only-in relation ->symbol ->number)) + +(struct instruction (direction distance) #:transparent) + +(define (parse-instruction str) + (match str + [(regexp #px"(\\w)(\\d+)" (list _ dir dist)) (instruction (->symbol dir) (->number dist))])) + +(define instructions + (~>> (fetch-aoc-input (find-session) 2020 12) string-split (map parse-instruction))) + +;; part 1 +(struct boat (x y nav) #:transparent) + +(define (angle->direction n) + (case n + [(0) 'E] + [(90) 'N] + [(180) 'W] + [(270) 'S])) + +(define (move-via-direct-command inst b) + (match-define (boat x y facing) b) + (match inst + [(instruction 'N n) (boat x (+ y n) facing)] + [(instruction 'S n) (boat x (- y n) facing)] + [(instruction 'E n) (boat (+ x n) y facing)] + [(instruction 'W n) (boat (- x n) y facing)] + [(instruction 'L n) (boat x y (modulo (+ facing n) 360))] + [(instruction 'R n) (boat x y (modulo (- facing n) 360))] + [(instruction 'F n) (move-via-direct-command (instruction (angle->direction facing) n) b)])) + +(define (find-boat-destination using start instructions) + (match-define (boat x y _) (foldl using start instructions)) + (+ (abs x) (abs y))) + +(find-boat-destination move-via-direct-command (boat 0 0 0) instructions) + +;; part 2 +(define (move-via-waypoint inst b) + (match-define (boat x y (cons wp-x wp-y)) b) + (match inst + [(instruction 'N n) (boat x y (cons wp-x (+ wp-y n)))] + [(instruction 'S n) (boat x y (cons wp-x (- wp-y n)))] + [(instruction 'E n) (boat x y (cons (+ wp-x n) wp-y))] + [(instruction 'W n) (boat x y (cons (- wp-x n) wp-y))] + [(instruction _ 180) (boat x y (cons (- wp-x) (- wp-y)))] + [(or (instruction 'L 90) (instruction 'R 270)) (boat x y (cons (- wp-y) wp-x))] + [(or (instruction 'R 90) (instruction 'L 270)) (boat x y (cons wp-y (- wp-x)))] + [(instruction 'F n) (boat (+ x (* n wp-x)) (+ y (* n wp-y)) (cons wp-x wp-y))])) + +(find-boat-destination move-via-waypoint (boat 0 0 '(10 . 1)) instructions) diff --git a/racket/aoc2020/day-13/day-13.rkt b/racket/aoc2020/day-13/day-13.rkt new file mode 100644 index 0000000..b53f045 --- /dev/null +++ b/racket/aoc2020/day-13/day-13.rkt @@ -0,0 +1,32 @@ +#lang racket + +(require advent-of-code + (only-in relation ->number) + threading) + +(define (process-ids str) + (~> str (string-split ",") (filter-map (λ (s) (string->number s 10 'number-or-false)) _))) + +(match-define (regexp #px"(\\d+)\n(.+)" (list _ (app ->number timestamp) raw-bus-ids)) + (fetch-aoc-input (find-session) 2020 13)) + +(define bus-ids (process-ids raw-bus-ids)) + +;; part 1 +(for/first ([minute (in-naturals timestamp)] + #:do [(define departing-bus + (for/first ([b bus-ids] #:when (= 0 (remainder minute b))) + b))] + #:when departing-bus) + (* departing-bus (- minute timestamp))) + +;; part 2 +(for/fold ([step 1] [current-timestamp 1] #:result current-timestamp) + ([b* (in-list (string-split (string-trim raw-bus-ids) ","))] + [offset (in-naturals)] + #:unless (equal? b* "x") + #:do [(define bus (->number b*))]) + (values + (* step bus) + (for/first ([n (in-range current-timestamp +inf.0 step)] #:when (= 0 (remainder (+ n offset) bus))) + n))) diff --git a/racket/aoc2020/day-14/day-14.rkt b/racket/aoc2020/day-14/day-14.rkt new file mode 100644 index 0000000..9ac339c --- /dev/null +++ b/racket/aoc2020/day-14/day-14.rkt @@ -0,0 +1,11 @@ +#lang racket + +(require advent-of-code + threading + fancy-app + relation + rebellion/binary/bitstring) + +(define instructions (string-split (fetch-aoc-input (find-session) 2020 14) "\n")) + +(~> (number->string 11 2) ->list (map (->number _) _)) diff --git a/racket/aoc2020/day-15/day-15.rkt b/racket/aoc2020/day-15/day-15.rkt new file mode 100644 index 0000000..4dd9e88 --- /dev/null +++ b/racket/aoc2020/day-15/day-15.rkt @@ -0,0 +1,22 @@ +#lang rackjure + +(define first-numbers '(2 20 0 4 1 17)) + +(define number-hash + (for/hash ([(xs i) (in-indexed (drop-right first-numbers 1))]) + (values xs (add1 i)))) + +(define starting-round (~> number-hash hash-values (apply max _) (+ 2))) + +(define (find-spoken-number-at end) + (for/fold ([ns number-hash] [previous-number (last first-numbers)] #:result previous-number) + ([rnd (inclusive-range starting-round end)]) + (define next-spoken-number + (match (ns previous-number) + [#f 0] + [n (- (sub1 rnd) n)])) + (values (ns previous-number (sub1 rnd)) next-spoken-number))) + +(find-spoken-number-at 2020) + +(find-spoken-number-at 30000000)
\ No newline at end of file diff --git a/racket/aoc2020/day-16/day-16.rkt b/racket/aoc2020/day-16/day-16.rkt new file mode 100644 index 0000000..9a38eda --- /dev/null +++ b/racket/aoc2020/day-16/day-16.rkt @@ -0,0 +1,52 @@ +#lang racket + +(require racket/struct + advent-of-code + fancy-app + relation + threading + rebellion/base/range) + +(struct field-rule (name range1 range2) #:transparent) + +(define (make-lines strs) + (string-split strs "\n")) +(define (seperate-fields strs) + (~>> (string-split strs ",") (map ->number))) + +(define (process-rules str) + (match str + [(regexp + #px"(.+): (\\d+)-(\\d+) or (\\d+)-(\\d+)" + (list _ name (app ->number min1) (app ->number max1) (app ->number min2) (app ->number max2))) + (field-rule name (closed-range min1 max1) (closed-range min2 max2))])) + +(match-define (list (app (λ~>> make-lines (map process-rules)) ticket-rules) + (app (λ~>> make-lines second seperate-fields) your-ticket) + (app (λ~>> make-lines rest (map seperate-fields)) other-tickets)) + (~> (fetch-aoc-input (find-session) 2020 16 #:cache #true) (string-split "\n\n"))) + +;; part 1 +(define (fails-all-checks? field rules) + (define rule-list (~>> rules (map (λ~> struct->list rest)) flatten)) + (for/and ([rule (in-list rule-list)]) + (not (range-contains? rule field)))) + +(define (ticket-scanning-error-rate tickets rules) + (for*/sum + ([ticket (in-list tickets)] (field (in-list ticket)) #:when (fails-all-checks? field rules)) + field)) + +(ticket-scanning-error-rate other-tickets ticket-rules) + +;; part 2 +(define valid-tickets (filter (ormap (fails-all-checks? _ ticket-rules) _) other-tickets)) + +(define fields (apply map list valid-tickets)) + +(for/list ([field (in-list fields)]) + (for*/list ( + [rule (in-list ticket-rules)] + #:unless (not (or (range-contains? (field-rule-range1 rule) value) + (range-contains? (field-rule-range2 rule) value)))) + (field-rule-name rule))) diff --git a/racket/aoc2021/day-01/day-01.pl b/racket/aoc2021/day-01/day-01.pl new file mode 100644 index 0000000..d3c3fa7 --- /dev/null +++ b/racket/aoc2021/day-01/day-01.pl @@ -0,0 +1,20 @@ +:- use_module(library(yall)). +:- use_module(library(apply)). + +get_data(Result) :- + setup_call_cleanup(open("day-01/input.txt", read, In), + (read_string(In, _, Str), + split_string(Str, "\n", "\s\t\n", Lines), + maplist(number_string, Result, Lines)), + close(In)). + +calculate_diffs(Result, WindowWidth) :- + get_data(Xs), + length(TrimLeft, WindowWidth), append(TrimLeft, RightSide, Xs), + length(TrimRight, WindowWidth), append(LeftSide, TrimRight, Xs), + maplist([X, Y, Z]>>(Z is Y - X), LeftSide, RightSide, Diffs), + include([X]>>(X > 0), Diffs, Increases), + length(Increases, Result). + +part1_answer(Result) :- calculate_diffs(Result, 1). +part2_answer(Result) :- calculate_diffs(Result, 3).
\ No newline at end of file diff --git a/racket/aoc2021/day-01/day-01.rkt b/racket/aoc2021/day-01/day-01.rkt new file mode 100644 index 0000000..48ef158 --- /dev/null +++ b/racket/aoc2021/day-01/day-01.rkt @@ -0,0 +1,20 @@ +#lang racket +(require advent-of-code + threading) + +;; part 1 +(define sensor-data + (~> (open-aoc-input (find-session) 2021 1 #:cache (string->path "./cache")) + (port->list read _))) + +(define (count-increases data offset) + (for/sum ([x (in-list data)] + [y (in-list (drop data offset))] + #:when (< x y)) + 1)) + +(~a "Part 1: " (count-increases sensor-data 1)) + +;; part 2 + +(~a "Part 2: " (count-increases sensor-data 3))
\ No newline at end of file diff --git a/racket/aoc2021/day-02/day-02.ex b/racket/aoc2021/day-02/day-02.ex new file mode 100644 index 0000000..d37ab05 --- /dev/null +++ b/racket/aoc2021/day-02/day-02.ex @@ -0,0 +1,32 @@ +defmodule Day02 do + def part_one(data) do + data + |> Enum.reduce(%{pos: 0, dep: 0}, &method_one/2) + |> get_answer() + end + + def part_two(data) do + data + |> Enum.reduce(%{pos: 0, dep: 0, aim: 0}, &method_two/2) + |> get_answer() + end + + defp method_one({:forward, x}, s), do: %{s | pos: s.pos + x} + defp method_one({:up, x}, s), do: %{s | dep: s.dep - x} + defp method_one({:down, x}, s), do: %{s | dep: s.dep + x} + + defp method_two({:forward, x}, s), do: %{s | pos: s.pos + x, dep: s.dep + s.aim * x} + defp method_two({:up, x}, s), do: %{s | aim: s.aim - x} + defp method_two({:down, x}, s), do: %{s | aim: s.aim + x} + + defp get_answer(s), do: s.pos * s.dep +end + +data = + File.read!("day-02/input.txt") + |> String.split("\n", trim: true) + |> Enum.map(&String.split/1) + |> Enum.map(fn [dir, amt] -> {String.to_atom(dir), String.to_integer(amt)} end) + +Day02.part_one(data) |> IO.inspect() +Day02.part_two(data) |> IO.inspect() diff --git a/racket/aoc2021/day-02/day-02.rkt b/racket/aoc2021/day-02/day-02.rkt new file mode 100644 index 0000000..0bd0c3d --- /dev/null +++ b/racket/aoc2021/day-02/day-02.rkt @@ -0,0 +1,24 @@ +#lang racket +(require advent-of-code + threading + algorithms) + +(define motion-data + (~> (open-aoc-input (find-session) 2021 2 #:cache (string->path "./cache")) + (port->list read _) + (chunks-of _ 2))) + +;; part 1 +(for/fold ([depth 0] [position 0] #:result (* depth position)) ([motion (in-list motion-data)]) + (match motion + [(list 'forward x) (values depth (+ position x))] + [(list 'up x) (values (- depth x) position)] + [(list 'down x) (values (+ depth x) position)])) + +;; part 2 +(for/fold ([aim 0] [depth 0] [position 0] #:result (* depth position)) + ([motion (in-list motion-data)]) + (match motion + [(list 'forward x) (values aim (+ depth (* aim x)) (+ position x))] + [(list 'up x) (values (- aim x) depth position)] + [(list 'down x) (values (+ aim x) depth position)])) diff --git a/racket/aoc2021/day-03/day-03.rkt b/racket/aoc2021/day-03/day-03.rkt new file mode 100644 index 0000000..95b7efd --- /dev/null +++ b/racket/aoc2021/day-03/day-03.rkt @@ -0,0 +1,39 @@ +#lang racket +(require advent-of-code + threading) + +(define data + (~> (open-aoc-input (find-session) 2021 3 #:cache (string->path "./cache")) + port->lines + (map string->list _))) + +;; part 1 +(define most-common-bits + (for*/list ([row (in-list (apply map list data))] [len (in-value (length data))]) + (if (> (count (λ (c) (char=? #\1 c)) row) (/ len 2)) #\1 #\0))) +(define (bit-list->number lst) + (~> lst (apply string _) (string->number _ 2))) + +(define gamma (bit-list->number most-common-bits)) +(define epsilon (~> most-common-bits (map (λ (c) (if (char=? c #\1) #\0 #\1)) _) bit-list->number)) + +(* gamma epsilon) + +;; part 2 +(define (rating-search data comparison) + (for/fold ([candidates data] #:result (bit-list->number (first candidates))) + ([bit (in-list most-common-bits)] [bit-index (in-range 0 (length most-common-bits))]) + #:break (= 1 (length candidates)) + (define keep-bit + (~> candidates + (apply map list _) + (list-ref _ bit-index) + (count (λ (c) (char=? #\1 c)) _) + (comparison _ (/ (length candidates) 2)) + (if _ #\1 #\0))) + (filter (λ (row) (char=? keep-bit (list-ref row bit-index))) candidates))) + +(define oxygen-rating (rating-search data >=)) +(define scrubber-rating (rating-search data <)) + +(* oxygen-rating scrubber-rating) diff --git a/racket/aoc2021/day-04/day-04.rkt b/racket/aoc2021/day-04/day-04.rkt new file mode 100644 index 0000000..c572f74 --- /dev/null +++ b/racket/aoc2021/day-04/day-04.rkt @@ -0,0 +1,51 @@ +#lang racket +(require advent-of-code + threading + (only-in algorithms chunks-of)) + +(define data + (for/list ([l (in-lines (open-aoc-input (find-session) 2021 4 #:cache (string->path "./cache")))] + #:unless (equal? l "")) + l)) + +(define call-sheet (~> data car (string-split ",") (map string->number _))) +(define bingo-cards + (~> data cdr (map string-split _) (map (λ (row) (map string->number row)) _) (chunks-of 5))) + +(define test-card (first bingo-cards)) + +(define (mark-card card call) + (for/list ([row (in-list card)]) + (for/list ([col (in-list row)]) + (if (eq? col call) 'X col)))) + +(define (check-card card) + (for/or ([row (in-sequences card (apply map list card))]) + (equal? row '(X X X X X)))) + +(define (multiply-by-last-call n call) + (match n + ['X 0] + [n (* n call)])) + +(define (evaluate-cards cards calls [check (curry ormap check-card)] [exception not]) + (for/fold ([current-cards cards] + [last-call 0] + #:result (~> current-cards + (findf check-card _) + flatten + (map (λ (n) (multiply-by-last-call n last-call)) _) + (apply + _))) + ([call (in-list calls)]) + #:break (check current-cards) + (values (for/list ([card (in-list current-cards)] #:unless (exception card)) + (mark-card card call)) + call))) + +;; part 1 +(evaluate-cards bingo-cards call-sheet) +;; part 2 +(evaluate-cards bingo-cards + call-sheet + (λ (cards) (and (= (length cards) 1) (check-card (first cards)))) + check-card) diff --git a/racket/aoc2021/day-05/day-05.rkt b/racket/aoc2021/day-05/day-05.rkt new file mode 100644 index 0000000..e568490 --- /dev/null +++ b/racket/aoc2021/day-05/day-05.rkt @@ -0,0 +1,57 @@ +#lang racket +(require advent-of-code + threading) + +(define data + (for/list ([l (in-lines (open-aoc-input (find-session) 2021 5 #:cache (string->path "./cache")))]) + (~> l (string-replace " -> " ",") (string-split ",") (map string->number _)))) + +(define (trace-line! x y vec) + (define linear-coord (+ y (* 1000 x))) + (vector-set! vec linear-coord (+ 1 (vector-ref vec linear-coord)))) + +(define/match (orthogonal? coord-pair) + [((or (list n _ n _) (list _ n _ n))) #t] + [(_) #f]) + +(define-values (orthogonal-lines diagonal-lines) (partition orthogonal? data)) + +(define (dir a b) + (if (< a b) 1 -1)) + +(define (trace-orthogonal! coord-pairs tracer result) + (for ([coord-pair (in-list coord-pairs)]) + (match coord-pair + [(list x y1 x y2) + (for ([y (inclusive-range y1 y2 (dir y1 y2))]) + (tracer x y result))] + [(list x1 y x2 y) + (for ([x (inclusive-range x1 x2 (dir x1 x2))]) + (tracer x y result))]))) + +(define (trace-diagonal! coord-pairs tracer result) + (for ([coord-pair (in-list coord-pairs)]) + (match-define (list x1 y1 x2 y2) coord-pair) + (for ([x (inclusive-range x1 x2 (dir x1 x2))] [y (inclusive-range y1 y2 (dir y1 y2))]) + (tracer x y result)))) + +;; part 1 +(define sea-floor (make-vector 1000000)) +(trace-orthogonal! orthogonal-lines trace-line! sea-floor) +(vector-count (curry <= 2) sea-floor) + +;; part 2 +;; since the orthogonal lines have already been traced, +;; all I need to do is add the diagonal ones to the existing vector +(trace-diagonal! diagonal-lines trace-line! sea-floor) +(vector-count (curry <= 2) sea-floor) + +;; alternate sparse representation +(define (trace-line-sparse! x y dict) + (hash-update! dict (list x y) (curry + 1) 0)) + +(define sea-floor-dict (make-hash)) +(trace-orthogonal! orthogonal-lines trace-line-sparse! sea-floor-dict) +(count (curry <= 2) (hash-values sea-floor-dict)) +(trace-diagonal! diagonal-lines trace-line-sparse! sea-floor-dict) +(count (curry <= 2) (hash-values sea-floor-dict)) diff --git a/racket/aoc2021/day-06/day-06.ex b/racket/aoc2021/day-06/day-06.ex new file mode 100644 index 0000000..efe10e4 --- /dev/null +++ b/racket/aoc2021/day-06/day-06.ex @@ -0,0 +1,35 @@ +defmodule Day06 do + def next_day(state) do + with one_day_older <- Enum.into(state, %{}, fn {k, v} -> {k - 1, v} end), + {n, s} <- Map.pop(one_day_older, -1, 0) do + Map.update(s, 6, n, &(&1 + n)) + |> Map.put(8, n) + end + end +end + +school = + with {:ok, data} <- File.read("input.txt") do + data + |> String.trim() + |> String.split(",") + |> Enum.map(&String.to_integer/1) + end + +starting_state = Enum.frequencies(school) + +Enum.reduce( + Enum.to_list(1..80), + starting_state, + fn _, acc -> Day06.next_day(acc) end +) +|> Enum.reduce(0, fn {_, v}, acc -> v + acc end) +|> IO.inspect() + +Enum.reduce( + Enum.to_list(1..256), + starting_state, + fn _, acc -> Day06.next_day(acc) end +) +|> Enum.reduce(0, fn {_, v}, acc -> v + acc end) +|> IO.inspect() diff --git a/racket/aoc2021/day-06/day-06.livemd b/racket/aoc2021/day-06/day-06.livemd new file mode 100644 index 0000000..5ab794f --- /dev/null +++ b/racket/aoc2021/day-06/day-06.livemd @@ -0,0 +1,152 @@ +<!-- vim: set syntax=markdown: --> +<!-- livebook:{"persist_outputs":true} --> + +# Advent of Code 2021, Day 6 + +## Short problem summary + +A school of fish reproduce according to the following rules: + +* Every fish has an "internal timer" +* The timer decrements by one every day +* If the timer is at 0, the timer is instead reset to 6, + and a new fish with an internal timer of 8 is added to the school + +Questions: + +1. How many fish are in the school after 80 days? +2. How many fish are in the school after 256 days? + +## Setting up + +The initial input is a list of fish, represented by the initial value of their internal timer: + +```elixir +school = + with {:ok, data} <- File.read("day-06/input.txt") do + data + |> String.trim() + |> String.split(",") + |> Enum.map(&String.to_integer/1) + end +``` + +```output +[5, 4, 3, 5, 1, 1, 2, 1, 2, 1, 3, 2, 3, 4, 5, 1, 2, 4, 3, 2, 5, 1, 4, 2, 1, 1, 2, 5, 4, 4, 4, 1, 5, + 4, 5, 2, 1, 2, 5, 5, 4, 1, 3, 1, 4, 2, 4, 2, 5, 1, ...] +``` + +Every fish with the same starting internal timer will reproduce at the same time, +as will all of the children of those fish and their children, and so forth, +so we don't need to track individual fish; we just need to group the fish based on +their starting internal timer and track those groups throughout the simulation. + +```elixir +starting_state = Enum.frequencies(school) +``` + +```output +%{1 => 88, 2 => 45, 3 => 54, 4 => 52, 5 => 61} +``` + +Every time a day passes, the following things happen: + +* All the internal timers decrement by 1 +* The group of fish with an internal timer of -1 is reset to 6 + (added to any existing fish whose timers are already at 6), + and an equal-sized group of fish with internal timer 8 is added + +```elixir +defmodule Day06 do + def next_day(state) do + with one_day_older <- Enum.into(state, %{}, fn {k, v} -> {k - 1, v} end), + {n, s} <- Map.pop(one_day_older, -1, 0) do + Map.update(s, 6, n, &(&1 + n)) + |> Map.put(8, n) + end + end +end + +day1 = Day06.next_day(starting_state) +``` + +```output +%{0 => 88, 1 => 45, 2 => 54, 3 => 52, 4 => 61, 6 => 0, 8 => 0} +``` + +After the first day there's not any fish old enough to reproduce yet, but after the second day, + +```elixir +day2 = Day06.next_day(day1) +``` + +```output +%{0 => 45, 1 => 54, 2 => 52, 3 => 61, 5 => 0, 6 => 88, 7 => 0, 8 => 88} +``` + +The 88 fish whose timers were at 0 have rolled over to 6 and created 88 more fish with timers at 8. + +## Solution + +Now we just need to apply the transformation function the necessary number +of times and sum up the total population in the end: + +```elixir +part1_state = + Enum.reduce( + Enum.to_list(1..80), + starting_state, + fn _, acc -> Day06.next_day(acc) end + ) + |> IO.inspect() + |> Enum.reduce(0, fn {_, v}, acc -> v + acc end) +``` + +```output +%{ + 0 => 24572, + 1 => 43660, + 2 => 30525, + 3 => 48458, + 4 => 41318, + 5 => 47697, + 6 => 57731, + 7 => 23218, + 8 => 33738 +} +``` + +```output +350917 +``` + +Identically for part 2, + +```elixir +part2_state = + Enum.reduce( + Enum.to_list(1..256), + starting_state, + fn _, acc -> Day06.next_day(acc) end + ) + |> IO.inspect() + |> Enum.reduce(0, fn {_, v}, acc -> v + acc end) +``` + +```output +%{ + 0 => 139170477178, + 1 => 162618979933, + 2 => 169389497028, + 3 => 188231720546, + 4 => 207908029672, + 5 => 217769615201, + 6 => 252681772250, + 7 => 117023886952, + 8 => 138124736869 +} +``` + +```output +1592918715629 +``` diff --git a/racket/aoc2021/day-06/day-06.rkt b/racket/aoc2021/day-06/day-06.rkt new file mode 100644 index 0000000..d8855ba --- /dev/null +++ b/racket/aoc2021/day-06/day-06.rkt @@ -0,0 +1,27 @@ +#lang racket +(require advent-of-code + list-utils + threading + racket/hash) + +(define fish-data + (~> (open-aoc-input (find-session) 2021 6 #:cache (string->path "./cache")) + port->string + string-trim + (string-split ",") + (map string->number _))) + +(define (simulate-fish time-period) + (for/fold ([state (frequencies fish-data)] #:result (~> state hash-values (apply + _))) + ([day (inclusive-range 1 time-period)]) + (define day-older-fish + (for/hash ([(days pop) (in-hash state)]) + (values (sub1 days) pop))) + (define breeding-fish (hash-ref day-older-fish -1 0)) + (hash-union (hash-remove day-older-fish -1) (hash 8 breeding-fish 6 breeding-fish) #:combine +))) + +;; part 1 +(simulate-fish 80) + +;; part 2 +(simulate-fish 256) diff --git a/racket/aoc2021/day-06/input.txt b/racket/aoc2021/day-06/input.txt new file mode 100644 index 0000000..ba3c3cc --- /dev/null +++ b/racket/aoc2021/day-06/input.txt @@ -0,0 +1 @@ +5,4,3,5,1,1,2,1,2,1,3,2,3,4,5,1,2,4,3,2,5,1,4,2,1,1,2,5,4,4,4,1,5,4,5,2,1,2,5,5,4,1,3,1,4,2,4,2,5,1,3,5,3,2,3,1,1,4,5,2,4,3,1,5,5,1,3,1,3,2,2,4,1,3,4,3,3,4,1,3,4,3,4,5,2,1,1,1,4,5,5,1,1,3,2,4,1,2,2,2,4,1,2,5,5,1,4,5,2,4,2,1,5,4,1,3,4,1,2,3,1,5,1,3,4,5,4,1,4,3,3,3,5,5,1,1,5,1,5,5,1,5,2,1,5,1,2,3,5,5,1,3,3,1,5,3,4,3,4,3,2,5,2,1,2,5,1,1,1,1,5,1,1,4,3,3,5,1,1,1,4,4,1,3,3,5,5,4,3,2,1,2,2,3,4,1,5,4,3,1,1,5,1,4,2,3,2,2,3,4,1,3,4,1,4,3,4,3,1,3,3,1,1,4,1,1,1,4,5,3,1,1,2,5,2,5,1,5,3,3,1,3,5,5,1,5,4,3,1,5,1,1,5,5,1,1,2,5,5,5,1,1,3,2,2,3,4,5,5,2,5,4,2,1,5,1,4,4,5,4,4,1,2,1,1,2,3,5,5,1,3,1,4,2,3,3,1,4,1,1 diff --git a/racket/aoc2021/day-07/day-07.rkt b/racket/aoc2021/day-07/day-07.rkt new file mode 100644 index 0000000..89d5009 --- /dev/null +++ b/racket/aoc2021/day-07/day-07.rkt @@ -0,0 +1,28 @@ +#lang racket +(require advent-of-code + threading + math/statistics) + +(define crab-data + (~> (open-aoc-input (find-session) 2021 7 #:cache #t) + port->string + string-trim + (string-split ",") + (map string->number _))) + +(define (gauss-sum n) + (/ (* n (+ n 1)) 2)) +(define (compute-fuel-use f crabs align-to) + (for/sum ([crab (in-list crabs)]) (f (abs (- crab align-to))))) + +;; using the fact that the optimum location is at the median +;; of the crabs' starting location for the linear relationship +;; and at a coordinate within 1 unit of the mean for the quadratic one + +(~>> crab-data (median <) (compute-fuel-use identity crab-data)) + +(~>> crab-data + mean + ((λ (m) (list (floor m) (ceiling m)))) + (map (curry compute-fuel-use gauss-sum crab-data)) + (apply min)) diff --git a/racket/aoc2021/day-08/day-08.rkt b/racket/aoc2021/day-08/day-08.rkt new file mode 100644 index 0000000..6476eae --- /dev/null +++ b/racket/aoc2021/day-08/day-08.rkt @@ -0,0 +1,64 @@ +#lang racket +(require threading + list-utils + "../../jj-aoc.rkt") + +(struct trial-data (signal output) #:transparent) + +(define (string->sets s) + (~> s string-split (map (λ~> string->list list->set) _))) + +(define data + (for/list ([l (in-lines (open-day 8))] #:unless (equal? l "")) + (~> l (string-split _ " | ") (map string->sets _) (apply trial-data _)))) + +;; part 1 +(for*/sum ([trial (in-list data)] [output (in-list (trial-data-output trial))] + #:when (ormap (λ~> (= (set-count output))) '(2 3 4 7))) + 1) + +;; part 2 +(define (matching-pattern len trial) + (define solution-set + (for*/list ([signal (in-list (trial-data-signal trial))] #:when (= (set-count signal) len)) + signal)) + (match solution-set + [(list s) s] + [s (apply set-intersect s)])) + +(define (determine-arrangement t) + (let* ([pattern-1 (matching-pattern 2 t)] + [pattern-4 (matching-pattern 4 t)] + [pattern-7 (matching-pattern 3 t)] + [pattern-8 (matching-pattern 7 t)] + [pattern-shared-235 (matching-pattern 5 t)] + [pattern-3 (set-union pattern-1 pattern-shared-235)] + [pattern-9 (set-union pattern-4 pattern-shared-235)] + [pattern-shared-069 (matching-pattern 6 t)] + [pattern-just-f (set-subtract pattern-shared-069 pattern-shared-235)] + [pattern-just-e + (set-subtract pattern-8 (set-union pattern-4 pattern-shared-235 pattern-shared-069))] + [pattern-2 (set-union (set-subtract pattern-3 pattern-just-f) pattern-just-e)] + [pattern-just-c (set-subtract (set-intersect pattern-4 pattern-7) pattern-just-f)] + [pattern-6 (set-subtract pattern-8 pattern-just-c)] + [pattern-5 (set-subtract pattern-6 pattern-just-e)] + [pattern-0 (set-union (set-subtract pattern-8 pattern-shared-235) pattern-shared-069)]) + (~> (list pattern-0 + pattern-1 + pattern-2 + pattern-3 + pattern-4 + pattern-5 + pattern-6 + pattern-7 + pattern-8 + pattern-9) + enumerate + make-hash))) + +(for/sum ([trial (in-list data)]) + (~>> trial + trial-data-output + (map (λ~>> (hash-ref (determine-arrangement trial)))) + (apply ~a) + string->number)) diff --git a/racket/aoc2021/day-09/day-09.livemd b/racket/aoc2021/day-09/day-09.livemd new file mode 100644 index 0000000..3b984a5 --- /dev/null +++ b/racket/aoc2021/day-09/day-09.livemd @@ -0,0 +1,138 @@ +<!-- vim: set syntax=markdown: --> +<!-- livebook:{"persist_outputs":true} --> + +# Advent of Code 2021, Day 9 + +## Short problem summary + + + +**Part 1.** Find the total "risk level" of all the local minima on a relief map, represented by a +100 $\times$ 100 array of integers from 1 to 9. Only orthogonal neighbors count. +The risk level is the elevation plus one. + +**Part 2.** Find the product of the areas of the three largest basins on the relief map. Basins are regions +bordered by the edges of the map or by points with elevation 9. +Again, only orthogonal neighbors count. + +## Setup + +I'm using [Nx](https://github.com/elixir-nx/nx/tree/main/nx#readme) tensors +since this problem will require a lot of arbitrary indexing. + +```elixir +Mix.install([ + {:nx, "~> 0.1.0-dev", github: "elixir-nx/nx", sparse: "nx", override: true} +]) +``` + +```output +:ok +``` + +Bringing in the data as a new 2D tensor: + +```elixir +floor = + with {:ok, data} <- File.read("input.txt") do + data + |> String.trim() + |> String.split("\n") + |> Enum.flat_map(&String.graphemes/1) + |> Enum.map(&String.to_integer/1) + |> Enum.chunk_every(100) + |> Nx.tensor(names: [:y, :x]) + end +``` + +```output +#Nx.Tensor< + s64[y: 100][x: 100] + [ + [7, 6, 5, 9, 9, 9, 1, 0, 9, 8, 9, 9, 9, 8, 7, 6, 5, 7, 9, 9, 1, 0, 1, 2, 9, 8, 7, 9, 9, 9, 9, 8, 7, 6, 4, 3, 2, 1, 2, 3, 4, 5, 9, 8, 7, 4, 3, 4, 5, 5, ...], + ... + ] +> +``` + +## Part 1 + +For a given coordinate $(x, y)$, we want to examine its orthogonal neighbors, +but only the ones that exist within the map. + +```elixir +defmodule Day09.Part1 do + def neighbors({x, y}) do + [{x + 1, y}, {x - 1, y}, {x, y + 1}, {x, y - 1}] + |> Enum.filter(fn {x, y} -> x >= 0 && x < 100 && y >= 0 && y < 100 end) + end +end +``` + +```output +{:module, Day09.Part1, <<70, 79, 82, 49, 0, 0, 7, ...>>, {:neighbors, 1}} +``` + +Now scan the whole array to check each cell's neighbors, +and return the "risk level" for each local minimum, then accumulate the sum. + +```elixir +risk_level = + for x <- 0..99, + y <- 0..99, + reduce: 0 do + acc -> + Day09.Part1.neighbors({x, y}) + |> Enum.all?(fn {xn, yn} -> floor[x][y] < floor[xn][yn] end) + |> if(do: acc + Nx.to_number(floor[x][y]) + 1, else: acc) + end +``` + +```output +591 +``` + +## Part 2 + +Now we need to recursively walk outwards from each previously-unwalked point +until we can't walk any further. An agent will keep track of the visited points. + +```elixir +defmodule Day09.Part2 do + def walkable?({x, y} = coords, tensor, pid) do + Nx.to_number(tensor[x][y]) < 9 && not Agent.get(pid, fn m -> Map.has_key?(m, coords) end) + end + + def walk_it(coords, tensor, pid) do + if walkable?(coords, tensor, pid) do + Agent.update(pid, fn m -> Map.put(m, coords, true) end) + + for c <- Day09.Part1.neighbors(coords) do + walk_it(c, tensor, pid) + end + |> Enum.reduce(1, fn x, acc -> acc + x end) + else + 0 + end + end +end +``` + +```output +{:module, Day09.Part2, <<70, 79, 82, 49, 0, 0, 11, ...>>, {:walk_it, 3}} +``` + +```elixir +{:ok, tracker} = Agent.start_link(fn -> %{} end) + +for x <- 0..99, y <- 0..99 do + Day09.Part2.walk_it({x, y}, floor, tracker) +end +|> Enum.sort(:desc) +|> Enum.take(3) +|> Enum.reduce(fn x, acc -> acc * x end) +``` + +```output +1113424 +``` diff --git a/racket/aoc2021/day-09/day-09.rkt b/racket/aoc2021/day-09/day-09.rkt new file mode 100644 index 0000000..d550a9e --- /dev/null +++ b/racket/aoc2021/day-09/day-09.rkt @@ -0,0 +1,59 @@ +#lang racket + +(require threading + "../../jj-aoc.rkt") + +(define sea-floor-data + (for/vector ([l (in-lines (open-day 9))] #:unless (equal? l "")) + (~>> l string->list (map (λ~>> ~a string->number)) list->vector))) + +(define max-rows (vector-length sea-floor-data)) +(define max-cols (vector-length (vector-ref sea-floor-data 0))) +(define-values (min-rows min-cols) (values 0 0)) + +(define (vector2d-ref vec coord) + (match-define `(,r ,c) coord) + (~> vec (vector-ref r) (vector-ref c))) + +(define (adjacent coords) + (match-define `(,r ,c) coords) + `((,(add1 r) ,c) (,(sub1 r) ,c) (,r ,(add1 c)) (,r ,(sub1 c)))) + +(define (valid-coordinate coord) + (match-define `(,r ,c) coord) + (and (>= r min-rows) (< r max-rows) (>= c min-cols) (< c max-cols))) + +;; part 1 +(define (lowest-point? vec coord) + (for*/and ([neighbor (in-list (adjacent coord))] #:when (valid-coordinate neighbor)) + (< (vector2d-ref vec coord) (vector2d-ref vec neighbor)))) + +(for*/sum ([r (in-range min-rows max-rows)] [c (in-range min-cols max-cols)] + [coord (in-value `(,r ,c))] + #:when (lowest-point? sea-floor-data coord)) + (add1 (vector2d-ref sea-floor-data coord))) + +;; part 2 +;; all the basins are bordered by the edges or by ridges of elevation 9, +;; so it's not really necessary to start at a low point +(define walked (make-hash)) + +(define (walkable? vec coord) + (and (< (vector2d-ref vec coord) 9) (not (hash-has-key? walked coord)))) + +(define (walk-the-basin vec coord) + (cond + [(walkable? vec coord) + (hash-set! walked coord 'visited) + (add1 (for/sum [(neighbor (in-list (adjacent coord))) #:when (valid-coordinate neighbor)] + (walk-the-basin vec neighbor)))] + [else 0])) + +(define basins + (for*/list ([r (in-range min-rows max-rows)] + [c (in-range min-cols max-cols)] + [coord (in-value `(,r ,c))] + #:when (walkable? sea-floor-data coord)) + (walk-the-basin sea-floor-data coord))) + +(~> basins (sort >) (take 3) (apply * _)) diff --git a/racket/aoc2021/day-09/input.txt b/racket/aoc2021/day-09/input.txt new file mode 100644 index 0000000..322b31f --- /dev/null +++ b/racket/aoc2021/day-09/input.txt @@ -0,0 +1,100 @@ +7659991098999876579910129879999876432123459874345567890126678999876588975767899323456989767899432101 +8998789987898765467891239868899876541012398765123456789234567897643467894656798912399878948678944212 +9867678976789878598954398756789997632343459873234569898765679999856578943547987893989865434567894323 +7654567895896989679767499847994398755456579987656778969876892198767989652129876889876976725678965734 +8767678954345698799898987659943219876577694598787889545987931019879996543298965679965987438789876799 +9898789921239799898989899798794399987688965679898993534598942123989987654987654567894596549899989987 +4969999892398989987876789987689989998789898789979992123989653934598798965976543678943987678999999876 +3459898789497878976545678986567878999895679898768989239879869896789659879865432469432198799998789765 +2498765699976567995434389765434567899934989977655679356965998789896545989865321258921019999987678954 +3987654398765456789321238979325456799329898766434798999876797698987432198754310347894329789876567893 +4696543219876967998910147998214345678998789954323987689999977567898543479885541456789498678988678932 +5987654423987878987521236987601234567891678893219876567898765479987654567976632367899987569899789321 +6798767834698989997432345698524568698932456789398765466989876567898765689987545478959768456789893210 +7899898945679499876545656997434578799843569892987654345678989679999876799798658569349654367878964322 +8977999498789398987896769876545689998767678931999869656789698789999987987698767895498743212567895433 +9656789329898987899929879987676790199898989949877998767896559899878998977569978999987654323458986654 +8797995434987896989434989798989891987919499896765569888921434998767789865452989998998795434569997765 +9979899549876785678945995639698999876329398764354456999990125899545678954321299987899987895678949878 +9865678998765434567899894324567892985498999863212347898989436798434599876210389996789999998789434999 +7654567899896647978989789212459921296987899954393478987678945987324879965341567895679891019998959865 +8543878901987656789765678901268933459876999895989569658567959876412568897432467894599789923987898654 +9212389919898767897654577892999545998765798789878978945456899954323456789576578923989679899876789543 +9543457898769879986543456789889959876543987676567899432345789976436567897697989219878565678965678932 +8754568987654989765432347898767899989432976543456964321012498987545679998989892109867434699764567891 +9867689298543299986541034987656789998743989654677895632134987898696789989878789298754324789543488989 +1978792129654569987732129874543567897654799965789976853239876799989999878765678999865455697601245678 +0989893098765678998653298763212375789795679878994989966398754889879898765464569899976566789212346789 +9898954239987789998784987654301234699989789989873398765459765678968789884323456789987677894323456898 +8777895345698999899895698976214345789878999997762129876589896789345678965434567999898989976564568967 +7656976457899019767987899765423456789569899876543299987678987891234569876545898998769394987875679456 +6546899568992198657898939878534677893456789987654989898789398932347678999856789889843212398986989345 +5435688979879932545679929989665788902569991298779878789899299543458789798767895678932101569997890123 +4323567899767891434589898998776899543457899999898768655978987654569899679878934569643219878998921254 +6764579987658910123456797879887899656568978789987653234567998785678998532989545678954399989999432765 +7875689998767891234567896569998998967989767698798767145678999896789987643498756789765989992987543876 +8976796899878932545678997998769467899899854599659898657789985959897898784569867899876978931098954987 +9697895799989873467889989999652348999799965678943939768999664543956789895679878979989867892129895698 +4598954569899964578999878987643499997689978789432129879998543212345678976989989459899756789298789789 +3499543798798765699998767898984987843579899996583235989987632105468789987899992398789898999987678991 +4985432987679876789999658989876986532498798889874346798798743236589893498989891987678989459986568990 +9876521296567987899876545878989875421987656778965498987669654587679999999876789898547678998765456789 +9876432987678998998765434569998767410996545567896569896556965689789987898865676789435589765432369999 +3987543498799549769886325679987654329877434456789698789439879789899976987764545678923459879321287899 +4599656789989432458997212568999769498764321298999987688956989897999899876743236789212398998932456998 +9798767897678921367989323459998998999875210147899876567897891956789798765432145794301987897893569997 +8999878996569432349876534569987687898654321236789767456789932345679659976545012689419876896789878986 +7899989987458943499998765678976546789985434545678954345679543567789545987983234568998765645678989565 +6789199654347896589989876789987434579876545758789543237789654579895434499874356789329874234567893434 +5679298789756789678976987899874323459987859767899654345678965989965421298765667895499932125689932123 +4568999898969898789765698998765212398498869878998765656789879898965432399876878976987891034567894034 +3456789987898999899894329987654323987349978989999898767896999797896743987987899989896789123678985125 +2346899876767892999989212398765499876567989999899959878934987676989659876798945698765695434679876789 +1256998765458993498979903459876989987698999999789943989949898455678998765759899987654989545689989899 +4349879876569989986567894967989878999789989897678892099898789334569987674545678998743478957897696999 +5478968987678979765456999898998767879899876789546789298788699212989876543234567998932569768998565678 +6568956998789569876567897789999857867998975695437995987657598909898995432123459886521678989895434567 +7678939879892458987678956699896645456987764789567894696543467898767986321019598765432789496789323878 +8789998765901456798789545598775435349876543999698943495432356789656997432198969876547892345689439989 +9895987654312345789899434459654324234987875898789212989321234896549876545997656989856921234579998997 +6954398767433566789998921398743210125698986789894309878540145789432997859876543398767890123467897686 +5695999876544678998787892987654341234589987895999498765431236894320989767987432129898921294878987575 +4989899987698789987656789398765492395678998934998989896549898965999878979876421012969939989999098464 +2976789998789899876543789249876989989989239019887678987656789879878767898985432123457898679889198323 +9895678999894968987654590123989879978990129198764569998987894998765459987899543434568987546778987634 +8654547899923656798985891294598768969893298998765678999598912349821398795698976546699876435569876545 +6543336789012349899876789989987656756789987899976789989499909496543989654567898687987665523456987676 +7652125978923598987987899878996541234567896569899899978987898987859878965678998789876563212345698787 +6543234567894987876798998769876532345978943456799998767896987598998969898799989898765432101234569898 +7685346878999876765689987655989645567899212566778987658965398459987856789899978999876543212385789999 +8876798989998765434569976743498756789956401234568998789876999349876546899988769899987654525678999999 +9987899697989954323798765632359867899543212345679239899989899956997635789879656789999769434789898989 +4599996546567893212987654321235978998764637566989139999998789899986523998967545698999898945699787678 +3499989435457899433498765435348989689895547677891098988997655798765439897645634567899976896789676567 +2989978921345678994569876546757894578987678788992987677893234569898698765430125698999865689896543456 +9879868935458789989778987987868943989998989999789987566989123456989789876321234789997684578965432123 +8965657899599999878989998999979659899879596545678976435778934569879894987432545679876543467896673294 +7654545698989997569999999998989799789965432434789897324567895979964933498545789789987432456998784989 +6543234987978965478999899987899987656974321023498788212379976798763212379676899899997544567899999878 +5432129876568896567898789656789976549875432164589654323568987987654343456987895978999655778956798967 +4321019987456789678987698943495987678987543765678965454569998998765454567898954567898776789345987954 +5432998765345698989996587992976798789798765897789876875678999869876569878979943459979987893212986543 +6549879876234767899985456789897899997659897899897987996789998754997878989767892598965398994323497632 +7698765438123456789876323496789992198943998943956998987899987643298989997656999987890139989434598745 +8899954321014567899865212345678989989894989012345899498989998759109499998747988976789239878965987656 +9998765732125678999954345489789679878789876543456789349678939998912349876434567895678949867896798767 +4349876653346789998769497679898998765699998754678991234569019887893498765325658934589998758659999878 +4239999778659899899898989989967987854897899869789893965678998766799987654312349898699896549237899989 +9398999889767998789987978995459876543786789878898789896799987655678998895401256789798765432126789997 +8987899999879987678976567894398765432645699989997678789929876543767899986212868999899654321034599896 +7856789432989876569875456894298764321237989899986565678910987432347678997343479756998789532123456789 +6545678921098765454986587932129879854349876799867434568924596541234599987654569545689898743454567895 +5436889932129854323697698943234988765698765987654523567895987762345789598968678934578999654765678954 +4321968894298765634598789954445699878987654599763212348997898943579893459899789545678998765878789875 +5872456789349878745679897895768789989877653459894103456789999874568932598789897676789129878989899986 +8763878995467989856789976979879899998765432345989294567899899865679943987699998989899234989299999899 +7654567896568995977891234568999978919876521239878989678956756978989894976568999195978976790199899788 +8765698987878923988910123456789567923987432399765679789432347989998769876456789234567897892988698677 +9878789498999219899321256587995456894898643989813478997643458994987656987567894345679998999876545556 +2989893219879998765432347898932345995798759876524567899856969543499897897698987457989899298765434345 +1099954523467899876545456999545476789899899976435788923987897652101998998789876567897654349854321237 diff --git a/racket/aoc2021/day-10/day-10.rkt b/racket/aoc2021/day-10/day-10.rkt new file mode 100644 index 0000000..ea1b389 --- /dev/null +++ b/racket/aoc2021/day-10/day-10.rkt @@ -0,0 +1,57 @@ +#lang racket + +(require math/statistics + threading + "../../jj-aoc.rkt") + +(define chunks (port->lines (open-day 10 2021))) + +(define (opening-bracket? c) + (member c (string->list "([{<"))) + +(define (matching-brackets? c-left c-right) + (member (string c-left c-right) '("()" "[]" "{}" "<>"))) + +(define (parse-brackets lst [acc '()]) + (cond + [(empty? lst) acc] + [(opening-bracket? (first lst)) (parse-brackets (rest lst) (cons (first lst) acc))] + [(matching-brackets? (first acc) (first lst)) (parse-brackets (rest lst) (rest acc))] + [else (get-score (first lst))])) + +;; part 1 +(define (get-score c) + (match (string c) + [")" 3] + ["]" 57] + ["}" 1197] + [">" 25137])) + +(define (score-invalid-string chunk) + (match (parse-brackets (string->list chunk)) + [(? list?) 0] + [n n])) + +(for/sum ([chunk (in-list chunks)]) (score-invalid-string chunk)) + +;; part 2 +(define (completion-score lst) + (for/fold ([score 0]) ([c (in-list lst)]) + (define val + (match (string c) + ["(" 1] + ["[" 2] + ["{" 3] + ["<" 4])) + (+ (* 5 score) val))) + +(define (score-incomplete-string chunk) + (match (parse-brackets (string->list chunk)) + [(? list? lst) (completion-score lst)] + [n 0])) + +(~>> (for*/list ([chunk (in-list chunks)] + [score (in-value (score-incomplete-string chunk))] + #:when (> score 0)) + score) + (median <)) diff --git a/racket/aoc2021/day-11/day-11.rkt b/racket/aoc2021/day-11/day-11.rkt new file mode 100644 index 0000000..bc22991 --- /dev/null +++ b/racket/aoc2021/day-11/day-11.rkt @@ -0,0 +1,56 @@ +#lang racket +(require "../../jj-aoc.rkt" + threading) + +(define coords + (~>> (for/list ([r (in-range 10)]) + (for/list ([c (in-range 10)]) + (cons r c))) + (apply append))) + +(define octopus-data + (~>> (for/list ([l (in-lines (open-day 11 2021))] #:unless (equal? l "")) + (~>> l string->list (map (λ~>> ~a string->number)))) + (apply append) + (map cons coords) + make-hash)) + +(define total-length (hash-count octopus-data)) +(define row-length (sqrt total-length)) + +(define (adjacent-to coord) + (match-define (cons r c) coord) + (for*/list ([row (in-list '(-1 0 1))] [col (in-list '(-1 0 1))] #:unless (= 0 row col)) + (cons (+ r row) (+ c col)))) + +(define (simulate-octopi-step data) + (define flashed-this-step (mutable-set)) + + (let look-for-more-flashes ([octopi (for/hash ([(k v) data]) + (values k (add1 v)))] + [flashes-so-far 0]) + (define-values (next-octopus-update flashes-this-update) + (for*/fold ([octopi octopi] [flashes 0]) + ([(p x) (in-hash octopi)] #:when (> x 9) #:unless (set-member? flashed-this-step p)) + (set-add! flashed-this-step p) + (define flashed-octopi + (for*/fold ([o (hash-set octopi p 0)]) + ([adj (in-list (adjacent-to p))] + #:when (hash-has-key? o adj) + #:unless (set-member? flashed-this-step adj)) + (hash-update o adj add1))) + (values flashed-octopi (add1 flashes)))) + (if (> flashes-this-update 0) + (look-for-more-flashes next-octopus-update (+ flashes-so-far flashes-this-update)) + (values next-octopus-update flashes-so-far)))) + +;; part 1 +(for/fold ([octopi octopus-data] [total-flashes 0] #:result total-flashes) ([step (in-range 100)]) + (define-values [next-state flashes-from-this-state] (simulate-octopi-step octopi)) + (values next-state (+ total-flashes flashes-from-this-state))) + +;; part 2 +(for/fold ([octopi octopus-data] [synchro-step 0] #:result synchro-step) ([step (in-naturals 1)]) + (define-values [next-state flashes-from-this-state] (simulate-octopi-step octopi)) + #:final (= flashes-from-this-state 100) + (values next-state step)) diff --git a/racket/aoc2021/day-12/day-12.rkt b/racket/aoc2021/day-12/day-12.rkt new file mode 100644 index 0000000..18ed86f --- /dev/null +++ b/racket/aoc2021/day-12/day-12.rkt @@ -0,0 +1,38 @@ +#lang racket +(require "../../jj-aoc.rkt" + threading) + +(define path-pairs + (for/list ([l (in-lines (open-day 12 2021))]) + (match (string-split l "-") + [(list start end) (cons start end)]))) + +(define edges-hash (make-hash)) + +(for ([pair (in-list path-pairs)]) + (match-define (cons start end) pair) + (hash-update! edges-hash start (curry cons end) '()) + (hash-update! edges-hash end (curry cons start) '())) + +;; part 1 +(define (backtracking-disallowed? next prevs) + (and (equal? (string-downcase next) next) (member next prevs))) + +(define (look-for-next-cave [path-list '("start")] #:only-one-visit? [visit-used-up? #t]) + (define current-cave (car path-list)) + (cond + [(equal? current-cave "end") (list path-list)] + [else + (~>> (for/list ([next-path (in-list (hash-ref edges-hash current-cave null))] + #:when (and (not (equal? next-path "start")) + (not (and (backtracking-disallowed? next-path path-list) + visit-used-up?)))) + (look-for-next-cave + (cons next-path path-list) + #:only-one-visit? (or (backtracking-disallowed? next-path path-list) visit-used-up?))) + (apply append))])) + +(~> (look-for-next-cave) length time) + +;; part 2 +(~> (look-for-next-cave #:only-one-visit? #f) length time) diff --git a/racket/aoc2021/day-13/day-13.rkt b/racket/aoc2021/day-13/day-13.rkt new file mode 100644 index 0000000..153eabc --- /dev/null +++ b/racket/aoc2021/day-13/day-13.rkt @@ -0,0 +1,57 @@ +#lang racket +(require "../../jj-aoc.rkt" + threading) + +(define (make-pt x y) + (hash 'x x 'y y)) +(struct fold (dir loc) #:transparent) +(define (make-fold dir loc) + (fold (string->symbol dir) (string->number loc))) + +(define data (port->lines (open-day 13 2021))) +(define-values (points-list folds-list) (splitf-at data (λ~> (equal? "") not))) + +(define pts + (for/set ([pt (in-list points-list)]) + (~> pt (string-split ",") (map string->number _) (apply make-pt _)))) + +(define folds + (for/list ([f (in-list (rest folds-list))]) + (~>> f (regexp-match #px"fold along (.)=(.*)") rest (apply make-fold)))) + +(define (fold-over f pts) + (define dir (fold-dir f)) + (define loc (fold-loc f)) + (for/set ([pt (in-set pts)]) + (cond + [(> (hash-ref pt dir) loc) (hash-update pt dir (λ (l) (- (* 2 loc) l)))] + [else pt]))) + +;; part 1 +(~>> pts (fold-over (first folds)) set-count) + +;; part 2 +(define final-pts + (for/fold ([pt pts]) ([f (in-list folds)]) + (fold-over f pt))) + +(define (max-dim pts dim) + (~>> (for/list ([pt (in-set pts)]) + (hash-ref pt dim)) + (apply max))) + +(for ([y (in-inclusive-range 0 (max-dim final-pts 'y))]) + (~>> (for/list ([x (in-inclusive-range 0 (max-dim final-pts 'x))]) + (if (set-member? final-pts (hash 'x x 'y y)) #\█ #\space)) + (apply string) + println)) + +#| +for this data set, the result looks like +" ██ █ █ ██ ██ ███ ██ ██ █ █" +"█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █" +"█ █ ████ █ █ █ █ █ █ █ █ █" +"████ █ █ █ ██ █ ███ █ ██ ████ █ █" +"█ █ █ █ █ █ █ █ █ █ █ █ █ █ █" +"█ █ █ █ ███ ██ █ ███ █ █ ██ " +|# diff --git a/racket/aoc2021/day-14/day-14.rkt b/racket/aoc2021/day-14/day-14.rkt new file mode 100644 index 0000000..e445694 --- /dev/null +++ b/racket/aoc2021/day-14/day-14.rkt @@ -0,0 +1,61 @@ +#lang racket +(require "../../jj-aoc.rkt" + threading + memoize + algorithms + list-utils) + +(define data (port->lines (open-day 14 2021))) + +(define (starting-polymer d) + (~> d first string->list (sliding 2 1))) + +(define (first-char d) + (first (first (starting-polymer d)))) + +(define (starting-counts d) + (~> d first frequencies hash->list make-hash)) + +(define (starting-pairs d) + (~>> d (drop _ 2) (map (λ~> (substring 0 2) string->list)))) + +(define (new-pairs d) + (~>> d + (drop _ 2) + (map (λ~> (string-replace " -> " "") + string->list + ((match-lambda + [(list a b c) (list a c b)]) + _) + (sliding 2 1))))) + +(define (transform d) + (~>> (map list (starting-pairs d) (new-pairs d)) (append*) (apply hash))) + +(define transformation (transform data)) + +(define/memo (get-count polymer times) + (match times + [0 + (for/fold ([counts (hash)]) ([pair (in-list polymer)]) + (hash-update counts (second pair) add1 0))] + [_ + (for*/fold ([counts (hash)]) + ([pair (in-list polymer)] + [(c n) (in-hash (get-count (hash-ref transformation pair) (sub1 times)))]) + (hash-update counts c (λ~> (+ n)) 0))])) + +;; part 1 +(define (process-polymer d n) + (~> d + starting-polymer + (get-count _ n) + (hash-update _ (first-char d) add1 0) + hash-values + (sort >) + ((λ (l) (- (first l) (last l)))))) + +(process-polymer data 10) + +;; part 2 +(process-polymer data 40) diff --git a/racket/aoc2021/day-15/day-15-list-nodes.rkt b/racket/aoc2021/day-15/day-15-list-nodes.rkt new file mode 100644 index 0000000..38c558a --- /dev/null +++ b/racket/aoc2021/day-15/day-15-list-nodes.rkt @@ -0,0 +1,67 @@ +#lang racket +(require "../../jj-aoc.rkt" + threading + graph) + +(define data + (for/fold ([cells (hash)]) + ([row (in-lines (open-day 15 2021))] + [x (in-naturals)]) + (for/fold ([cells cells]) + ([n (in-string row)] + [y (in-naturals)]) + (hash-set cells + `(,x ,y) + (~> n string string->number))))) + +(define x-max (~>> data hash-keys (map first) (apply max))) +(define y-max (~>> data hash-keys (map second) (apply max))) + +(define (neighbors pt d) + (match-define (list x y) pt) + (~>> (list (list (add1 x) y) + (list (sub1 x) y) + (list x (add1 y)) + (list x (sub1 y))) + (filter (curry hash-has-key? d)))) + +(define (grid-graph d) + (weighted-graph/directed + (for*/list ([coord (in-list (hash-keys d))] + [neighbor (in-list (neighbors coord d))] + [weight (in-value (hash-ref d neighbor))]) + (list weight coord neighbor)))) + +;; part 1 +(define (find-path-weight d) + (define grid (grid-graph d)) + (let-values ([(node-distances _) (dijkstra grid '(0 0))]) + (define xm (~>> d hash-keys (map first) (apply max))) + (define ym (~>> d hash-keys (map second) (apply max))) + (hash-ref node-distances (list xm ym)))) + +(~> data + find-path-weight + time) + +;; part 2 +(define nine-cycle + (in-cycle (inclusive-range 1 9))) + +(define (expand-data data) + (for/fold ([cells (hash)]) + ([coord (in-list (hash-keys data))]) + (match-define (list x y) coord) + (for*/fold ([cells cells]) + ([n (in-range 5)] + [m (in-range 5)] + [val (in-value (hash-ref data coord))]) + (hash-set cells + (list (+ x (* n (add1 x-max))) + (+ y (* m (add1 y-max)))) + (sequence-ref nine-cycle (+ val n m -1)))))) + +(~> data + expand-data + find-path-weight + time)
\ No newline at end of file diff --git a/racket/aoc2021/day-15/day-15.livemd b/racket/aoc2021/day-15/day-15.livemd new file mode 100644 index 0000000..2495c32 --- /dev/null +++ b/racket/aoc2021/day-15/day-15.livemd @@ -0,0 +1,287 @@ +<!-- vim: set syntax=markdown: --> +<!-- livebook:{"persist_outputs":true} --> + +# Advent of Code 2021, Day 15 + +## Short summary + +**Parts 1 and 2.** Find the least "risky" path through a grid of nodes, +where each node has a "risk" cost to enter. Part 1's grid is the given data, +while Part 2's is the data after undergoing a transformation. +Only orthogonal travel is possible. + +## Setup + +Using the `libgraph` library to build the graph +and use its implementation of the Dijkstra algorithm: + +```elixir +Mix.install([ + {:libgraph, github: "bitwalker/libgraph"} +]) +``` + +```output +* Getting libgraph (https://github.com/bitwalker/libgraph.git) +remote: Enumerating objects: 783, done. +remote: Counting objects: 100% (64/64), done. +remote: Compressing objects: 100% (52/52), done. +remote: Total 783 (delta 27), reused 24 (delta 10), pack-reused 719 +origin/HEAD set to main +==> libgraph +Compiling 14 files (.ex) +Generated libgraph app +``` + +```output +:ok +``` + +```elixir +floor = + with {:ok, data} <- File.read("./2021/day-15/input") do + data + |> String.split() + |> Enum.map(fn xs -> + String.graphemes(xs) + |> Enum.map(&String.to_integer/1) + end) + end +``` + +```output +[ + [4, 5, 5, 2, 2, 8, 5, 9, 8, 9, 4, 4, 1, 1, 2, 4, 7, 1, 9, 7, 9, 8, 4, 6, 5, 8, 2, 5, 7, 7, 3, 3, + 1, 8, 2, 5, 2, 2, 6, 9, 4, 2, 2, 6, 2, 5, 7, 3, 1, ...], + [8, 3, 1, 1, 8, 2, 6, 9, 1, 7, 6, 7, 7, 2, 9, 5, 5, 6, 1, 3, 9, 5, 7, 1, 6, 2, 4, 5, 4, 2, 6, 1, + 8, 4, 2, 1, 2, 4, 7, 1, 3, 1, 1, 2, 1, 4, 8, 5, ...], + [3, 1, 3, 4, 1, 5, 5, 5, 2, 8, 2, 3, 1, 2, 9, 6, 3, 1, 3, 2, 9, 2, 9, 5, 1, 6, 8, 9, 3, 7, 1, 6, + 7, 1, 6, 1, 1, 3, 5, 5, 1, 9, 9, 4, 2, 9, 3, ...], + [7, 1, 3, 4, 1, 5, 9, 6, 8, 1, 5, 3, 3, 3, 3, 1, 1, 7, 2, 9, 9, 1, 9, 3, 2, 8, 1, 9, 1, 2, 8, 7, + 1, 8, 1, 3, 1, 1, 2, 1, 4, 2, 8, 7, 1, 5, ...], + [3, 2, 9, 1, 6, 2, 2, 1, 2, 1, 8, 9, 3, 8, 2, 1, 2, 5, 8, 2, 1, 5, 1, 1, 1, 5, 3, 1, 6, 1, 4, 2, + 3, 3, 9, 9, 9, 6, 6, 1, 6, 4, 9, 2, 7, ...], + [9, 1, 1, 6, 1, 9, 7, 3, 1, 9, 9, 9, 2, 2, 2, 3, 7, 8, 4, 9, 7, 3, 7, 4, 9, 1, 5, 9, 9, 1, 2, 9, + 1, 8, 9, 4, 8, 5, 1, 4, 8, 5, 5, 2, ...], + [4, 9, 6, 8, 4, 3, 9, 1, 1, 4, 1, 3, 5, 9, 9, 6, 9, 2, 3, 9, 3, 9, 6, 2, 1, 5, 2, 1, 2, 2, 7, 4, + 3, 8, 6, 1, 6, 4, 3, 2, 2, 6, 2, ...], + [6, 2, 3, 9, 2, 1, 9, 7, 8, 5, 2, 3, 4, 6, 7, 5, 3, 9, 1, 2, 8, 1, 6, 4, 1, 1, 1, 4, 1, 4, 8, 2, + 2, 5, 9, 4, 2, 6, 2, 2, 1, 9, ...], + [1, 9, 3, 9, 2, 7, 3, 1, 5, 2, 1, 1, 4, 1, 6, 5, 8, 1, 5, 6, 2, 1, 8, 9, 8, 1, 3, 1, 7, 4, 1, 6, + 2, 5, 1, 4, 2, 2, 7, 3, 7, ...], + [3, 8, 1, 6, 2, 9, 5, 1, 2, 1, 2, 1, 1, 7, 1, 3, 9, 1, 9, 9, 1, 8, 1, 2, 9, 1, 4, 1, 5, 2, 1, 7, + 4, 8, 3, 1, 5, 5, 7, 9, ...], + [2, 8, 5, 2, 1, 7, 4, 3, 9, 2, 1, 7, 5, 2, 1, 7, 8, 9, 1, 2, 9, 2, 7, 6, 9, 2, 9, 7, 3, 2, 9, 7, + 3, 4, 1, 4, 1, 5, 2, ...], + [8, 1, 8, 3, 4, 1, 1, 9, 8, 1, 1, 3, 6, 7, 1, 8, 4, 2, 8, 8, 2, 3, 1, 2, 7, 6, 8, 1, 4, 1, 1, 2, + 4, 6, 5, 2, 8, 2, ...], + [3, 3, 3, 8, 4, 1, 8, 9, 5, 8, 8, 1, 8, 2, 2, 9, 3, 3, 1, 3, 9, 9, 5, 1, 3, 6, 9, 1, 9, 2, 1, 9, + 2, 1, 8, 6, 9, ...], + [6, 1, 1, 8, 5, 8, 2, 7, 6, 5, 3, 7, 5, 5, 4, 1, 1, 4, 4, 4, 9, 1, 5, 1, 9, 8, 7, 1, 1, 2, 3, 1, + 2, 1, 1, 7, ...], + [1, 4, 6, 9, 2, 5, 7, 4, 1, 8, 3, 1, 3, 2, 1, 8, 9, 5, 1, 2, 1, 1, 5, 1, 1, 5, 9, 1, 2, 2, 4, 2, + 8, 1, 8, ...], + [1, 4, 2, 2, 1, 1, 8, 4, 1, 9, 3, 1, 9, 5, 1, 5, 4, 9, 6, 1, 8, 9, 3, 9, 1, 1, 5, 8, 5, 1, 6, 4, + 6, 2, ...], + [8, 1, 6, 2, 5, 4, 6, 1, 3, 9, 9, 5, 2, 1, 3, 6, 1, 8, 9, 9, 1, 1, 1, 7, 7, 5, 3, 3, 3, 5, 7, 8, + 2, ...], + [1, 4, 8, 8, 2, 3, 9, 6, 1, 1, 4, 6, 1, 4, 9, 1, 9, 7, 3, 8, 6, 5, 1, 8, 5, 2, 4, 2, 9, 5, 9, 5, + ...], + [2, 2, 1, 8, 9, 1, 7, 6, 6, 1, 8, 6, 8, 1, 3, 3, 1, 4, 1, 2, 4, 3, 8, 1, 9, 9, 6, 2, 4, 1, 3, ...], + [5, 7, 3, 4, 3, 2, 6, 7, 3, 2, 8, 8, 4, 7, 9, 2, 4, 2, 8, 1, 9, 1, 2, 4, 1, 1, 2, 1, 1, 9, ...], + [1, 5, 1, 1, 9, 3, 5, 1, 1, 1, 8, 6, 1, 1, 2, 1, 9, 3, 5, 7, 4, 1, 7, 6, 8, 3, 3, 8, 9, ...], + [9, 6, 9, 1, 5, 6, 2, 1, 8, 3, 5, 1, 3, 3, 8, 8, 2, 2, 1, 7, 2, 6, 4, 9, 3, 8, 2, 1, ...], + [2, 7, 2, 9, 2, 3, 7, 5, 3, 2, 9, 5, 9, 4, 4, 4, 7, 3, 3, 2, 2, 5, 4, 3, 5, 9, 9, ...], + [7, 3, 1, 2, 5, 5, 9, 1, 8, 1, 3, 2, 3, 7, 4, 3, 9, 3, 8, 2, 2, 1, 2, 1, 2, 2, ...], + [9, 4, 1, 5, 7, 4, 7, 7, 3, 8, 3, 4, 6, 1, 4, 1, 6, 9, 1, 4, 1, 8, 8, 2, 5, ...], + [1, 7, 1, 6, 2, 6, 3, 1, 2, 1, 1, 1, 5, 6, 3, 1, 1, 4, 1, 7, 8, 4, 2, 5, ...], + [2, 1, 1, 3, 4, 1, 4, 9, 1, 1, 7, 3, 1, 1, 6, 4, 9, 6, 2, 3, 4, 2, 2, ...], + [5, 6, 1, 1, 9, 1, 2, 3, 3, 4, 9, 2, 7, 2, 2, 1, 2, 5, 2, 7, 1, 1, ...], + [1, 2, 3, 2, 3, 1, 3, 2, 7, 1, 1, 1, 3, 5, 5, 4, 1, 8, 9, 9, 5, ...], + [2, 9, 2, 8, 1, 2, 3, 1, 2, 1, 1, 9, 1, 2, 6, 7, 7, 2, 1, 3, ...], + [3, 7, 3, 8, 1, 5, 2, 5, 4, 4, 6, 2, 5, 5, 1, 6, 9, 1, 6, ...], + [4, 9, 7, 8, 9, 3, 2, 2, 3, 7, 3, 1, 2, 6, 1, 5, 4, 5, ...], + [3, 9, 1, 2, 8, 9, 9, 1, 8, 2, 7, 9, 8, 9, 1, 6, 6, ...], + [1, 1, 9, 2, 9, 2, 7, 4, 2, 1, 3, 7, 2, 5, 8, 4, ...], + [3, 9, 1, 1, 1, 8, 5, 2, 1, 5, 5, 8, 2, 4, 3, ...], + [2, 9, 5, 3, 2, 1, 6, 7, 2, 2, 8, 1, 5, 2, ...], + [1, 1, 9, 8, 3, 8, 2, 7, 6, 7, 2, 1, 8, ...], + [2, 7, 2, 1, 8, 8, 4, 4, 1, 2, 9, 4, ...], + [3, 1, 1, 5, 1, 1, 8, 2, 2, 6, 2, ...], + [1, 4, 6, 6, 1, 3, 8, 1, 5, 2, ...], + [9, 1, 2, 2, 1, 3, 4, 4, 5, ...], + [6, 6, 1, 9, 4, 1, 3, 2, ...], + [1, 5, 9, 1, 8, 4, 5, ...], + [9, 1, 4, 5, 6, 7, ...], + [1, 7, 5, 6, 3, ...], + [7, 1, 1, 5, ...], + [1, 5, 9, ...], + [1, 5, ...], + [2, ...], + [...], + ... +] +``` + +Give each node a label based on its coordinates: + +```elixir +floor_nodes = + for {row, i} <- Enum.with_index(floor), + {col, j} <- Enum.with_index(row), + into: %{} do + {{i, j}, col} + end +``` + +```output +%{ + {76, 13} => 1, + {37, 47} => 2, + {65, 63} => 5, + {38, 2} => 1, + {1, 26} => 4, + {83, 76} => 2, + {32, 15} => 6, + {89, 14} => 1, + {35, 30} => 7, + {37, 53} => 7, + {4, 5} => 2, + {8, 50} => 7, + {78, 98} => 7, + {95, 56} => 7, + {74, 12} => 9, + {11, 39} => 2, + {65, 43} => 4, + {22, 38} => 1, + {14, 86} => 4, + {20, 41} => 1, + {29, 25} => 1, + {86, 10} => 1, + {83, 36} => 3, + {29, 26} => 3, + {47, 27} => 9, + {4, 81} => 3, + {31, 42} => 1, + {9, 34} => 3, + {90, 0} => 3, + {67, 98} => 1, + {13, 85} => 1, + {63, 81} => 4, + {82, 60} => 4, + {47, 38} => 1, + {15, 92} => 1, + {58, 58} => 1, + {20, 3} => 1, + {61, 95} => 7, + {23, 67} => 4, + {78, 75} => 1, + {79, 17} => 2, + {75, 0} => 7, + {16, 73} => 2, + {76, 2} => 8, + {58, 84} => 1, + {58, 33} => 7, + {47, 44} => 2, + {54, 31} => 6, + {13, ...} => 1, + {...} => 9, + ... +} +``` + +We can travel in the four cardinal directions from each node, so we need +a function to identify a node's neighbors: + +```elixir +neighbors = fn {i, j}, nodes -> + [{i + 1, j}, {i - 1, j}, {i, j + 1}, {i, j - 1}] + |> Enum.filter(&Map.has_key?(nodes, &1)) +end + +[neighbors.({0, 0}, floor_nodes), neighbors.({50, 50}, floor_nodes)] +``` + +```output +[[{1, 0}, {0, 1}], [{51, 50}, {49, 50}, {50, 51}, {50, 49}]] +``` + +## Part 1 + +Now we fold all the edges into a `Graph`: + +```elixir +make_graph = fn nodes -> + for {coord, _} <- nodes, + neighbor <- neighbors.(coord, nodes), + risk = Map.get(nodes, neighbor), + reduce: Graph.new(vertex_identifier: fn v -> v end) do + acc -> Graph.add_edge(acc, coord, neighbor, weight: risk) + end +end + +floor_graph = make_graph.(floor_nodes) +``` + +```output +#Graph<type: directed, num_vertices: 10000, num_edges: 39600> +``` + +Now we just need to use Dijkstra's algorithm to find the best path from the upper left to the lower right, +and use the map of each node's risk value to sum up the total risk for the path. + +```elixir +get_lowest_risk_path = fn nodes -> + with {{min_c, _}, {max_c, _}} <- Enum.min_max_by(nodes, fn {{i, j}, _} -> i + j end) do + nodes + |> then(make_graph) + |> Graph.dijkstra(min_c, max_c) + |> tl() + |> Enum.reduce(0, fn coord, sum -> sum + Map.get(nodes, coord) end) + end +end + +get_lowest_risk_path.(floor_nodes) +``` + +```output +403 +``` + +## Part 2 + +The process for Part 2 will be similar; the only difference +is that the graph is five times bigger after the transform. + +If the transformed risk is greater than 9, it rolls over to 1, so a +`Stream.cycle` can be used to easily get the rolled-over values. + +```elixir +expanded_floor_nodes = + with {{max_i, max_j}, _} <- Enum.max_by(floor_nodes, fn {{i, j}, _} -> i + j end), + nine_cycle = Stream.cycle(1..9) do + for {{i, j}, risk} <- floor_nodes, + x <- 0..4, + y <- 0..4, + into: %{} do + {{i + x * (max_i + 1), j + y * (max_j + 1)}, Enum.at(nine_cycle, risk - 1 + x + y)} + end + end + +Enum.count(expanded_floor_nodes) +``` + +```output +250000 +``` + +We repeat the same steps as before: building the graph, +using Dijkstra's algorithm and summing up the risks along the best path. + +```elixir +get_lowest_risk_path.(expanded_floor_nodes) +``` + +```output +2840 +``` diff --git a/racket/aoc2021/day-15/day-15.rkt b/racket/aoc2021/day-15/day-15.rkt new file mode 100644 index 0000000..6ab67b1 --- /dev/null +++ b/racket/aoc2021/day-15/day-15.rkt @@ -0,0 +1,50 @@ +#lang racket +(require advent-of-code + threading + graph) + +(struct Point (x y) #:transparent) + +(define data + (for/fold ([cells (hash)]) + ([row (in-lines (open-aoc-input (find-session) 2021 15 #:cache #true))] [x (in-naturals)]) + (for/fold ([cells cells]) ([n (in-string row)] [y (in-naturals)]) + (hash-set cells (Point x y) (~> n string string->number))))) + +(define x-max (~>> data hash-keys (map Point-x) (apply max))) +(define y-max (~>> data hash-keys (map Point-y) (apply max))) + +(define (neighbors pt d) + (match-define (Point x y) pt) + (~>> (list (Point (add1 x) y) (Point (sub1 x) y) (Point x (add1 y)) (Point x (sub1 y))) + (filter (curry hash-has-key? d)))) + +(define (grid-graph d) + (weighted-graph/directed (for*/list ([coord (in-list (hash-keys d))] + [neighbor (in-list (neighbors coord d))] + [weight (in-value (hash-ref d neighbor))]) + (list weight coord neighbor)))) + +;; part 1 +(define (find-path-weight d) + (define grid (grid-graph d)) + (let-values ([(node-distances _) (dijkstra grid (Point 0 0))]) + (define xm (~>> d hash-keys (map Point-x) (apply max))) + (define ym (~>> d hash-keys (map Point-y) (apply max))) + (hash-ref node-distances (Point xm ym)))) + +(~> data find-path-weight time) + +;; part 2 +(define nine-cycle (in-cycle (inclusive-range 1 9))) + +(define (expand-data data) + (for/fold ([cells (hash)]) ([coord (in-list (hash-keys data))]) + (match-define (Point x y) coord) + (for*/fold ([cells cells]) + ([n (in-range 5)] [m (in-range 5)] [val (in-value (hash-ref data coord))]) + (hash-set cells + (Point (+ x (* n (add1 x-max))) (+ y (* m (add1 y-max)))) + (sequence-ref nine-cycle (+ val n m -1)))))) + +(~> data expand-data find-path-weight time) diff --git a/racket/aoc2021/day-16/day-16.rkt b/racket/aoc2021/day-16/day-16.rkt new file mode 100644 index 0000000..86083ef --- /dev/null +++ b/racket/aoc2021/day-16/day-16.rkt @@ -0,0 +1,97 @@ +#lang racket +(require "../../jj-aoc.rkt" + bitsyntax + threading) + +(struct packet (version type type-id contents len) #:transparent) + +(define (BITS->bitstring str) + (integer->bit-string (string->number str 16) (* 4 (string-length str)) #true)) + +(define data (~> (open-day 16 2021) port->string string-trim BITS->bitstring)) + +(define (get-literal-contents bitstr) + (for/fold ([assembled (bit-string)] + [remaining bitstr] + [total-length 6] + [complete? #f] + #:result (values (bit-string->integer assembled #t #f) remaining total-length)) + ([_ (in-naturals)] #:break complete?) + (bit-string-case remaining + ([(= 1 :: bits 1) (number :: bits 4) (remaining :: binary)] + (values (bit-string-append assembled (integer->bit-string number 4 #t)) + remaining + (+ total-length 5) + #f)) + ([(= 0 :: bits 1) (number :: bits 4) (remaining :: binary)] + (values (bit-string-append assembled (integer->bit-string number 4 #t)) + remaining + (+ total-length 5) + #t))))) + +(define (get-type-0-contents cnt bitstr [acc '()] [len 0]) + (cond + [(<= cnt 0) (values (reverse acc) bitstr len)] + [else + (define-values (packet remaining) (identify-next-packet bitstr)) + (get-type-0-contents (- cnt (packet-len packet)) + remaining + (cons packet acc) + (+ len (packet-len packet)))])) + +(define (get-type-1-contents cnt bitstr [acc '()] [len 0]) + (cond + [(= cnt 0) (values (reverse acc) bitstr len)] + [else + (define-values (packet remaining) (identify-next-packet bitstr)) + (get-type-1-contents (sub1 cnt) remaining (cons packet acc) (+ len (packet-len packet)))])) + +(define (identify-next-packet bitstr) + (bit-string-case + bitstr + ([(packet-version :: bits 3) (= 4 :: bits 3) (remaining :: binary)] + (define-values (n now-remaining len) (get-literal-contents remaining)) + (values (packet packet-version 'literal 4 n len) now-remaining)) + ([(packet-version :: bits 3) + (type-id :: bits 3) + (= 0 :: bits 1) + (subpacket-length :: bits 15) + (remaining :: binary)] + (define-values (contents now-remaining sublength) + (get-type-0-contents subpacket-length remaining)) + (values (packet packet-version 'operator type-id contents (+ 22 sublength)) now-remaining)) + ([(packet-version :: bits 3) + (type-id :: bits 3) + (= 1 :: bits 1) + (subpacket-count :: bits 11) + (remaining :: binary)] + (define-values (contents now-remaining sublength) (get-type-1-contents subpacket-count remaining)) + (values (packet packet-version 'operator type-id contents (+ 22 sublength)) now-remaining)))) + +(match-define-values (outer-packet n) (identify-next-packet data)) + +;; part 1 +(define (packet-sum-version p) + (match p + [(packet v 'literal _type-id _contents _len) v] + [(packet v 'operator _type-id ps _len) (foldl (λ (p acc) (+ acc (packet-sum-version p))) v ps)])) + +(packet-sum-version outer-packet) + +;; part 2 +(define packet-f + (match-lambda + [0 +] + [1 *] + [2 min] + [3 max] + [5 (λ (a b) (if (> a b) 1 0))] + [6 (λ (a b) (if (< a b) 1 0))] + [7 (λ (a b) (if (= a b) 1 0))])) + +(define packet-eval + (match-lambda + [(packet _v 'literal _type-id n _len) n] + [(packet _v 'operator type-id ps _len) (~>> ps (map packet-eval) (apply (packet-f type-id)))])) + +(packet-eval outer-packet) diff --git a/racket/aoc2021/day-17/day-17.rkt b/racket/aoc2021/day-17/day-17.rkt new file mode 100644 index 0000000..7de44a0 --- /dev/null +++ b/racket/aoc2021/day-17/day-17.rkt @@ -0,0 +1,43 @@ +#lang racket +(require "../../jj-aoc.rkt" + threading) + +(define-values (x-min x-max y-min y-max) + (~> (open-day 17 2021) + port->string + (regexp-match #px"target area: x=(.*)\\.\\.(.*), y=(.*)\\.\\.(.*)\n" _) + rest + (map string->number _) + (apply values _))) + +(define (hit? x y) + (and (x . >= . x-min) (x . <= . x-max) (y . >= . y-min) (y . <= . y-max))) + +(define (miss? x y) + (or (y . < . y-min) (x . > . x-max))) + +(define (drag dx i) + (max (- dx i) 0)) +(define (gravity dy i) + (- dy i)) + +(define (find-trajectory-apex dx dy) + (for/fold ([x 0] [y 0] [y-apex 0] [result #f] #:result (list y-apex result)) + ([i (in-naturals)] #:break result) + (cond + [(hit? x y) (values dx dy y-apex 'hit)] + [(miss? x y) (values x y 'miss 'miss)] + [else (values (+ x (drag dx i)) (+ y (gravity dy i)) (if (y . > . y-apex) y y-apex) #f)]))) + +(define on-target + (for*/list ([dx (in-inclusive-range 1 x-max)] + [dy (in-inclusive-range y-min (abs (* 2 y-max)))] + [velocity (in-value (find-trajectory-apex dx dy))] + #:when (equal? (second velocity) 'hit)) + (list dx dy (first velocity)))) + +;; part 1 +(~>> on-target (argmax third) third) + +;; part 2 +(length on-target) diff --git a/racket/aoc2021/day-18/day-18.rkt b/racket/aoc2021/day-18/day-18.rkt new file mode 100644 index 0000000..45016b1 --- /dev/null +++ b/racket/aoc2021/day-18/day-18.rkt @@ -0,0 +1,5 @@ +#lang racket +(require "../../jj-aoc.rkt" + threading) + +;; tbd
\ No newline at end of file diff --git a/racket/aoc2021/day-19/day-19.rkt b/racket/aoc2021/day-19/day-19.rkt new file mode 100644 index 0000000..4c6334d --- /dev/null +++ b/racket/aoc2021/day-19/day-19.rkt @@ -0,0 +1,48 @@ +#lang racket +(require "../../jj-aoc.rkt" + threading + racket/struct) + +(struct coord (x y z) #:transparent) + +(define (coord-broadcast f c1 c2) + (match-define (coord x1 y1 z1) c1) + (match-define (coord x2 y2 z2) c2) + (coord (f x1 x2) (f y1 y2) (f z1 z2))) + +(define (coord-reduce f c1 c2) + (foldl (λ (i1 i2 acc) (+ acc (f i1 i2))) 0 (struct->list c1) (struct->list c2))) + +(define coord-delta (curry coord-broadcast -)) +(define coord-sum (curry coord-broadcast +)) +(define coord-manhattan (curry coord-reduce (coord 0 0 0))) + +(define (create-scan-data d) + (for/list ([l (in-list d)]) + (for/list ([pt (in-list (~> (string-split l "\n") rest))]) + (~>> pt + (regexp-match #px"(-?\\d+),(-?\\d+),(-?\\d+)") + rest + (map string->number) + (apply coord))))) + +(define scanner-data (create-scan-data (~>> (open-day 19 2021) port->string (string-split _ "\n\n")))) + +(define (generate-rotations scanner) + (apply + map + list + (for*/list ([pt (in-list scanner)]) + (match-define (coord x y z) pt) + (define orientations (list (list x y z) (list x z (- y)) (list x (- y) (- z)) (list x (- z) y))) + (append* (for/list ([o (in-list orientations)]) + (match-define (list x* y* z*) o) + (list (list x y z) + (list (- x) z y) + (list z (- y) x) + (list y x (- z)) + (list (- y) (- z) x))))))) + +(define (find-overlaps scan1 scan2) + (for/list ([rotation (in-permutations scan2)]) + (map coord-sum scan1 rotation))) diff --git a/racket/aoc2021/day-19/test-scanners b/racket/aoc2021/day-19/test-scanners new file mode 100644 index 0000000..b596cc4 --- /dev/null +++ b/racket/aoc2021/day-19/test-scanners @@ -0,0 +1,136 @@ +--- scanner 0 --- +404,-588,-901 +528,-643,409 +-838,591,734 +390,-675,-793 +-537,-823,-458 +-485,-357,347 +-345,-311,381 +-661,-816,-575 +-876,649,763 +-618,-824,-621 +553,345,-567 +474,580,667 +-447,-329,318 +-584,868,-557 +544,-627,-890 +564,392,-477 +455,729,728 +-892,524,684 +-689,845,-530 +423,-701,434 +7,-33,-71 +630,319,-379 +443,580,662 +-789,900,-551 +459,-707,401 + +--- scanner 1 --- +686,422,578 +605,423,415 +515,917,-361 +-336,658,858 +95,138,22 +-476,619,847 +-340,-569,-846 +567,-361,727 +-460,603,-452 +669,-402,600 +729,430,532 +-500,-761,534 +-322,571,750 +-466,-666,-811 +-429,-592,574 +-355,545,-477 +703,-491,-529 +-328,-685,520 +413,935,-424 +-391,539,-444 +586,-435,557 +-364,-763,-893 +807,-499,-711 +755,-354,-619 +553,889,-390 + +--- scanner 2 --- +649,640,665 +682,-795,504 +-784,533,-524 +-644,584,-595 +-588,-843,648 +-30,6,44 +-674,560,763 +500,723,-460 +609,671,-379 +-555,-800,653 +-675,-892,-343 +697,-426,-610 +578,704,681 +493,664,-388 +-671,-858,530 +-667,343,800 +571,-461,-707 +-138,-166,112 +-889,563,-600 +646,-828,498 +640,759,510 +-630,509,768 +-681,-892,-333 +673,-379,-804 +-742,-814,-386 +577,-820,562 + +--- scanner 3 --- +-589,542,597 +605,-692,669 +-500,565,-823 +-660,373,557 +-458,-679,-417 +-488,449,543 +-626,468,-788 +338,-750,-386 +528,-832,-391 +562,-778,733 +-938,-730,414 +543,643,-506 +-524,371,-870 +407,773,750 +-104,29,83 +378,-903,-323 +-778,-728,485 +426,699,580 +-438,-605,-362 +-469,-447,-387 +509,732,623 +647,635,-688 +-868,-804,481 +614,-800,639 +595,780,-596 + +--- scanner 4 --- +727,592,562 +-293,-554,779 +441,611,-461 +-714,465,-776 +-743,427,-804 +-660,-479,-426 +832,-632,460 +927,-485,-438 +408,393,-506 +466,436,-512 +110,16,151 +-258,-428,682 +-393,719,612 +-211,-452,876 +808,-476,-593 +-575,615,604 +-485,667,467 +-680,325,-822 +-627,-443,-432 +872,-547,-609 +833,512,582 +807,604,487 +839,-516,451 +891,-625,532 +-652,-548,-490 +30,-46,-14
\ No newline at end of file diff --git a/racket/aoc2021/day-20/day-20.rkt b/racket/aoc2021/day-20/day-20.rkt new file mode 100644 index 0000000..b7ed092 --- /dev/null +++ b/racket/aoc2021/day-20/day-20.rkt @@ -0,0 +1,59 @@ +#lang racket +(require "../../jj-aoc.rkt" + threading) + +(struct pixel (i j) #:transparent) + +(define-values (raw-enhancement raw-image) + (~> (open-day 20 2021) port->string (string-split "\n\n") (apply values _))) + +(define (char->pixel c) + (if (char=? #\# c) 'light 'dark)) +(define (pixel->char c) + (if (equal? 'light c) #\# #\.)) + +(define enhancement-algorithm + (for/hash ([(c i) (in-indexed (~> raw-enhancement (string-replace "\n" "")))]) + (values i (char->pixel c)))) + +(define image-hash + (for*/hash ([(row i) (in-indexed (string-split raw-image "\n"))] [(c j) (in-indexed row)]) + (values (pixel i j) (char->pixel c)))) + +(define (window->new-pixel p ps bg) + (match-define (pixel i j) p) + (~> (for*/list ([di '(-1 0 1)] [dj '(-1 0 1)]) + (if (equal? 'dark (hash-ref ps (pixel (+ i di) (+ j dj)) bg)) #\0 #\1)) + (apply string _) + (string->number _ 2) + (hash-ref enhancement-algorithm _))) + +(define (pad-hash ps bg) + (define coords (hash-keys ps)) + (define i-min (~>> coords (map pixel-i) (apply min))) + (define i-max (~>> coords (map pixel-i) (apply max))) + (define j-min (~>> coords (map pixel-j) (apply min))) + (define j-max (~>> coords (map pixel-j) (apply max))) + (for*/hash ([i (in-inclusive-range (- i-min 2) (+ i-max 2))] + [j (in-inclusive-range (- j-min 2) (+ j-max 2))]) + (values (pixel i j) (hash-ref ps (pixel i j) bg)))) + +(define (enhance-image ps bg) + (for/hash ([(p _) (in-hash (pad-hash ps bg))]) + (values p (window->new-pixel p ps bg)))) + +;; part 1 +;; looking at the enhancement algorithm, since a window of 0 -> light and 512 -> dark, +;; the infinite background flips colors every other enhancement step +;; instead of trying to account for this algorithmically I just hardcoded it in +(~> image-hash + (enhance-image 'dark) + (enhance-image 'light) + hash-values + (count (curry equal? 'light) _)) + +;; part 2 +(~> (for/fold ([img image-hash]) ([_ (in-range 25)]) + (~> img (enhance-image 'dark) (enhance-image 'light))) + hash-values + (count (curry equal? 'light) _)) diff --git a/racket/aoc2021/day-21/day-21.rkt b/racket/aoc2021/day-21/day-21.rkt new file mode 100644 index 0000000..9ca9b1b --- /dev/null +++ b/racket/aoc2021/day-21/day-21.rkt @@ -0,0 +1,59 @@ +#lang racket +(require threading + memoize) + +;; not going to bother importing the data since it's just two lines of text +(define player-1-start 4) +(define player-2-start 6) + +(define track (sequence->stream (in-cycle (inclusive-range 1 10)))) +(define current-turn (in-cycle (list 'player-1 'player-2))) +(define die-rolls (sequence->stream (in-cycle (inclusive-range 1 100)))) + +;; part 1 +(~> (for/fold ([player-1-score 0] + [player-1-track (stream-tail track (sub1 player-1-start))] + [player-2-score 0] + [player-2-track (stream-tail track (sub1 player-2-start))] + [dice die-rolls] + [last-turn 0] + #:result (list (min player-1-score player-2-score) (* 3 last-turn))) + ([turn-count (in-naturals 1)] + [turn-for current-turn] + #:break (or (player-1-score . >= . 1000) (player-2-score . >= . 1000))) + (define rolls (apply + (stream->list (stream-take dice 3)))) + (match turn-for + ['player-1 + (define next-track (stream-tail player-1-track rolls)) + (values (+ player-1-score (stream-first next-track)) + next-track + player-2-score + player-2-track + (stream-tail dice 3) + turn-count)] + ['player-2 + (define next-track (stream-tail player-2-track rolls)) + (values player-1-score + player-1-track + (+ player-2-score (stream-first next-track)) + next-track + (stream-tail dice 3) + turn-count)])) + (apply * _)) + +;; part 2 +(define d3 (list 1 2 3)) +(define roll-space (~>> (cartesian-product d3 d3 d3) (map (λ~>> (apply +))))) + +(define/memo + (next-turns p1-score p2-score p1-start p2-start) + (cond + [(p1-score . >= . 21) '(1 0)] + [(p2-score . >= . 21) '(0 1)] + [else + (for/fold ([wins '(0 0)]) ([roll (in-list roll-space)]) + (define move-to (add1 (modulo (sub1 (+ roll p1-start)) 10))) + (define possible-wins (next-turns p2-score (+ p1-score move-to) p2-start move-to)) + (list (+ (first wins) (second possible-wins)) (+ (second wins) (first possible-wins))))])) + +(~>> (next-turns 0 0 player-1-start player-2-start) (apply max)) diff --git a/racket/aoc2021/day-22/day-22.rkt b/racket/aoc2021/day-22/day-22.rkt new file mode 100644 index 0000000..1dc4211 --- /dev/null +++ b/racket/aoc2021/day-22/day-22.rkt @@ -0,0 +1,32 @@ +#lang racket +(require "../../jj-aoc.rkt" + threading) + +(struct step (instruction xs ys zs) #:transparent) + +;; part 1 +(define (clamped-range nmin nmax) + (in-inclusive-range (max (string->number nmin) -50) + (min (string->number nmax) 50))) + +(define steps + (for/list ([l (in-list (~> (open-day 22 2021) (port->lines)))]) + (~>> l + (regexp-match #px"(.+) x=(-?\\d+)..(-?\\d+),y=(-?\\d+)..(-?\\d+),z=(-?\\d+)..(-?\\d+)") + rest + (match _ [(list direction xmin xmax ymin ymax zmin zmax) + (step (string->symbol direction) + (clamped-range xmin xmax) + (clamped-range ymin ymax) + (clamped-range zmin zmax))])))) + +(~> (for*/fold ([cubes (hash)]) + ([s (in-list steps)] + [to (in-value (step-instruction s))] + [x (step-xs s)] + [y (step-ys s)] + [z (step-zs s)]) + (hash-set cubes (list x y z) to)) + hash-values + (count (curry equal? 'on) _)) + diff --git a/racket/aoc2021/day-25/day-25.rkt b/racket/aoc2021/day-25/day-25.rkt new file mode 100644 index 0000000..7a3a5ca --- /dev/null +++ b/racket/aoc2021/day-25/day-25.rkt @@ -0,0 +1,35 @@ +#lang racket +(require "../../jj-aoc.rkt" + threading) + +(define sea-floor + (for*/hash ([(row i) (in-indexed (in-lines (open-day 25 2021)))] [(c j) (in-indexed row)]) + (values (list i j) c))) + +(define-values (max-i max-j) + (~> sea-floor hash-keys (argmax (λ (coord) (apply + coord)) _) (apply values _))) + +(define (cucumber-movement h c delta-i delta-j) + (define new-hash (hash-copy h)) + (for* ([(coord x) (in-hash h)] #:when (eq? x c)) + (match-define (list i j) coord) + (define neighbor (list (+ delta-i i) (+ delta-j j))) + (define neighbor-or-wrap + (if (hash-has-key? h neighbor) neighbor (list (if (= delta-i 0) i 0) (if (= delta-j 0) j 0)))) + (when (eq? #\. (hash-ref h neighbor-or-wrap)) + (hash-set*! new-hash coord #\. neighbor-or-wrap c))) + new-hash) + +(define (eastwards-movement h) + (cucumber-movement h #\> 0 1)) + +(define (southwards-movement h) + (cucumber-movement h #\v 1 0)) + +;; part 1 +(for/fold ([f sea-floor] [step 0] #:result (add1 step)) ([i (in-naturals 1)]) + (define f* (~> f eastwards-movement southwards-movement)) + #:break (equal? f* f) + (values f* i)) + +;; no part 2 -- merry Christmas diff --git a/racket/aoc2022/commentary.md b/racket/aoc2022/commentary.md new file mode 100644 index 0000000..8616464 --- /dev/null +++ b/racket/aoc2022/commentary.md @@ -0,0 +1,35 @@ +# Reflections on Advent of Code 2022 from a guy who kinda knows Racket + +I've gotten into the habit of doing [Advent of Code](https://adventofcode.com/), the annual puzzle-a-day programming challenge. There's a competitive aspect to it, but I'm mostly interested in it as a way to challenge myself and get exposed to algorithms and concepts I haven't seen before. I'm not a programmer by trade; I learned the basics of C in college and a little bit of numerical methods in grad school, but most of what I've learned since then is self-taught as a hobby, so structured programming challenges like this are a good way for me to gauge my skill level and see how CS theory is put into practice. + +The language I'm most comfortable with is [Racket](https://racket-lang.org/), a general-purpose high-level language in the Scheme family, so it's what I used this year for Advent of Code. In my experience, Racket is pretty well-suited for the kinds of problems you find in programming challenges; there hasn't been any problem yet where I've felt like trying to solve it in Racket has been a handicap. For the first few days I used [Jupyter Notebook with the IRacket kernel](https://docs.racket-lang.org/iracket/index.html) to annotate my answers, but I eventually gave up on that because it ended up being harder to debug. + +So, here's my day-by-day opinion on how this year went for me. + +* **[Day 1](https://github.com/hunkyjimpjorps/AdventOfCode/blob/main/aoc2022/day-01/day-01.rkt)**. *Parse a list of lists and return the top three sums.* This is the kind of problem Racket feels made for -- the code is basically a direct declarative translation of the problem statement. +* **[Day 2](https://github.com/hunkyjimpjorps/AdventOfCode/blob/main/aoc2022/day-02/day-02.ipynb)**. *Rock-paper-scissors strategy.* Lots of pattern-matching; the hardest part was probably figuring out the rules of the tournament. +* **[Day 3](https://github.com/hunkyjimpjorps/AdventOfCode/blob/main/aoc2022/day-03/day-03.ipynb)**. *Find common items in rucksacks.* Another problem well-suited to declarative style. +* **[Day 4](https://github.com/hunkyjimpjorps/AdventOfCode/blob/main/aoc2022/day-04/day-04.ipynb)**. *Find overlapping and redundant ranges.* I used the [`rebellion`](https://docs.racket-lang.org/rebellion/index.html) library here for its [range](https://docs.racket-lang.org/rebellion/Ranges.html) data type, which takes care of most of the problem by itself. AoC is a good opportunity to explore a language's ecosystem. +* **[Day 5](https://github.com/hunkyjimpjorps/AdventOfCode/blob/main/aoc2022/day-05/day-05.ipynb)**. *Move stacks of boxes around.* Linked lists make first-in, last-out stack problems like this pretty easy. The major difficulty here was actually parsing the input (and it was probably the hardest input to parse out of all the problems), but once that was resolved it was smooth sailing. +* **[Day 6](https://github.com/hunkyjimpjorps/AdventOfCode/blob/main/aoc2022/day-06/day-06.rkt)**. *Find chunks of unique characters in a string.* Another one that Racket handles without difficulty -- the entire problem statement can be represented as one `for` comprehension. +* **[Day 7](https://github.com/hunkyjimpjorps/AdventOfCode/blob/main/aoc2022/day-07/day-07.rkt)**. *Parse terminal output and calculate directory sizes.* The first speedbump of the year, and probably the first day where there's a rift between "do it right" and "just get the answer". I saw a lot of solutions that properly built the file hierarchy and walked the tree to calculate directory sizes, while I just made a hashtable of absolute paths and the sizes of everything within them. +* **[Day 8](https://github.com/hunkyjimpjorps/AdventOfCode/blob/main/aoc2022/day-08/day-08.rkt)**. *Scout a forest for the treehouse site with the best visibility.* A traditional AoC "search a grid of integers for something" problem, and the first one of the year where I represented a grid as a hashtable of coordinates. Racket doesn't have great multidimensional data structures built in, but rolling my own sparse matrix representation is pretty painless. +* **[Day 9](https://github.com/hunkyjimpjorps/AdventOfCode/blob/main/aoc2022/day-09/day-09.rkt)**. *Simulate a rope.* Simulation problems are my favorite kind of AoC problem (probably because they're the most similar to the kind of stuff I did in school). The solution I wrote for a one-segment rope in part 1 just needed to be folded over a list to give me a solution for the ten-segment rope in part 2. Higher-order functions are nice. +* **[Day 10](https://github.com/hunkyjimpjorps/AdventOfCode/blob/main/aoc2022/day-10/day-10.rkt)**. *The traditional fake-ASM parsing problem*, and another problem that's just largely just folding a function over an accumulator. I feel like if you get comfortable with `for/fold` you can get about 25 AoC stars right off the bat. +* **[Day 11](https://github.com/hunkyjimpjorps/AdventOfCode/blob/main/aoc2022/day-11/day-11.rkt)**. *Monkeys take your stuff.* This problem features my least favorite AoC trope, which is a Part 2 that requires you to know a particular fact about modular arithmetic to solve it in a reasonable amount of time. It also had a data file I found less annoying to retype by hand than to parse. Probably the worst one of the set. +* **[Day 12](https://github.com/hunkyjimpjorps/AdventOfCode/blob/main/aoc2022/day-12/day-12.rkt)**. *Find a hiking route up a spiral mountain.* I leaned pretty hard on the [`graph`](https://docs.racket-lang.org/graph/index.html) package for this one. Eventually I'd like to learn how to implement Dijkstra and A* and other graph traversal algorithms myself, but for now I'm satisfied with just figuring out how to use the prepackaged versions. +* **[Day 13](https://github.com/hunkyjimpjorps/AdventOfCode/blob/main/aoc2022/day-13/day-13.rkt)**. *Sort a recursive data type.* I'm really glad for two features of Racket for this one. First, the ability to read data as code, which meant I could read the nested lists directly with just one tweak to the readtable and without doing any string parsing at all. Second, structural pattern matching. Racket's matching syntax isn't as elegant as some languages (I miss the `[x | xs]` form from Elixir compared to `(list* x xs)`, for example), but it's powerful. +* **[Day 14](https://github.com/hunkyjimpjorps/AdventOfCode/blob/main/aoc2022/day-14/day-14.rkt)**. *Simulate falling sand.* A fun simulation problem, and one that both has a naive solution that solves in a reasonable amount of time and an optimized backtracking solution that's easy to reason out. This was my favorite puzzle of the set. +* **[Day 15](https://github.com/hunkyjimpjorps/AdventOfCode/blob/main/aoc2022/day-15/day-15.rkt)**. *Find the only possible place for a missing beacon.* The fundamental problem here is finding out a way to represent a range of integers sparsely (or [using a library that does that for you](https://docs.racket-lang.org/data/integer-set.html)). Lots of opportunities for optimization. +* **[Day 16](https://github.com/hunkyjimpjorps/AdventOfCode/blob/main/aoc2022/day-16/day-16.rkt)**. *Teach an elephant to open valves*. The first of a few heuristic search problems with enormous solution spaces. Part 1 (one person opening valves) is OK, but I only eventually solved Part 2 (two people simultaneously opening valves) a week later by watching console output and making a reasonable guess. [Nearly half of the remaining participants in AoC quit at this point](https://adventofcode.com/aoc2022/stats) and I don't really blame them. +* **Day 17** (no solution). *Find the period in the pattern of falling rocks.* I'm sure this one isn't too bad, but I was feeling too burnt out by Day 16 to give it more than a token try. +* **[Day 18](https://github.com/hunkyjimpjorps/AdventOfCode/blob/main/aoc2022/day-18/day-18.rkt)**. *Find the surface area of an irregular rock.* A nice straightforward volume-scanning problem, and another opportunity to use my sparse matrix data type. +* **Day 19** (no solution). *Pick the best plan for building mining robots.* Another heuristic search problem, so I skipped this one since I was still failing at day 16 part 2. Picking out an optimized robot building strategy had more moving parts than I knew how to deal with. +* **[Day 20](https://github.com/hunkyjimpjorps/AdventOfCode/blob/main/aoc2022/day-20/day-20.rkt)**. *Repeatedly shift elements around in a circular array.* Singly-linked lists are terrible for repeated arbitrary access, but I don't care. I figured out an algorithm that worked and I was happy enough with that to accept it and move on. After the previous streak of problems, I was just happy to figure out Part 2 on my own. (There was a modulo math trick element to Part 2 here as well, but this one was a lot more obvious to spot.) +* **[Day 21](https://github.com/hunkyjimpjorps/AdventOfCode/blob/main/aoc2022/day-21/day-21.rkt)**. *Figure out what to shout in the monkeys' math game.* I have a feeling the intended solution for Part 2 of this one was to build the expression tree and backtrack through the operations to calculate the unknown value. However, I just blindly started evaluating the result for various guesses and discovered through trial and error that the relationship between the guess and the result is linear, so all you need for part 2 is two guesses, their corresponding results, and some algebra. It's fun to accidentally discover simple solutions to complex-looking problems. +* **Day 22** (no solution). *Something involving walking around on a map.* I skipped this one because of the flu. C'est la vie. +* **[Day 23](https://github.com/hunkyjimpjorps/AdventOfCode/blob/main/aoc2022/day-23/day-23.rkt)**. *Cellular elfomata.* Another AoC staple, though later than usual. I think the story got in the way of the problem description a little bit here, but once I talked through the rules with someone else it was a straight shot to the solution. +* **Day 24** (no solution). *Walk through a blizzard.* I had to skip this one because of holiday travel (ironically made more difficult by a blizzard) and family obligations. It looks interesting, though, so I'm hoping to have some time to come back to it before January. +* **[Day 25](https://github.com/hunkyjimpjorps/AdventOfCode/blob/main/aoc2022/day-25/day-25.rkt)**. *Figure out balanced penternary.* The final AoC problem is always a clever little math brain teaser with one part, and this one was fun to puzzle out with pencil and paper. + +I think Days 16 and 19 could've used a second pass to narrow the possible solution space, or at least make the best heuristic a little more straightforward to reason out, but on the balance, I enjoyed myself this year, and I'm generally happy with my solutions and implementations.
\ No newline at end of file diff --git a/racket/aoc2022/day-01/day-01.ipynb b/racket/aoc2022/day-01/day-01.ipynb new file mode 100644 index 0000000..c79a3f6 --- /dev/null +++ b/racket/aoc2022/day-01/day-01.ipynb @@ -0,0 +1,139 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Advent of Code 2022\n", + "#### Day 1: Calorie Counting\n", + "\n", + "Elves carry various amounts of food with various caloric contents.\n", + "\n", + "**Part 1.** How many calories is the elf with the most calories of food carrying?\n", + "\n", + "**Part 2.** How many calories are the three elves with the most calories of food carrying?" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "(require racket\n", + " advent-of-code\n", + " threading)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Part 1\n", + "\n", + "The data file is a list of integers, one on each line, with an empty line separating the inventory of each elf.\n", + "\n", + "1. Fetch the input file,\n", + "2. split it on double newlines to find each elf's inventory,\n", + "3. split each inventory on single newlines,\n", + "4. convert each inventory member to a number,\n", + "5. sum up each list, and\n", + "6. find the maximum one. \n", + "\n", + "This is straightforward to do with threading/piping:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "<code>70374</code>" + ], + "text/plain": [ + "70374" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(define calorie-data (fetch-aoc-input (find-session) 2022 1))\n", + "\n", + "(~>> calorie-data\n", + " (string-split _ \"\\n\\n\")\n", + " (map (λ~>> string-split (map string->number) (apply +)))\n", + " (apply max))\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Part 2\n", + "\n", + "Similarly, to find the top three calorie holders,\n", + "\n", + "1. fetch the input file,\n", + "2. split it on double newlines,\n", + "3. split each list member on single newlines,\n", + "4. convert each sublist member to a number,\n", + "5. sum up each list, \n", + "6. sort the list from high to low,\n", + "7. take the first three members,\n", + "8. and sum them." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "<code>204610</code>" + ], + "text/plain": [ + "204610" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(~>> calorie-data\n", + " (string-split _ \"\\n\\n\")\n", + " (map (λ~>> string-split (map string->number) (apply +)))\n", + " (sort _ >)\n", + " (take _ 3)\n", + " (apply +))\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Racket (trusted)", + "language": "racket", + "name": "racket-trusted" + }, + "language_info": { + "codemirror_mode": "scheme", + "file_extension": ".rkt", + "mimetype": "text/x-racket", + "name": "racket", + "pygments_lexer": "racket", + "version": "8.7" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/racket/aoc2022/day-01/day-01.rkt b/racket/aoc2022/day-01/day-01.rkt new file mode 100644 index 0000000..5215014 --- /dev/null +++ b/racket/aoc2022/day-01/day-01.rkt @@ -0,0 +1,20 @@ +#lang racket + +(require advent-of-code + threading) + +(define calorie-data (fetch-aoc-input (find-session) 2022 1)) + +;; part 1 +(~>> calorie-data + (string-split _ "\n\n") + (map (λ~>> string-split (map string->number) (apply +))) + (apply max)) + +;; part 2 +(~>> calorie-data + (string-split _ "\n\n") + (map (λ~>> string-split (map string->number) (apply +))) + (sort _ >) + (take _ 3) + (apply +)) diff --git a/racket/aoc2022/day-02/day-02.ipynb b/racket/aoc2022/day-02/day-02.ipynb new file mode 100644 index 0000000..13b9986 --- /dev/null +++ b/racket/aoc2022/day-02/day-02.ipynb @@ -0,0 +1,193 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Advent of Code 2022\n", + "#### Day 2: Rock Paper Scissors\n", + "\n", + "You've given a strategy guide for how to win at a Rock Paper Scissors tournament. The first column is what your opponent will throw. Your score is determined by the result (win, lose, draw) of each round and what you played (rock, paper, scissors).\n", + "\n", + "**Part 1.** What's your tournament score if the second column represents what you should play in each round?\n", + "\n", + "**Part 2.** What's your tournament score if the second column represents the result of each round?\n", + "\n", + "For this solution, I'm using `rackjure` since I'm planning on using a bunch of dictionaries, and `rackjure`'s shorthand makes them easier to work with." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "vscode": { + "languageId": "racket" + } + }, + "outputs": [], + "source": [ + "#lang iracket/lang #:require rackjure\n", + "(require advent-of-code)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The input for this problem is a list with two columns; the first column is one of the characters `A`, `B` or `C` (corresponding to the opponent's throw of rock, paper or scissors) and the second column is `X`, `Y` or `Z`, whose meaning is currently unknown." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "vscode": { + "languageId": "racket" + } + }, + "outputs": [], + "source": [ + "(define strategy-guide (~> (fetch-aoc-input (find-session) 2022 2) (string-split \"\\n\")))\n", + "(define opponent-throw {\"A\" 'rock \"B\" 'paper \"C\" 'scissors})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We're also given the score for a round result and the bonus for the selected throw, and we write a function that determines the result for a given round." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "vscode": { + "languageId": "racket" + } + }, + "outputs": [], + "source": [ + "(define score-bonus {'rock 1 'paper 2 'scissors 3 'win 6 'draw 3 'lose 0})\n", + "\n", + "(define winning-rounds {'rock 'paper 'paper 'scissors 'scissors 'rock})\n", + "(define losing-rounds {'rock 'scissors 'paper 'rock 'scissors 'paper})\n", + "\n", + "(define (outcome them me)\n", + " (match* (them me)\n", + " [(x x) 'draw]\n", + " [(x y) #:when (eq? y (winning-rounds x)) 'win]\n", + " [(_ _) 'lose]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Part 1\n", + "\n", + "In part 1, we assume that the second column refers to the throw we should select in each round. We add that to our existing dictionary and write a `for` comprehension to calculate each round result." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "vscode": { + "languageId": "racket" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "<code>13809</code>" + ], + "text/plain": [ + "13809" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(define assume-throw (dict-merge opponent-throw {\"X\" 'rock \"Y\" 'paper \"Z\" 'scissors}))\n", + "\n", + "(for/sum ([play (in-list strategy-guide)])\n", + " (match-define (list them me) (string-split play))\n", + " (+ (score-bonus (outcome (assume-throw them) (assume-throw me)))\n", + " (score-bonus (assume-throw me))))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Part 2\n", + "Now we're told that the second column actually represents the round result: `X` is lose, `Y` is draw, `Z` is win. We can look up what we should throw in response for each round, and then calculate the score from that." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "vscode": { + "languageId": "racket" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "<code>12316</code>" + ], + "text/plain": [ + "12316" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(define assume-result (dict-merge opponent-throw {\"X\" 'lose \"Y\" 'draw \"Z\" 'win}))\n", + "(define (pick-throw them result)\n", + " (match* (them result)\n", + " [(x 'draw) x]\n", + " [(x 'win) (winning-rounds x)]\n", + " [(x 'lose) (losing-rounds x)]))\n", + "\n", + "(for/sum ([play (in-list strategy-guide)])\n", + " (match-define (list them result) (string-split play))\n", + " (+ (score-bonus (assume-result result))\n", + " (score-bonus (pick-throw (assume-result them) (assume-result result)))))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Racket (trusted)", + "language": "racket", + "name": "racket-trusted" + }, + "language_info": { + "codemirror_mode": "scheme", + "file_extension": ".rkt", + "mimetype": "text/x-racket", + "name": "Racket", + "pygments_lexer": "racket", + "version": "8.7" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/racket/aoc2022/day-02/day-02.pl b/racket/aoc2022/day-02/day-02.pl new file mode 100644 index 0000000..707da41 --- /dev/null +++ b/racket/aoc2022/day-02/day-02.pl @@ -0,0 +1,72 @@ +:- use_module(library(yall)). +:- use_module(library(apply)). + +output_solutions :- + get_data(Games), + part1_total(Games, Part1), + write(Part1), nl, !, + part2_total(Games, Part2), + write(Part2), nl. + +% Facts + +game(X, X, draw). +game(rock, scissors, win). +game(scissors, paper, win). +game(paper, rock, win). +game(rock, paper, lose). +game(scissors, rock, lose). +game(paper, scissors, lose). + +opponent_move("A", rock). +opponent_move("B", paper). +opponent_move("C", scissors). + +assume_move("X", rock). +assume_move("Y", paper). +assume_move("Z", scissors). + +assume_outcome("X", lose). +assume_outcome("Y", draw). +assume_outcome("Z", win). + +bonus(rock, 1). +bonus(paper, 2). +bonus(scissors, 3). +bonus(lose, 0). +bonus(draw, 3). +bonus(win, 6). + +% Rules + +get_data(Result) :- + setup_call_cleanup(open("2022/day-02/prolog-input.txt", read, In), + (read_string(In, _, Str), + split_string(Str, "\n", "\s\t\n", Lines), + maplist([In, Out] >> split_string(In, "\s", "", Out), Lines, Result)), + close(In)). + +score_game(MyMove, Result, Score) :- + bonus(Result, X), + bonus(MyMove, Y), + Score is X + Y. + +part1_score([Them, Me], Score) :- + opponent_move(Them, TheirMove), + assume_move(Me, MyMove), + game(MyMove, TheirMove, Result), + score_game(MyMove, Result, Score). + +part1_total(Games, Total) :- + maplist(part1_score, Games, Scores), + sum_list(Scores, Total). + +part2_score([Them, Outcome], Score) :- + opponent_move(Them, TheirMove), + assume_outcome(Outcome, Result), + game(MyMove, TheirMove, Result), + score_game(MyMove, Result, Score). + +part2_total(Games, Total) :- + maplist(part2_score, Games, Scores), + sum_list(Scores, Total).
\ No newline at end of file diff --git a/racket/aoc2022/day-02/prolog-input.txt b/racket/aoc2022/day-02/prolog-input.txt new file mode 100644 index 0000000..95a2b2a --- /dev/null +++ b/racket/aoc2022/day-02/prolog-input.txt @@ -0,0 +1,2500 @@ +C X +C X +A Y +C X +B Y +A X +A Z +B Y +C Z +C Z +B X +C Z +B Y +C Z +B Y +A Z +B Y +C X +C X +C X +B X +C Z +C X +C Z +C X +A Y +B Y +B Z +A X +C X +C Z +C Z +A Z +B Y +C Z +C X +C X +C Z +B Y +C Z +C Z +C X +B X +B X +A Y +C Z +C Z +B Y +B Y +C Z +C X +A Z +C X +C Z +C Z +B X +C Z +C Z +B Y +A Y +B X +C X +C X +C Z +C X +A Y +C X +C Z +A Z +B Y +C Z +C X +C X +C Z +C Z +C Z +A Z +C Z +A Z +A Z +C X +B Y +C X +C X +A Z +C X +A Y +C Z +C Z +A Y +A Z +C Z +C Z +A Y +C Z +C Y +B Y +B Y +A Z +A Y +C Z +C Z +A Z +C Z +A Y +B Z +C X +C Z +C X +B Y +C Z +C X +B Y +B X +A Y +C Z +C Z +C Z +B Z +A Y +C Z +C Z +C X +C Z +C X +A Y +C Z +C Z +C X +B Y +B X +B Y +C Z +C X +B X +C Z +C Z +A Y +C Z +A Z +A Y +C Z +A Y +C X +A Y +C Y +A Z +C X +C X +B Y +B Y +A Y +A Z +C Z +C X +C Z +A Y +B Y +A Y +B X +C Z +C Z +A Z +A Y +C X +C X +C X +A Z +B Y +C Z +C Z +C Z +C X +B Z +C Z +C Z +B Z +C Z +A Z +B Y +A Y +C Z +B Y +B X +B X +C X +C Z +A X +C Z +C X +C X +C X +C X +C X +B Y +C X +C X +A Y +A Y +C Z +C Z +B X +C Z +C Z +C Z +C Z +A Z +C Z +A Z +B Y +B Y +C Z +B Y +C Z +C Z +C Z +C X +B Y +B Y +C Z +A Z +C Z +C X +B Y +C Z +A Z +C Z +C X +C Z +C Z +C X +C X +B Z +A Y +B X +C Z +B Y +C X +C X +C Z +C Z +B X +B X +B Y +A X +C X +A Z +A Z +C Z +B Y +C Z +A Z +B X +C X +B Y +A Z +C X +A X +A Z +B Y +B X +B Y +A Y +C Z +C Z +B X +C Z +C X +B Y +C Z +C Z +A Y +B Y +A Z +A Y +C X +C X +C Z +A Z +C Z +B Z +A Z +A Y +C X +C Z +C X +B Z +C Z +B Y +A Y +B X +A Y +C Z +A Y +C Z +C X +B Y +C X +A Y +A Z +C Z +B Y +C X +A Y +C X +C Z +C X +B Y +C X +C Z +C Z +A Z +B Y +C X +B X +A Z +C X +C X +A Y +B X +C X +A Z +C Z +C Z +C Z +B Y +A Y +C X +C Z +C Z +A Z +C Z +A Y +C X +C X +C X +A Z +B Y +C Z +A Y +C Z +C Z +C X +C Z +A Z +C Z +B Y +C X +C X +C Z +B Z +B Y +C X +C X +A Y +A Z +A Z +A X +C X +A Y +B Y +A Y +A Z +B Y +B Y +A Y +B Y +C X +A Z +B X +C Z +A Z +B X +A Y +B X +B Y +A Y +A X +C Z +B Z +B X +B Z +C Z +C X +B X +B Y +A Y +B Y +B Y +B Y +A Z +A Y +B X +A Y +C X +B Y +B X +B Y +C X +A Y +C X +A Y +C Z +C Z +A Z +C Z +C X +C X +A Z +C X +C X +C X +A Y +A Z +A Z +C Z +B X +C X +C X +C Z +A Y +C X +C X +B X +C Z +C Z +C X +B Z +C X +C X +C Z +A Y +C X +A Z +C Z +C X +B X +B Y +C X +C Z +C X +C Z +A Z +C Z +C X +C Z +C Z +A Z +B X +C X +C Z +C X +C X +C Z +C X +C Z +A Z +A Z +A Z +C Z +C X +A Z +C Z +C Z +C Z +A Z +B Y +C X +B Y +C X +C Z +B Y +C X +C X +A Z +A Z +C X +C Z +C X +C Z +A Z +A Y +C Z +C Z +A Y +B Y +B Y +C Z +B Y +B X +B Z +A Y +A Z +C X +B Y +B Z +B Y +B Z +C Z +B Y +C Z +C Z +B Y +B X +B Z +C X +A Z +C X +C X +C X +A Z +C Z +A Z +C Z +A Y +C Y +B Y +A Z +B Y +C Z +A Z +A Y +B X +C X +C X +C X +C Z +C X +B X +C Z +A Y +C Z +A X +B Y +B Z +C Z +B Y +C Z +B X +B Y +C Z +B X +A Z +B X +B Y +A Y +B Z +C X +C Z +A Z +A Y +A Z +C X +C Z +B Z +A Z +A Z +C X +C Z +C X +C X +A Z +C X +C X +C X +B X +A Y +B Y +B X +B Y +C Z +B Y +B Z +C Z +C X +B Y +C Z +C Z +C Z +C Z +C Z +C Z +A Z +A Z +A Z +C X +C Z +B Y +C Z +C Z +C Z +C Z +C X +C X +A Z +B Y +A Z +B Z +C X +C X +C Z +C Z +C X +A Y +C X +A Y +C Z +A Z +C Z +B X +C Z +C Z +C X +C X +C Z +B Z +A Y +B X +A Y +B Y +A Y +A Z +A Z +C Z +B X +C Z +C X +C Z +C Z +A Z +C X +A Y +C X +A Z +C Z +C X +C Z +A Z +C X +C X +C X +C X +B X +B Y +C X +C Z +C Z +C Z +C Z +A Z +A Z +A Y +C Z +C X +C Z +C Z +C Z +A Y +C X +A Z +C Y +A Z +C Z +C X +A Y +C Z +C X +C X +A Z +B Y +C Z +A Z +C Z +C Z +C Z +B Y +C X +C X +A X +A Y +C Z +A Z +C Z +B Y +C X +B X +C X +C X +A Y +C Z +C Z +C Z +C X +A Z +B Y +A Y +B Z +B Z +B X +A Z +B X +B X +A Z +A Z +C Y +B Y +C Z +A X +C Z +B X +C Z +A Y +A Y +C Z +C Z +A Z +B Y +C Z +C Z +C Z +C X +A Y +C X +B Y +B Y +C X +C Z +C X +B X +A Y +A Y +C Z +C Z +C Z +C Z +C Z +B X +C Z +A Y +B X +A Y +A Z +C Z +C X +B Y +B Y +C Z +C Z +C Z +B Y +C X +B Y +A Y +B X +C Z +C Z +A Y +C X +C Z +A Y +C X +C X +A Z +C Z +B X +A Z +B Y +C Z +A Z +B X +C Z +B Y +C Z +B Y +C X +C Z +B X +C X +B Y +C X +A Y +C Z +C Z +C X +B Y +C X +C Z +C X +C Z +A Z +A Y +C X +C Z +C Z +A Z +C X +B Z +A Z +B Y +C X +A Z +C Z +B Y +C X +C X +C Z +C Z +B Y +A Z +C Z +C X +C Z +A Z +C X +A Z +C Z +C X +C X +C Z +B X +C Z +C Z +C Z +B Y +A X +B Y +C X +A Z +B X +A Z +C Z +C X +C Z +C Z +B Y +B X +C Z +B Z +B Y +B X +C X +C X +C X +C Z +B Y +C Z +C Z +C Z +C Z +C Z +C Z +C X +C Z +A Y +C X +B X +A Y +C X +C X +C Z +C Z +C X +B X +B Y +B Y +C Z +B X +C X +C X +C Z +C Z +A Y +C Z +A Y +C Z +C Z +B Y +A Z +B X +B X +C Z +C Z +A Y +A Y +C Z +C Z +C X +A Y +A Y +C Z +A Z +C X +B Z +A Y +C X +B Z +A Y +C X +B Y +C X +C X +C Z +A X +C Y +A Y +B Z +B Y +A X +B Y +A Z +C Z +C X +C X +C X +C Y +B Y +C Z +A Z +C X +C X +C Z +C Z +C X +C Z +B Y +C Z +C X +B Y +A Z +C X +B Y +C Z +C X +B Y +A Z +B X +C Z +B X +B X +C Z +C Z +C X +B Y +A Y +B X +B X +A Y +B Y +B Y +B X +A Z +A Y +C Z +B Y +C Z +A Z +C Z +C X +A Z +C X +C Z +C X +B Y +C X +A Z +B X +C Z +C X +C X +B Y +A Y +C Z +C X +A Y +A X +C Z +B Y +B X +C X +C X +C X +C Z +A Z +B Y +A Y +B Y +B X +B Y +B Y +A Z +B Y +B Y +B Y +C Z +C X +A Z +A X +B Z +C X +C X +C X +C X +C Z +A Z +B Y +A Z +B Y +C X +A Z +A Y +C Z +C X +B X +A Z +B Y +C Z +A Z +C X +A Z +A Y +B Y +C Z +B Y +C X +A Z +A Z +A X +C Z +C X +A Y +B Y +B X +C Z +A Z +C X +B X +B Y +A Z +C X +B Y +C X +C Z +C Z +B Y +A Z +A Y +C X +B Y +C X +C X +C Z +C Z +C X +A X +C X +A Z +C Z +B X +B X +C X +B X +B Y +C X +C X +A Y +B Y +C Z +C Z +C Z +C Z +C Z +C Z +A Z +B X +C X +B Y +B Y +C X +C Z +A Z +C X +C Z +C Z +C X +A Z +C Z +A Z +C Z +A Y +C X +C X +B Y +C X +C Z +B X +A Y +C Z +C X +B Y +B X +A Y +C X +A Z +C Z +C X +C X +A Y +B Y +C Z +B Z +C Z +C X +B Y +C Z +C X +A Y +C X +C Z +C X +C X +B X +C X +B Y +C X +C Z +C X +B X +B X +C Z +A Y +C Z +C Y +B X +A Z +C X +A Z +B Z +A Y +C Z +C Z +A Z +B Z +A Z +C X +C Z +C Z +A Z +A Y +C Z +C X +A Y +B Y +B X +A Z +A Y +C X +B X +A Y +C Z +B Y +C X +C X +C X +B X +A Z +B Y +B Y +A Z +C X +B X +B X +A Y +C Z +C X +C X +A Y +C X +C X +C Z +C X +A Y +B Y +C Z +A Z +C Z +A Z +A Z +A Y +C X +C X +C X +A Y +A Y +B Y +B Z +A Z +C X +C Z +C X +C Z +B X +C X +C X +B X +C Z +C X +B Y +B X +C Z +A Z +C Z +B Y +C Z +C Z +B X +A Y +B Y +A Z +B Y +C X +C X +A Z +C Z +C X +C Z +C X +A Z +C X +A Z +C X +A Y +A Z +C X +C Z +B Y +C Z +A Z +C Y +B Z +B Y +A Z +C Z +A X +A Z +C Z +C X +C X +A Y +C Z +C X +C Z +C Z +C Z +B Y +C Z +C X +C Z +B Y +C Z +B Y +C Z +C Z +C Z +C Z +C Z +B Y +C Z +B Y +A Y +C X +B Y +A Y +C X +A Z +A Y +C Z +B Y +C Z +C X +A Y +B Y +C X +C X +C X +A Y +A Z +B X +B X +B X +B Y +C Z +B Y +C Z +C Z +B Y +A X +C X +A Y +C Z +B Y +B Y +C Z +C Z +C X +C Z +C Z +B Y +C Z +C X +A Y +A Z +C X +B Z +C X +B Y +C Z +C X +A Y +A Z +C Z +C X +C Z +C X +C X +C X +C X +C Z +C X +B Y +A Y +C X +C Z +A Z +B Y +C Z +C X +C Z +B Y +A Z +A Y +A Y +B Y +B Y +A Z +B X +A Y +C X +C X +A Y +C X +A Y +B Y +C Z +A X +B X +A Y +A Z +C Z +B X +C Z +B Y +C X +B Y +C Z +A Z +A Y +C X +C Z +B X +B Y +C Z +C Z +A Z +C Z +B Z +C X +C X +C Z +C Z +C Z +C X +C X +C Z +B Y +C Z +B Z +C X +A Y +C Z +B Y +C X +A Y +C Z +B X +C Z +A Z +C Z +C X +A Y +A Y +A Y +C Z +C Z +A Y +A X +C Z +C Z +C X +C Z +A Z +A Z +C Z +C X +C Z +B Y +A Y +B Z +B Y +C X +C Z +C X +B X +B Y +C X +C X +C X +A Z +A Y +C X +C Z +C X +B Y +C Z +B X +C Z +B Y +A Z +C X +B Y +C X +B X +A Z +C X +B Y +A Y +C Z +C X +A Y +C X +B Y +B X +C Z +C X +C X +C Z +C X +C X +A Y +A X +C Z +C Z +C Z +B Y +C Z +A Z +B X +C Z +C X +C X +A Y +A Z +B Y +B Y +C Z +C Z +A Y +A Z +A Z +C Z +B Y +B Y +C Z +B Y +C X +C X +C Z +A Z +C X +C X +B Y +B Y +C Z +B Y +C Z +C Z +C Z +C Z +C X +C Z +B Y +C Z +C Z +B X +C Z +C Z +C X +B Y +C Z +A Z +A Z +A Z +C X +B Y +C Z +A Y +B X +C X +C X +A X +A Y +B X +C Z +C X +C Z +C Z +B Y +C Z +C Z +B Z +C Z +C Z +B X +A Y +C X +A Z +B Y +A Z +C X +B X +B X +C Z +A X +B Z +A Z +B Y +C X +C Z +B Z +C X +B X +C Z +A Z +B X +C Z +C Z +A Z +A Y +C Z +C Z +B X +A Z +C Z +B Y +B Y +C Z +B Y +C Z +C Z +C Z +C Z +C Z +C Z +C Z +C X +A Y +C Z +C Z +C X +C Z +B Y +B X +B Y +A Z +C Z +B Y +B X +C Z +B X +C Y +C X +B Y +C Z +B Y +A Z +B Y +C X +C Z +B X +C Z +A Y +C Z +B X +A Z +A Y +B Y +C Z +A Y +B Y +C X +A Z +A Y +C Z +C Z +A Y +B X +C X +B Y +A Y +C X +B X +C X +C Z +C Z +B Y +A Z +B Y +A Z +A Y +A Z +B X +A Y +C X +B Y +C Z +C Z +A Z +C Z +C Z +C X +C Z +C Z +B Y +A Z +C X +A Y +C Z +A X +A Z +C Z +B Y +C X +C Y +A Y +B X +B Y +C Z +C Z +B X +B X +C Y +B Y +B X +C X +C Z +A Z +C Z +A Z +C Z +C Z +A Z +B X +C Z +C X +C Z +B Y +B X +A Z +C Z +B X +C Z +B Z +C Z +B Y +B Y +C Z +B Y +A Y +A Z +A Z +C X +A X +C X +C Z +C Z +A Y +C Z +C Z +C Z +A Y +B X +C Z +C X +B X +C X +C X +B Y +C Z +B X +C X +B Y +A Y +C Z +C X +C X +A Z +B Y +C Z +B Y +C Z +B Z +B Y +B Y +A Y +B Y +B Y +A X +A Y +C Z +C X +B X +C Z +C X +C X +B Y +C Z +C Z +B Y +A Y +B Y +C Z +C X +C Z +C Z +C X +A Y +A Y +C Z +A Y +C X +C Z +A Z +A Z +C Z +B Y +A X +A X +B X +A Z +C X +C X +C X +C X +A Z +A Z +C X +B X +B Y +C X +B Y +B Z +A Z +A Y +C X +B Y +B Y +C Z +B X +B X +C Z +B Y +C Z +C Z +B Y +C Z +C X +C Z +B Y +C X +C Z +C Z +C X +B X +C Z +C X +C X +C Y +C Z +B Y +C X +C X +A Y +C X +C X +C Z +C X +C Z +C Z +C Z +A X +C Z +C Y +C Z +C Z +C Z +C Z +A X +C Z +B X +C Z +C X +B Y +C X +A Z +C Z +C X +B Y +B Y +A Z +A Y +C X +A Z +C Z +C Z +A Y +A Z +A X +C Z +A Z +C Z +C Z +C Z +A Z +A Y +A Z +A Z +A Y +A Y +C X +A Y +A Y +C Z +C Z +C Z +B X +C X +C Z +B X +C Z +C Z +C Z +C Z +C Z +B Y +C Z +B Y +C Z +C X +A Z +C Z +B X +B Y +C X +B Y +C X +C X +A Y +C Z +C X +C X +C Z +C X +C X +C Z +B Y +B X +C X +B X +B Y +C X +A Z +B Y +A Z +B Y +A Z +A Y +C Z +C Z +C X +B Y +A Y +A Y +C X +B Y +C X +A Z +A Z +C X +A X +C X +A Z +C X +C Z +C X +A Y +C X +B Y +B Y +C X +A Z +C Z +C Z +B X +C Z +C Z +A Y +C Z +B Y +A Z +A X +C Z +C X +B Y +A Z +C X +B X +A Z +A Z +C Z +A Z +C Z +C Z +A X +C X +A Z +C X +A Y +B Y +C Z +B Y +B X +C Z +A X +B Z +A Z +A Z +C Z +A Z +C Z +A Z +C X +C X +C X +C X +A Z +C Z +C X +B Y +A Z +B Y +A Y +C Z +A Z +C X +C Z +B Y +C X +A Y +B X +A Y +B X +B Y +C Z +A Y +B Y +A Y +B Y +C Z +C X +C X +C X +C Z +B Y +C Z +B X +C X +A Y +A X +B Y +A Z +C Z +A Y +A Z +C X +B Y +A Z +C Z +C X +C Y +C X +A Z +A Y +C X +A Y +C X +C X +A Z +B Y +C Z +C X +B X +B Y +A Z +A Z +B X +B Y +C X +B Y +C Z +B Y +C Y +B Y +B X +B Y +A Y +B Y +C Z +B X +B Y +C Z +A Z +C Z +B X +A Y +B X +C Z +B Y +A Y +C Z +C Z +C Y +C Z +A Y +C X +A Y +C Z +C Z +C Z +C Z +C Z +C X +C Z +C Z +B Y +C X +C Z +B X +C X +C Z +C X +A Z +C X +C X +C Z +C X +C X +C Z +A Z +B X +B Y +C Z +A Z +C X +B X +B Y +C Z +C Z +A Y +C X +C Z +C X +C Z +B Y +C X +C Z +C X +C X +C Z +C X +B X +C X +C Z +A Y +A Y +A Y +C Z +C Z +B X +C X +A Z +C Z +C X +C Z +B X +C Z +B Z +C X +A Y +C Z +A Y +C X +B Y +B Y +A Z +C Z +C Z +C Z +A Z +C X +A Y +C Z +A Y +C Z +A Z +A Z +B Y +C Z +A Z +C X +A Y +C Z +C Z +C X +C X +C X +A X +B Z +C Z +C X +A Y +A Z +C Z +B X +B Y +C X +C Z +B X +B Y +C X +B X +C Z +C X +C X +C X +C X +A Y +A Z +A Y +B Y +C Z +B Y +B Y +C Z +A X +C Z +B X +C Z +C Z +A Z +B Y +C Z +C Z +A Y +A Z +A Z +A Z +B X +C X +C X +B Y +C Z +C X +B Y +A Y +C X +C Z +C Z +C X +C Y +A Y +A Z +C X +C X +A Z +B X +C Z +A X +C Z +C X +C Z +A X +A Z +C X +B Y +C Z +C Z +B X +A Y +B Y +C Z +C Z +A Z +A Y +C Z +C Z +A X +B Y +C Z +C Z +B Y +C X +C Z +B X +B Y +A Z +C Z +C X +C X +C Z +B Z +B Y +C Z +C X +A Y +C X +A Y +A Z +B Y +B X +C Z +A Z +C Z +C Z +C Z +A Y +B Y +A Z +B Y +C Z +C X +B Y +C Z +A Y +C Z +A Z +C Z +C Z +C Z +A Z +C X +B Y +C Z +A Z +C Z +C Z +A Z +C Z +C X +B Z +C Z +C Z +A Z +C Z +C Z +B Y +B X +C Z +C Z +B Y +A Y +C Z +A Z +A Z +C Z +C X +C X +B X +C X +A Y +B X +A Y +C Z +C X +B Y +C Z +C X +C X +C X +B X +B Z +C Z +B Y +C X +A Z +C Z +C Z +A Z +A Z +C Z +C Z +A Z +A Z +A Z +C Z +A Z +C Y +A Z +C Z +C Z +C X +C X +C Z +C Z +A Z +C Z +A Z +C X +C X +C X +A Y +A Z +A Z +A Y +B X +C Z +C Z +B X +C Z +C X +B Y +C Z +C X +C X +B Y +B Y +A Y +A Z +B X +C X +B Y +B Y +B Y +C Z +C X +C X +A Y +B Z +A Z +A Z +B X +C X +A Y +C X +C Z +C X +A Y +A Z +C Z +A Z +C Z +C X +C X +A Z +B X +B X +B Y +C Z +C Z +C X +C X +C X +B Y +C Z +C X +B X +C Z +C X +B X +A Y +B Y +C Z +A Z +A Y +C Z +A Y +A Y +C Z +B Y +C Z +B Z +C Z +A Z +C X +C X +C Z +B X +C X +B Y +C Z +B X +C Z +C X +B X +B Y +B Z +B X +A Y +C X +C Z +C Z +A Y +B X +A Y +C X +C Z +B Y +C Z +C Z +C Z +A Z +A Z +A Y +C Z +B Y +C Z +C X +B Y +C Z +C Z +A X +C X +B Y +A Z +C Z +C X +A Z +A Y +C Z +C Z +B X +C Z +A Z +A Y +C X +A Y +C Z +C X +C X +C X +B Y +C X +A Y +C X +B Y +B X +A Y +B X +A Y +B Z +C Z +A Z +B Y +B X +C Z +C Z +C X +B Y +A Z +A Z +A Y +A Y +C Z +A Z +A Z +B Y +C X +C Z +C Z +C Z +A Y +A Z diff --git a/racket/aoc2022/day-03/day-03.ipynb b/racket/aoc2022/day-03/day-03.ipynb new file mode 100644 index 0000000..27b8086 --- /dev/null +++ b/racket/aoc2022/day-03/day-03.ipynb @@ -0,0 +1,168 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Advent of Code 2022\n", + "#### Day 3: Rucksack Reorganization\n", + "\n", + "**Part 1.** Every elf has a rucksack with two compartments. What's the total priority value of the items that appear in both compartments of each sack?\n", + "\n", + "**Part 2.** Each group of three elves is carrying exactly one item in common. What's the total priority value of those common items?\n", + "\n", + "To ease the conversion between strings, lists and sets I bring in some utility functions from `relation`. " + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "vscode": { + "languageId": "racket" + } + }, + "outputs": [], + "source": [ + "(require racket\n", + " advent-of-code\n", + " threading\n", + " (only-in relation ->list ->set ->char))\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The items in each elf's inventory are represented by a string of alphabetic (case-sensitive) characters, each with a \"priority value\":\n", + "* Lowercase item types `a` through `z` have priorities 1 through 26.\n", + "* Uppercase item types `A` through `Z` have priorities 27 through 52." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "vscode": { + "languageId": "racket" + } + }, + "outputs": [], + "source": [ + "(define priority\n", + " (for/hash ([i (in-naturals 1)]\n", + " [c (in-sequences (inclusive-range 97 122) (inclusive-range 65 90))])\n", + " (values (->char c) i)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Part 1\n", + "\n", + "The front half and back half of the rucksack have one item in common we need to identify. For each rucksack in the inventory, we apply the following steps:\n", + "\n", + "1. Split the bag into the two halves,\n", + "2. find the common item in both halves (the intersection of the sets),\n", + "3. then look up that common item's value. " + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "vscode": { + "languageId": "racket" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "<code>7845</code>" + ], + "text/plain": [ + "7845" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(define raw-inventory (~> (fetch-aoc-input (find-session) 2022 3) string-split))\n", + "\n", + "(for/sum ([bag (in-list raw-inventory)])\n", + " (define len (/ (string-length bag) 2))\n", + " (~>> bag\n", + " ->list\n", + " ((λ (xs) (list (take xs len) (drop xs len)))) ; step 1\n", + " (apply set-intersect _) ; step 2\n", + " set-first\n", + " (hash-ref priority))) ; step 3\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Part 2\n", + "\n", + "Now we need to take three rucksacks at a time and find the common item they all share. The procedure is simpler than Part 1's, especially with the `in-slice` function that automatically forms groups of three." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "vscode": { + "languageId": "racket" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "<code>2790</code>" + ], + "text/plain": [ + "2790" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(for/sum ([bags (in-slice 3 raw-inventory)])\n", + " (~>> bags (map ->set) (apply set-intersect) set-first (hash-ref priority)))\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Racket (Trusted)", + "language": "racket", + "name": "racket-trusted" + }, + "language_info": { + "codemirror_mode": "scheme", + "file_extension": ".rkt", + "mimetype": "text/x-racket", + "name": "Racket", + "pygments_lexer": "racket", + "version": "8.7" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "7454d72389401259f8ab87ac90deac92d19baedf4a52e60301852b1f4c653c5c" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/racket/aoc2022/day-04/day-04.ipynb b/racket/aoc2022/day-04/day-04.ipynb new file mode 100644 index 0000000..44c8980 --- /dev/null +++ b/racket/aoc2022/day-04/day-04.ipynb @@ -0,0 +1,148 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Advent of Code 2022\n", + "#### Day 4: Camp Cleanup\n", + "\n", + "Each elf in a pair of elves is assigned a range of ID numbers.\n", + "\n", + "**Part 1.** How many pairs have one elf assigned to a range completely contained by the other's?\n", + "\n", + "**Part 2.** How many pairs have overlapping assignments?\n", + "\n", + "Since this problem heavily uses ranges, I'm using `rebellion/base/range` to do the heavy lifting." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "vscode": { + "languageId": "racket" + } + }, + "outputs": [], + "source": [ + "#lang iracket/lang #:require rackjure\n", + "(require advent-of-code\n", + " relation\n", + " rebellion/base/range)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Part 1\n", + "\n", + "All the assignments look like `\"11-22,33-44\"`, so we write a function to extract the numbers from the string with a regex, decompose the values with structural matching, and construct a pair of [`rebellion` ranges](https://docs.racket-lang.org/rebellion/Ranges.html).\n", + "\n", + "Once we have the two ranges, we can use a predicate that tests if one completely contains the other or vice versa to count the corresponding assignments." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "vscode": { + "languageId": "racket" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "<code>550</code>" + ], + "text/plain": [ + "550" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(define assignments (~> (fetch-aoc-input (find-session) 2022 4) string-split))\n", + "\n", + "(define (parse-range str)\n", + " (match str\n", + " [(regexp #px\"(\\\\d+)-(\\\\d+),(\\\\d+)-(\\\\d+)\" (list _ a b c d))\n", + " (values (closed-range (->number a) (->number b)) (closed-range (->number c) (->number d)))]))\n", + "\n", + "(define (one-encloses-the-other? str)\n", + " (define-values (range1 range2) (parse-range str))\n", + " (or (range-encloses? range1 range2) (range-encloses? range2 range1)))\n", + "\n", + "(count one-encloses-the-other? assignments)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Part 2\n", + "\n", + "The procedure for part 2 is the same, just using the predicate for overlapping ranges instead." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "vscode": { + "languageId": "racket" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "<code>931</code>" + ], + "text/plain": [ + "931" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(define (one-overlaps-the-other? str)\n", + " (define-values (range1 range2) (parse-range str))\n", + " (range-overlaps? range1 range2))\n", + "\n", + "(count one-overlaps-the-other? assignments)\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Racket (Trusted)", + "language": "racket", + "name": "racket-trusted" + }, + "language_info": { + "codemirror_mode": "scheme", + "file_extension": ".rkt", + "mimetype": "text/x-racket", + "name": "Racket", + "pygments_lexer": "racket", + "version": "8.7" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/racket/aoc2022/day-05/day-05.ipynb b/racket/aoc2022/day-05/day-05.ipynb new file mode 100644 index 0000000..34cf4e4 --- /dev/null +++ b/racket/aoc2022/day-05/day-05.ipynb @@ -0,0 +1,187 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Advent of Code 2022\n", + "#### Day 5: Supply Stacks\n", + "\n", + "You're operating a crane, following a set of instructions telling you how many boxes to move from one stack to another. After you follow the instructions, the top boxes in the stacks spell out a message.\n", + "\n", + "**Part 1.** What's the message if the crane moves one box at a time?\n", + "\n", + "**Part 2.** What's the message if the crane moves all of the boxes at once?\n", + "\n", + "I'm bringing in my usual utility functions." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "(require racket\n", + " advent-of-code\n", + " threading\n", + " (only-in relation ->string ->list ->number)\n", + " (only-in algorithms chunks-of))\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The data file in this one is unusual: the first 10 lines are a pictorial representation of the stacks of boxes, and the remaining lines are instructions in the form `move X from Y to Z`.\n", + "\n", + "Tackling the boxes first, I can get the contents of each stack if I treat the picture as an array and transpose it, drop the first row that contains only brackets, then only take rows 1, 5, 9..., which are the rows with letters in them and trim the leading spaces from each row. I then use this list to create a hashtable." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "(define assignments (~> (fetch-aoc-input (find-session) 2022 5) (string-split \"\\n\")))\n", + "\n", + "(define crates-list\n", + " (~>> assignments\n", + " (take _ 8)\n", + " (map ->list)\n", + " (apply map list _)\n", + " rest\n", + " (chunks-of _ 4)\n", + " (map (λ~> first ->string string-trim ->list) _)))\n", + "\n", + "(define crates\n", + " (for/hash ([c (in-list crates-list)] [i (in-naturals 1)])\n", + " (values i c)))\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The instructions are a little easier; this is just a regex and destructuring like in Day 4." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "(struct instruction (n from to))\n", + "\n", + "(define (parse-instruction str)\n", + " (match str\n", + " [(regexp #px\"move (\\\\d+) from (\\\\d) to (\\\\d)\" (list _ n from to))\n", + " (instruction (->number n) (->number from) (->number to))]))\n", + "\n", + "(define instructions (~>> assignments (drop _ 10) (map parse-instruction)))\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Part 1\n", + "\n", + "The main function to iterate over the list of instructions is the same for both parts, except for whether the boxes taken off of the origin stack are reversed or not when they end up on the destination stack. They end up reversed if they're taken off one at a time, and don't reverse if the whole stack is picked up at once.\n", + "\n", + "Once I've iterated through all the instructions, the `#:result` clause parses the final crate state." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "<code>\"WHTLRMZRC\"</code>" + ], + "text/plain": [ + "\"WHTLRMZRC\"" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(define (find-crate-message cs [reverse? #true])\n", + " (define direction (if reverse? reverse identity))\n", + " (for/fold ([current-crates cs]\n", + " #:result (~>> (hash-values current-crates) (map first) (apply string)))\n", + " ([i (in-list instructions)])\n", + " (match-define (instruction n from to) i)\n", + " (define taken (~> (hash-ref current-crates from) (take _ n) direction))\n", + " (~> current-crates\n", + " (hash-update _ from (λ (v) (drop v n)))\n", + " (hash-update _ to (λ (v) (append taken v))))))\n", + "\n", + "(find-crate-message crates)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Part 2\n", + "\n", + "The result, if the moved boxes don't get flipped:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "<code>\"GMPMLWNMG\"</code>" + ], + "text/plain": [ + "\"GMPMLWNMG\"" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(find-crate-message crates #false)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Racket (Trusted)", + "language": "racket", + "name": "racket-trusted" + }, + "language_info": { + "codemirror_mode": "scheme", + "file_extension": ".rkt", + "mimetype": "text/x-racket", + "name": "racket", + "pygments_lexer": "racket", + "version": "8.7" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/racket/aoc2022/day-05/day-05.rkt b/racket/aoc2022/day-05/day-05.rkt new file mode 100644 index 0000000..76d4ca6 --- /dev/null +++ b/racket/aoc2022/day-05/day-05.rkt @@ -0,0 +1,46 @@ +#lang racket + +(require advent-of-code + threading + (only-in relation ->string ->list ->number) + (only-in algorithms chunks-of)) + +(define data (~> (fetch-aoc-input (find-session) 2022 5) (string-split "\n"))) + +(struct instruction (n from to)) + +(define crates-list + (~>> data + (take _ 8) + (map (λ~>> ->list)) + (apply map list _) + rest + (chunks-of _ 4) + (map (λ~> first ->string string-trim ->list) _))) + +(define crates + (for/hash ([c (in-list crates-list)] [i (in-naturals 1)]) + (values i c))) + +(define (parse-instruction str) + (match str + [(regexp #px"move (\\d+) from (\\d) to (\\d)" (list _ n from to)) + (instruction (->number n) (->number from) (->number to))])) + +(define instructions (~>> data (drop _ 10) (map parse-instruction))) + +(define (find-crate-message cs [reverse-function reverse]) + (for/fold ([current-crates cs] + #:result (~>> (hash-values current-crates) (map first) (apply string))) + ([i (in-list instructions)]) + (match-define (instruction n from to) i) + (define taken (~> (hash-ref current-crates from) (take _ n) reverse-function)) + (~> current-crates + (hash-update _ from (λ (v) (drop v n))) + (hash-update _ to (λ (v) (append taken v)))))) + +;; part 1 +(find-crate-message crates) + +;; part 2 +(find-crate-message crates identity) diff --git a/racket/aoc2022/day-06/day-06.ipynb b/racket/aoc2022/day-06/day-06.ipynb new file mode 100644 index 0000000..0c89fa1 --- /dev/null +++ b/racket/aoc2022/day-06/day-06.ipynb @@ -0,0 +1,122 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Advent of Code 2022\n", + "#### Day 6: Tuning Trouble\n", + "\n", + "You're parsing a buffer of characters, looking for a \"marker\": the index of the first character where the previous $n$ characters are all unique.\n", + "\n", + "**Part 1.** What if $n = 4$, for the \"start of packet\" marker?\n", + "\n", + "**Part 2.** What if $n = 14$, for the \"start of message\" marker?" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "(require racket\n", + " advent-of-code\n", + " (only-in algorithms sliding))\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Parts 1 and 2\n", + "\n", + "`(sliding xs n)` returns all the overlapping sublists of `xs` that are `n` elements wide. This turns this problem into a simple `for` comprehension, just looking for the first sublist with no duplicates (which means its length after deduplication doesn't change).\n", + "\n", + "The solution to part 1 and part 2 is the same, just differing in how wide the window is." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "<code>1042</code>" + ], + "text/plain": [ + "1042" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(define buffer (fetch-aoc-input (find-session) 2022 6))\n", + "\n", + "(define (find-marker data type)\n", + " (define n\n", + " (match type\n", + " ['start-of-packet 4]\n", + " ['start-of-message 14]))\n", + " (for/first ([chunk (in-list (sliding (string->list data) n))]\n", + " [i (in-naturals n)]\n", + " #:when (= n (~> chunk remove-duplicates length)))\n", + " i))\n", + "\n", + "(find-marker buffer 'start-of-packet)\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "<code>2980</code>" + ], + "text/plain": [ + "2980" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(find-marker buffer 'start-of-message)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Racket (trusted)", + "language": "racket", + "name": "racket-trusted" + }, + "language_info": { + "codemirror_mode": "scheme", + "file_extension": ".rkt", + "mimetype": "text/x-racket", + "name": "racket", + "pygments_lexer": "racket", + "version": "8.7" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/racket/aoc2022/day-06/day-06.rkt b/racket/aoc2022/day-06/day-06.rkt new file mode 100644 index 0000000..1c167a6 --- /dev/null +++ b/racket/aoc2022/day-06/day-06.rkt @@ -0,0 +1,22 @@ +#lang racket + +(require advent-of-code + (only-in algorithms sliding)) + +(define buffer (fetch-aoc-input (find-session) 2022 6)) + +(define (find-marker data type) + (define n + (case type + [(start-of-packet) 4] + [(start-of-message) 14])) + (for/first ([chunk (in-list (sliding (string->list data) n))] + [i (in-naturals n)] + #:unless (check-duplicates chunk)) + i)) + +;; part 1 +(find-marker buffer 'start-of-packet) + +;; part 2 +(find-marker buffer 'start-of-message) diff --git a/racket/aoc2022/day-07/day-07.rkt b/racket/aoc2022/day-07/day-07.rkt new file mode 100644 index 0000000..3826cc4 --- /dev/null +++ b/racket/aoc2022/day-07/day-07.rkt @@ -0,0 +1,36 @@ +#lang racket + +(require advent-of-code + fancy-app + threading + (only-in relation ->number)) + +(define command-output (~> (fetch-aoc-input (find-session) 2022 7) (string-split "\n"))) + +(define (parse-commands cmds) + (define (update-sizes h path size) + (match path + ['() h] + [(list* _ fs) (update-sizes (hash-update h path (+ _ size) 0) fs size)])) + + (for/fold ([folders (hash)] [current-path '()] [previously-seen? #false] #:result folders) + ([cmd (in-list cmds)]) + (match (string-split cmd) + [(list "$" "ls") (values folders current-path (hash-has-key? folders current-path))] + [(list "$" "cd" "/") (values folders '("/") #false)] + [(list "$" "cd" "..") (values folders (rest current-path) #false)] + [(list "$" "cd" folder) (values folders (cons folder current-path) #false)] + [(list "dir" _) (values folders current-path previously-seen?)] + [(list (app ->number size) _) + (cond + [previously-seen? (values folders current-path previously-seen?)] + [else (values (update-sizes folders current-path size) current-path previously-seen?)])]))) + +(define folders (parse-commands command-output)) + +;; part 1 +(for/sum ([(_ v) (in-hash folders)] #:when (<= v 100000)) v) + +;; part 2 +(define required-to-delete (- 30000000 (- 70000000 (hash-ref folders '("/"))))) +(~>> folders hash-values (filter (> _ required-to-delete)) (apply min)) diff --git a/racket/aoc2022/day-08/day-08.ipynb b/racket/aoc2022/day-08/day-08.ipynb new file mode 100644 index 0000000..890a9bb --- /dev/null +++ b/racket/aoc2022/day-08/day-08.ipynb @@ -0,0 +1,257 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Advent of Code 2022\n", + "#### Day 8: Treetop Tree House\n", + "\n", + "For a rectangular grid of trees of varying heights,\n", + "\n", + "**Part 1.** How many trees are visible (not blocked by same-height or taller trees in their row or column) from outside the patch?\n", + "\n", + "**Part 2.** What's the tree with the best \"scenic score\" (the product of the number of trees it can see in each cardinal direction)?\n", + "\n", + "For this solution, I didn't use any packages besides the standard distribution and `advent-of-code`." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "vscode": { + "languageId": "racket" + } + }, + "outputs": [], + "source": [ + "(require racket\n", + " advent-of-code)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Part 1\n", + "\n", + "I built this array as a hashtable with a coordinate struct as the key and the tree height as the value, which makes it easy to check if a particular location falls outside the grid." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "vscode": { + "languageId": "racket" + } + }, + "outputs": [], + "source": [ + "(struct posn (r c) #:transparent)\n", + "\n", + "(define (make-tree-grid data)\n", + " (for*/hash ([(row r) (in-indexed data)] [(col c) (in-indexed (in-string row))])\n", + " (values (posn r c) (string->number (string col)))))\n", + "\n", + "(define tree-grid (make-tree-grid (in-lines (open-aoc-input (find-session) 2022 8))))" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A tree is visible if there aren't any other trees as tall as it or taller blocking the visibility in at least one of the cardinal directions. We check in a particular direction by repeatedly stepping outwards from the original coordinate and checking if we've found a blocking tree yet.\n", + "\n", + "The `for/first` returns `#true` if it finds a blocking tree and `#false` if it runs out of trees to check, and the result is negated to turn it into a predicate about whether the tree can see (and be seen from) outside." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "vscode": { + "languageId": "racket" + } + }, + "outputs": [], + "source": [ + "(define (can-see-to-outside? p height dr dc h)\n", + " (match-define (posn r c) p)\n", + " (not (for/first ([n (in-naturals 1)]\n", + " #:do [(define p* (posn (+ r (* n dr)) (+ c (* n dc))))]\n", + " #:break (not (hash-has-key? h p*))\n", + " #:when (<= height (hash-ref h p*)))\n", + " #true)))" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we need a function to check the four cardinal directions and see if at least one of them is unblocked." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "vscode": { + "languageId": "racket" + } + }, + "outputs": [], + "source": [ + "(define (visible-in-any-direction? p height h)\n", + " (for*/or ([dr (in-list '(-1 0 1))] [dc (in-list '(-1 0 1))] #:when (= 1 (abs (+ dr dc))))\n", + " (can-see-to-outside? p height dr dc h)))" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, add 1 to the count for every tree that's visible:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "vscode": { + "languageId": "racket" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "<code>1733</code>" + ], + "text/plain": [ + "1733" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(define (count-visible-trees trees)\n", + " (for/sum ([(tree-posn height) (in-hash trees)])\n", + " (cond\n", + " [(visible-in-any-direction? tree-posn height trees) 1]\n", + " [else 0])))\n", + "\n", + "(count-visible-trees tree-grid)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Part 2\n", + "\n", + "Now we're searching for the tree with the most interior visibility, which is the product of how far we can look in each cardinal direction before running out of visible trees. \n", + "\n", + "The search is similar, except now we're calculating a score instead of just checking a predicate. We walk one step at a time, increment the counter and check each tree; if we find a blocking tree, we return the counter, and if we run out of trees to check we break and return the previous counter value. If there aren't any trees in a direction and the `for` body is never evaluated, `for/last` returns `#false`, which is equivalent to a visibility of 0." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "vscode": { + "languageId": "racket" + } + }, + "outputs": [], + "source": [ + "(define (scenic-score-in-direction p height dr dc h)\n", + " (match-define (posn r c) p)\n", + " (define score\n", + " (for/last ([n (in-naturals 1)]\n", + " #:do [(define p* (posn (+ r (* n dr)) (+ c (* n dc))))]\n", + " #:break (not (hash-has-key? h p*))\n", + " #:final (<= height (hash-ref h p*)))\n", + " n))\n", + " (if (not score) 0 score))\n", + "\n", + "(define (scenic-score p height h)\n", + " (for*/product ([dr (in-list '(-1 0 1))] [dc (in-list '(-1 0 1))] #:when (= 1 (abs (+ dr dc))))\n", + " (scenic-score-in-direction p height dr dc h)))" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With these functions written, calculate each tree's score and find the maximum:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "vscode": { + "languageId": "racket" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "<code>284648</code>" + ], + "text/plain": [ + "284648" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(define (find-best-scenic-score trees)\n", + " (apply max\n", + " (for/list ([(tree-posn height) (in-hash trees)])\n", + " (scenic-score tree-posn height trees))))\n", + "\n", + "(find-best-scenic-score tree-grid)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Racket (Trusted)", + "language": "racket", + "name": "racket-trusted" + }, + "language_info": { + "codemirror_mode": "scheme", + "file_extension": ".rkt", + "mimetype": "text/x-racket", + "name": "Racket", + "pygments_lexer": "racket", + "version": "8.7" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/racket/aoc2022/day-08/day-08.rkt b/racket/aoc2022/day-08/day-08.rkt new file mode 100644 index 0000000..6b60eca --- /dev/null +++ b/racket/aoc2022/day-08/day-08.rkt @@ -0,0 +1,56 @@ +#lang racket + +(require advent-of-code) + +(struct posn (r c) #:transparent) + +(define (make-tree-grid data) + (for*/hash ([(row r) (in-indexed data)] [(col c) (in-indexed (in-string row))]) + (values (posn r c) (string->number (string col))))) + +(define tree-grid (make-tree-grid (in-lines (open-aoc-input (find-session) 2022 8)))) + +;; part 1 + +(define (can-see-to-outside? p height dr dc h) + (match-define (posn r c) p) + (not (for/first ([n (in-naturals 1)] + #:do [(define p* (posn (+ r (* n dr)) (+ c (* n dc))))] + #:break (not (hash-has-key? h p*)) + #:when (<= height (hash-ref h p*))) + #true))) + +(define (visible-in-any-direction? p height h) + (for*/or ([dr (in-list '(-1 0 1))] [dc (in-list '(-1 0 1))] #:when (= 1 (abs (+ dr dc)))) + (can-see-to-outside? p height dr dc h))) + +(define (count-visible-trees trees) + (for/sum ([(tree-posn height) (in-hash trees)]) + (cond + [(visible-in-any-direction? tree-posn height trees) 1] + [else 0]))) + +(count-visible-trees tree-grid) + +;; part 2 + +(define (scenic-score-in-direction p height dr dc h) + (match-define (posn r c) p) + (define score + (for/last ([n (in-naturals 1)] + #:do [(define p* (posn (+ r (* n dr)) (+ c (* n dc))))] + #:break (not (hash-has-key? h p*)) + #:final (<= height (hash-ref h p*))) + n)) + (if (not score) 0 score)) + +(define (scenic-score p height h) + (for*/product ([dr (in-list '(-1 0 1))] [dc (in-list '(-1 0 1))] #:when (= 1 (abs (+ dr dc)))) + (scenic-score-in-direction p height dr dc h))) + +(define (find-best-scenic-score trees) + (apply max + (for/list ([(tree-posn height) (in-hash trees)]) + (scenic-score tree-posn height trees)))) + +(find-best-scenic-score tree-grid) diff --git a/racket/aoc2022/day-09/day-09.rkt b/racket/aoc2022/day-09/day-09.rkt new file mode 100644 index 0000000..0390d2e --- /dev/null +++ b/racket/aoc2022/day-09/day-09.rkt @@ -0,0 +1,76 @@ +#lang racket +(require advent-of-code + threading) + +(struct cmd (dir amt)) +(struct posn (x y) #:transparent) + +(define moves + (~> (fetch-aoc-input (find-session) 2022 9) + (string-split "\n") + (map (λ~> (string-split _) + (match _ + [(list dir amt) (cmd (string->symbol dir) (string->number amt))])) + _))) + +(define (move-head p dir) + (match-define (posn x y) p) + (match dir + ['U (posn x (add1 y))] + ['D (posn x (sub1 y))] + ['R (posn (add1 x) y)] + ['L (posn (sub1 x) y)])) + +(define (avg n m) + (/ (+ n m) 2)) + +(define (manhattan-distance p1 p2) + (match-define (posn x1 y1) p1) + (match-define (posn x2 y2) p2) + (+ (abs (- x2 x1)) (abs (- y2 y1)))) + +(define (follow-head head tail) + (match-define (posn hx hy) head) + (match-define (posn tx ty) tail) + + (case (manhattan-distance head tail) + [(0 1) tail] + [(2 4) + (cond + [(and (= 1 (abs (- hx tx)) (abs (- hy ty)))) tail] + [else (posn (avg hx tx) (avg hy ty))])] + [(3) + (cond + [(= 2 (abs (- hx tx))) (posn (avg hx tx) hy)] + [(= 2 (abs (- hy ty))) (posn hx (avg hy ty))])])) + +;; part 1 +(for*/fold ([head (posn 0 0)] [tail (posn 0 0)] [tail-posns (set)] #:result (set-count tail-posns)) + ([move (in-list moves)] #:do [(match-define (cmd dir amt) move)] [_ (in-range amt)]) + (define new-head (move-head head dir)) + (define new-tail (follow-head new-head tail)) + (values new-head new-tail (set-add tail-posns new-tail))) + +;; part 2 +(for*/fold ([knots (make-list 10 (posn 0 0))] [tail-posns (set)] #:result (set-count tail-posns)) + ([move (in-list moves)] #:do [(match-define (cmd dir amt) move)] [_ (in-range amt)]) + (define updated-knots + (for/fold ([knots-list (list (move-head (first knots) dir))]) + ([following-knot (in-list (rest knots))]) + (cons (follow-head (car knots-list) following-knot) knots-list))) + (values (reverse updated-knots) (set-add tail-posns (first updated-knots)))) + +;; refactor: part 1 and 2 combined +(define (follow-tail move-list rope-length) + (for*/fold ([knots (make-list rope-length (posn 0 0))] + [tail-posns (set)] + #:result (set-count tail-posns)) + ([move (in-list move-list)] #:do [(match-define (cmd dir amt) move)] [_ (in-range amt)]) + (define updated-knots + (for/fold ([knots-list (list (move-head (first knots) dir))]) + ([following-knot (in-list (rest knots))]) + (cons (follow-head (car knots-list) following-knot) knots-list))) + (values (reverse updated-knots) (set-add tail-posns (first updated-knots))))) + +(time (follow-tail moves 2)) +(time (follow-tail moves 10)) diff --git a/racket/aoc2022/day-10/day-10.rkt b/racket/aoc2022/day-10/day-10.rkt new file mode 100644 index 0000000..70c80d3 --- /dev/null +++ b/racket/aoc2022/day-10/day-10.rkt @@ -0,0 +1,43 @@ +#lang racket + +(require advent-of-code + threading + fancy-app + (only-in algorithms chunks-of)) + +(define/match (process-instruction _) + [((list "noop")) (list 'noop)] + [((list "addx" (app string->number val))) (list 'noop (cons 'addx val))]) + +(define instructions + (~> (fetch-aoc-input (find-session) 2022 10) + (string-split "\n") + (map (λ~> string-split process-instruction) _) + (apply append _))) + +;; part 1 +(define interesting-times (inclusive-range 20 220 40)) + +(define/match (evaluate-instruction _op acc) + [('noop _) acc] + [((cons 'addx n) _) (+ acc n)]) + +(for/fold ([acc 1] [interesting-strengths 0] #:result interesting-strengths) + ([inst (in-list instructions)] [i (in-naturals 1)]) + (define new-interesting + (if (member i interesting-times) (+ interesting-strengths (* i acc)) interesting-strengths)) + (values (evaluate-instruction inst acc) new-interesting)) + +;; part 2 +(for/fold ([acc 1] [pixels '()] #:result (~> pixels reverse (chunks-of 40) (map (apply string _) _))) + ([inst (in-list instructions)] [i (in-naturals)]) + (define new-pixel (if (member (modulo i 40) (list (sub1 acc) acc (add1 acc))) #\█ #\space)) + (values (evaluate-instruction inst acc) (cons new-pixel pixels))) + +; for my data set, +; '("███ ████ ████ █ █ ████ ████ █ █ ██ " +; "█ █ █ █ █ █ █ █ █ █ █ █ " +; "█ █ █ ███ ██ ███ ███ ████ █ █ " +; "███ █ █ █ █ █ █ █ █ ████ " +; "█ █ █ █ █ █ █ █ █ █ █ █ " +; "█ █ ████ ████ █ █ ████ █ █ █ █ █ ") diff --git a/racket/aoc2022/day-11/day-11.rkt b/racket/aoc2022/day-11/day-11.rkt new file mode 100644 index 0000000..af7b4ee --- /dev/null +++ b/racket/aoc2022/day-11/day-11.rkt @@ -0,0 +1,85 @@ +#lang racket +(require threading) + +(struct monkey ([items #:mutable] operation modulus yes no)) + +;; really don't feel like parsing the input today +(define monkeys-list + (list (cons 0 (monkey '(97 81 57 57 91 61) + (λ (n) (* n 7)) + 11 5 6)) + (cons 1 (monkey '(88 62 68 90) + (λ (n) (* n 17)) + 19 4 2)) + (cons 2 (monkey '(74 87) + (λ (n) (+ n 2)) + 5 7 4)) + (cons 3 (monkey '(53 81 60 87 90 99 75) + (λ (n) (+ n 1)) + 2 2 1)) + (cons 4 (monkey '(57) + (λ (n) (+ n 6)) + 13 7 0)) + (cons 5 (monkey '(54 84 91 55 59 72 75 70) + (λ (n) (* n n)) + 7 6 3)) + (cons 6 (monkey '(95 79 79 68 78) + (λ (n) (+ n 3)) + 3 1 3)) + (cons 7 (monkey '(61 97 67) + (λ (n) (+ n 4)) + 17 0 5)))) + +(define monkey-lcm (~> monkeys-list (map (λ~> cdr monkey-modulus) _) (apply lcm _))) + +;; part 1 +(define monkeys-count-pt1 (make-hash)) +(define monkeys-hash-pt1 (make-hash monkeys-list)) + +(for + ([rnd (in-range 20)]) + (for + ([turn (inclusive-range 0 7)]) + (match-define (monkey items op modulus yes no) (hash-ref monkeys-hash-pt1 turn)) + (for ([i (in-list items)]) + (define i* (quotient (op i) 3)) + (define m (if (= 0 (modulo i* modulus)) yes no)) + (match-define (monkey items* op* modulus* yes* no*) (hash-ref monkeys-hash-pt1 m)) + (hash-update! monkeys-count-pt1 turn add1 0) + (hash-set! monkeys-hash-pt1 + m + (monkey (append items* (list i*)) op* modulus* yes* no*))) + (hash-set! monkeys-hash-pt1 turn (monkey '() op modulus yes no)))) + +(~> monkeys-count-pt1 + hash->list + (sort _ > #:key cdr) + (take _ 2) + (map cdr _) + (apply * _)) + +;; part 2 +(define monkeys-count-pt2 (make-hash)) +(define monkeys-hash-pt2 (make-hash monkeys-list)) + +(for + ([rnd (in-range 10000)]) + (for + ([turn (inclusive-range 0 7)]) + (match-define (monkey items op modulus yes no) (hash-ref monkeys-hash-pt2 turn)) + (for ([i (in-list items)]) + (define i* (op i)) + (define m (if (= 0 (modulo i* modulus)) yes no)) + (match-define (monkey items* op* modulus* yes* no*) (hash-ref monkeys-hash-pt2 m)) + (hash-update! monkeys-count-pt2 turn add1 0) + (hash-set! monkeys-hash-pt2 + m + (monkey (append items* (list (remainder i* monkey-lcm))) op* modulus* yes* no*))) + (hash-set! monkeys-hash-pt2 turn (monkey '() op modulus yes no)))) + +(~> monkeys-count-pt2 + hash->list + (sort _ > #:key cdr) + (take _ 2) + (map cdr _) + (apply * _))
\ No newline at end of file diff --git a/racket/aoc2022/day-12/day-12.rkt b/racket/aoc2022/day-12/day-12.rkt new file mode 100644 index 0000000..c3f01ac --- /dev/null +++ b/racket/aoc2022/day-12/day-12.rkt @@ -0,0 +1,46 @@ +#lang racket + +(require advent-of-code + graph) + +(define raw-terrain (fetch-aoc-input (find-session) 2022 12 #:cache #true)) +(define special-points (make-hash)) + +(define terrain-mesh + (for*/hash ([(row x) (in-indexed (string-split raw-terrain))] [(col y) (in-indexed row)]) + (define p (cons x y)) + (case col + [(#\S) + (hash-set! special-points 'start p) + (values p 0)] + [(#\E) + (hash-set! special-points 'end p) + (values p 25)] + [else (values p (- (char->integer col) (char->integer #\a)))]))) + +(define (neighbors p) + (match-define (cons x y) p) + (for*/list ([dx (in-list '(-1 0 1))] + [dy (in-list '(-1 0 1))] + #:when (= 1 (abs (+ dx dy))) + #:do [(define p* (cons (+ x dx) (+ y dy)))] + #:when (hash-has-key? terrain-mesh p*)) + p*)) + +(define paths + (directed-graph (for*/list ([p (in-list (hash-keys terrain-mesh))] + [p* (in-list (neighbors p))] + #:unless (> (sub1 (hash-ref terrain-mesh p*)) + (hash-ref terrain-mesh p))) + (list p p*)))) + +;; part 1 +(time (match-define-values (distances _) (bfs paths (hash-ref special-points 'start))) + (hash-ref distances (hash-ref special-points 'end))) + +;; part 2 +(time (for/lists + (lengths #:result (apply min lengths)) + ([start (in-list (hash-keys terrain-mesh))] #:when (= 0 (hash-ref terrain-mesh start))) + (match-define-values (distances _) (bfs paths start)) + (hash-ref distances (hash-ref special-points 'end)))) diff --git a/racket/aoc2022/day-13/day-13.rkt b/racket/aoc2022/day-13/day-13.rkt new file mode 100644 index 0000000..39435e9 --- /dev/null +++ b/racket/aoc2022/day-13/day-13.rkt @@ -0,0 +1,28 @@ +#lang racket + +(require advent-of-code) + +(define raw-packets + (parameterize ([current-readtable (make-readtable #f #\, #\space #f)]) + (port->list read (open-aoc-input (find-session) 2022 13 #:cache #true)))) + +(define (compare xs ys) + (match* (xs ys) + [('() (list* _)) #true] + [((list* _) '()) #false] + [((list* _same x-rest) (list* _same y-rest)) (compare x-rest y-rest)] + [((list* (? integer? x) _) (list* (? integer? y) _)) (< x y)] + [((list* (? list? xs*) _) (list* (? list? ys*) _)) (compare xs* ys*)] + [(xs (list* (? integer? y) y-rest)) (compare xs (cons (list y) y-rest))] + [((list* (? integer? x) x-rest) ys) (compare (cons (list x) x-rest) ys)])) + +;; part 1 +(for/sum ([i (in-naturals 1)] [packet (in-slice 2 raw-packets)] #:when (apply compare packet)) i) + +;; part 2 +(define divider-packets (list '((2)) '((6)))) +(define amended-packets (append divider-packets raw-packets)) + +(for/product ([i (in-naturals 1)] [packet (in-list (sort amended-packets compare))] + #:when (member packet divider-packets)) + i) diff --git a/racket/aoc2022/day-14/day-14.rkt b/racket/aoc2022/day-14/day-14.rkt new file mode 100644 index 0000000..88ba297 --- /dev/null +++ b/racket/aoc2022/day-14/day-14.rkt @@ -0,0 +1,51 @@ +#lang racket + +(require advent-of-code + threading + algorithms) + +(define data (fetch-aoc-input (find-session) 2022 14 #:cache #true)) + +(define (trace-line-between-points p1 p2) + (match* (p1 p2) + [((list x y1) (list x y2)) (map (λ (y) (cons x y)) (inclusive-range (min y1 y2) (max y1 y2)))] + [((list x1 y) (list x2 y)) (map (λ (x) (cons x y)) (inclusive-range (min x1 x2) (max x1 x2)))])) + +(define (find-points-in-structure str) + (define endpoints + (for/list ([coord-pair (in-list (string-split str " -> "))]) + (for/list ([coord (in-list (string-split coord-pair ","))]) + (string->number coord)))) + (~>> endpoints (adjacent-map trace-line-between-points) (apply append) (list->set))) + +(define blocked-locations + (~> data (string-split "\n") (map find-points-in-structure _) (apply set-union _))) + +(define max-vertical-distance (~>> blocked-locations (set->list) (argmax cdr) cdr add1)) + +(define (open? pts p) + (not (set-member? pts p))) + +;; part 1 +(define (trace-grain pts path #:at-limit do-at-limit) + (match-define (list* (and p (cons x y)) _) path) + (match-define (list dest-1 dest-2 dest-3) (map (λ (d) (cons (+ x d) (add1 y))) '(0 -1 1))) + (cond + [(>= y max-vertical-distance) (values (do-at-limit pts p) path)] + [(open? pts dest-1) (trace-grain pts (cons dest-1 path) #:at-limit do-at-limit)] + [(open? pts dest-2) (trace-grain pts (cons dest-2 path) #:at-limit do-at-limit)] + [(open? pts dest-3) (trace-grain pts (cons dest-3 path) #:at-limit do-at-limit)] + [else (values (set-add pts (car path)) path)])) + +(time (for/fold ([pts blocked-locations] [path (list (cons 500 0))] [grains 0] #:result grains) + ([_ (in-naturals 1)]) + (define-values (pts* path*) (trace-grain pts path #:at-limit (const 'break))) + #:break (equal? pts* 'break) + (values pts* (cdr path*) (add1 grains)))) + +;; part 2 +(time (for/fold ([pts blocked-locations] [path (list (cons 500 0))] [grains 0] #:result grains) + ([_ (in-naturals 1)]) + #:break (not (open? pts (cons 500 0))) + (define-values (pts* path*) (trace-grain pts path #:at-limit set-add)) + (values pts* (cdr path*) (add1 grains)))) diff --git a/racket/aoc2022/day-15/day-15.rkt b/racket/aoc2022/day-15/day-15.rkt new file mode 100644 index 0000000..b050807 --- /dev/null +++ b/racket/aoc2022/day-15/day-15.rkt @@ -0,0 +1,54 @@ +#lang racket + +(require advent-of-code + threading + fancy-app + algorithms + (prefix-in iset- data/integer-set)) + +(struct beacon-record (sensor beacon)) +(struct posn (x y)) + +(define beacon-records + (~> (fetch-aoc-input (find-session) 2022 15 #:cache #true) + (string-split "\n") + (map (λ~> (string-replace #px"[^\\d\\s-]" "") + string-split + (map string->number _) + (chunks-of 2) + (map (apply posn _) _) + (apply beacon-record _)) + _))) + +(define (manhattan-distance-to-beacon record) + (match-define (beacon-record (posn sx sy) (posn bx by)) record) + (+ (abs (- sx bx)) (abs (- sy by)))) + +(define (coverage-at-row record row) + (match-define (beacon-record (posn sx sy) _) record) + (define x-distance (- (manhattan-distance-to-beacon record) (abs (- row sy)))) + (cond + [(negative? x-distance) (iset-make-range)] + [else (iset-make-range (- sx x-distance) (+ sx x-distance))])) + +(define (total-coverage-at-row records row) + (for/fold ([coverage (iset-make-range)]) ([r (in-list records)]) + (iset-union coverage (coverage-at-row r row)))) + +;; part 1 +(define (coverage-without-beacons records row) + (~> (total-coverage-at-row records row) + iset-count + (- (count (λ (b) (= row (posn-y b))) + (~> beacon-records (map beacon-record-beacon _) remove-duplicates))))) + +(coverage-without-beacons beacon-records 2000000) + +;; part 2 +(define (find-only-beacon beacon-records size) + (for/first ([y (in-range 0 size)] + #:do [(define xs (iset-complement (total-coverage-at-row beacon-records y) 0 size))] + #:when (= 1 (iset-count xs))) + (+ (* 4000000 (iset-get-integer xs)) y))) + +(find-only-beacon beacon-records 4000000) diff --git a/racket/aoc2022/day-16/day-16.rkt b/racket/aoc2022/day-16/day-16.rkt new file mode 100644 index 0000000..5ec56d6 --- /dev/null +++ b/racket/aoc2022/day-16/day-16.rkt @@ -0,0 +1,107 @@ +#lang racket + +(require advent-of-code + fancy-app + graph + threading) + +(define (process-line str) + (match str + [(regexp #px"Valve (\\w\\w) has flow rate=(\\d+); tunnels? leads? to valves? (.+)" + (list _ name (app string->number rate) (app (string-split _ ", ") valves))) + (list name rate valves)])) + +(define cave-data + (~> (fetch-aoc-input (find-session) 2022 16 #:cache #true) + (string-split "\n") + (map process-line _))) + +(define cave-map + (for*/lists (tunnels #:result (directed-graph tunnels)) + ([valve (in-list cave-data)] #:do [(match-define (list name _ valves) valve)] + [destination (in-list valves)]) + (list name destination))) + +(define valve-flows + (for/hash ([valve (in-list cave-data)] + #:do [(match-define (list name flow _) valve)] + #:when (> flow 0)) + (values name flow))) + +(define shortest-path-lengths (johnson cave-map)) + +(define (reachable-destinations start dests minutes-left) + (for/list ([(dest _) (in-hash dests)] + #:do [(define travel-time + (hash-ref shortest-path-lengths (list start dest) minutes-left))] + #:when (<= 1 travel-time minutes-left)) + (cons dest travel-time))) + +;; part 1 +(define (find-best-single-route start + [minutes-left 30] + [current-pressure 0] + [available-valves valve-flows]) + (cond + [(>= minutes-left 1) + (for/fold ([running-pressure current-pressure] + [remaining-valves available-valves] + #:result (cons running-pressure remaining-valves)) + ([candidate (reachable-destinations start available-valves minutes-left)]) + (match-define (cons dest travel-time) candidate) + (define minutes-left* (- minutes-left (add1 travel-time))) + (match-define (cons candidate-pressure remaining-valves*) + (find-best-single-route dest + minutes-left* + (+ current-pressure (* (hash-ref valve-flows dest) minutes-left*)) + (hash-remove available-valves dest))) + (if (> candidate-pressure running-pressure) + (values candidate-pressure remaining-valves*) + (values running-pressure remaining-valves*)))] + [else (cons current-pressure available-valves)])) + +(car (find-best-single-route "AA")) + +;; part 2 + +(define (possible-paths start dests minutes-left) + (cond + [(or (hash-empty? dests) (< minutes-left 3)) '()] + [else + (for/fold ([path '()]) ([dest (in-list (reachable-destinations start dests minutes-left))]) + (match-define (cons valve minutes) dest) + (define dests* (hash-remove dests valve)) + (define next-valves (possible-paths valve dests* (- minutes-left minutes))) + (append (list (list dest)) (map (cons dest _) next-valves) path))])) + +(define (flow-for-path path minutes [sum 0]) + (match path + ['() sum] + [(list* (cons valve dist) tail) + (define valve-open-for (- minutes dist 1)) + (flow-for-path tail valve-open-for (+ sum (* (hash-ref valve-flows valve) valve-open-for)))])) + +(define minutes-left 26) + +(define human-paths + (~>> (possible-paths "AA" valve-flows minutes-left) + (map (λ (path) (cons (flow-for-path path minutes-left) (map car path)))) + (sort _ > #:key car))) + +(define (best-possible-elephant-path human-path) + (define remaining-dests + (for/hash ([(dest flow) (in-hash valve-flows)] #:unless (member dest (cdr human-path))) + (values dest flow))) + (~>> (possible-paths "AA" remaining-dests minutes-left) + (map (λ (path) (cons (flow-for-path path minutes-left) (map car path)))) + (sort _ > #:key car) + car)) + +;; this takes a long time to run but I stuck a displayln in for debugging +;; and just took the highest max-flow after letting it run for a while and waiting until +;; it stopped printing new bests to console +(for*/fold ([max-flow 0]) + ([human-path (in-list human-paths)] + #:do [(define elephant-path (best-possible-elephant-path human-path))]) + (define combined-flow (+ (car human-path) (car elephant-path))) + (if (< max-flow combined-flow) combined-flow max-flow)) diff --git a/racket/aoc2022/day-17/day-17.rkt b/racket/aoc2022/day-17/day-17.rkt new file mode 100644 index 0000000..28e8763 --- /dev/null +++ b/racket/aoc2022/day-17/day-17.rkt @@ -0,0 +1,53 @@ +#lang racket + +(require advent-of-code + threading + fancy-app + data/collection) + +(define (relative-rock-coordinates rock) + (for*/hash ([(row y) (in-indexed (reverse rock))] [(_col x) (in-indexed row)]) + (values (cons x y) #true))) + +(define rock-shapes + (~> (open-input-file "./2022/day-17/rock-shapes") + port->string + (string-split "\n\n") + (map (λ~> (string-split _ "\n") (map string->list _) relative-rock-coordinates) _))) + +(define gusts + (~> (fetch-aoc-input (find-session) 2022 17 #:cache #true) + string-trim + string->list + (map (match-lambda + [#\> 1] + [#\< -1]) + _))) + +(define (place-new-rock rock elevation) + (for/hash ([(posn _) (in-hash rock)]) + (match-define (cons x y) posn) + (values (cons (+ x 3) (+ y elevation 4)) #true))) + +(define (gust-move rock wind cave) + (define moved-rock + (for/hash ([(posn _) (in-hash rock)]) + (match-define (cons x y) posn) + (define posn* (cons (+ x wind) y)) + (if (hash-has-key? cave posn) (values 'collision #t) (values posn* #t)))) + (if (hash-has-key? moved-rock 'collision) rock moved-rock)) + +(for/fold ([cave (hash)] + [falling-rock #f] + [top-of-rocks 0] + [rock-cycle (cycle rock-shapes)] + [rock-number 0] + #:result top-of-rocks) + (#:break (> rock-number 2022) [gust (in-cycle gusts)]) + (cond + [(not falling-rock) + (values cave + (~> (first rock-cycle) (place-new-rock top-of-rocks) (gust-move gust cave)) + top-of-rocks + (rest rock-cycle) + (add1 rock-number))])) diff --git a/racket/aoc2022/day-17/rock-shapes b/racket/aoc2022/day-17/rock-shapes new file mode 100644 index 0000000..fbcc382 --- /dev/null +++ b/racket/aoc2022/day-17/rock-shapes @@ -0,0 +1,17 @@ +#### + +.#. +### +.#. + +..# +..# +### + +# +# +# +# + +## +##
\ No newline at end of file diff --git a/racket/aoc2022/day-18/day-18.rkt b/racket/aoc2022/day-18/day-18.rkt new file mode 100644 index 0000000..157784d --- /dev/null +++ b/racket/aoc2022/day-18/day-18.rkt @@ -0,0 +1,57 @@ +#lang racket + +(require advent-of-code + relation + threading + graph) + +(define positions (~> (fetch-aoc-input (find-session) 2022 18 #:cache #true) (string-split "\n"))) + +(struct posn (x y z) #:transparent) + +(define cubes + (for/list ([cube (in-list positions)]) + (match (string-split cube ",") + [(list (app ->number x) (app ->number y) (app ->number z)) (posn x y z)]))) + +(define cubes-set (list->set cubes)) + +(define (neighbors p) + (match-define (posn x y z) p) + (for*/list ([dx '(-1 0 1)] + [dy '(-1 0 1)] + [dz '(-1 0 1)] + #:when (= 1 (+ (abs dx) (abs dy) (abs dz)))) + (posn (+ x dx) (+ y dy) (+ z dz)))) + +;; part 1 + +(for*/sum ([cube (in-set cubes-set)] + [neighbor (in-list (neighbors cube))] + #:unless (set-member? cubes-set neighbor)) + 1) + +;; part 2 +(define max-x (~> cubes (apply max _ #:key posn-x) posn-x (+ 2))) +(define max-y (~> cubes (apply max _ #:key posn-y) posn-y (+ 2))) +(define max-z (~> cubes (apply max _ #:key posn-z) posn-z (+ 2))) + +(define air-set + (for*/set ([x (in-inclusive-range -1 max-x)] + [y (in-inclusive-range -1 max-y)] + [z (in-inclusive-range -1 max-z)] + #:do [(define p (posn x y z))] + #:unless (set-member? cubes-set p)) + p)) + +(define air-graph + (for*/lists (ps #:result (undirected-graph ps)) + ([a (in-set air-set)] + [neighbor (in-list (neighbors a))] + #:when (set-member? air-set neighbor)) + (list a neighbor))) + +(for*/sum ([air (in-set (~> air-graph cc (sort > #:key length _) car))] + [neighbor (in-list (neighbors air))] + #:when (set-member? cubes-set neighbor)) + 1) diff --git a/racket/aoc2022/day-19/day-19.rkt b/racket/aoc2022/day-19/day-19.rkt new file mode 100644 index 0000000..1400bf2 --- /dev/null +++ b/racket/aoc2022/day-19/day-19.rkt @@ -0,0 +1,11 @@ +#lang racket + +(require advent-of-code + threading) + +(struct blueprint (id ore clay obsidian geode)) + +(define (parse-line str) + (match (~> str (string-replace #px"[^\\d\\s]" "") string-split) + [(list id ore clay obsidian-ore obsidian-clay geode-ore geode-obsidian) + (blueprint id ore clay (cons obsidian-ore obsidian-clay) (cons geode-ore geode-obsidian))])) diff --git a/racket/aoc2022/day-20/day-20.rkt b/racket/aoc2022/day-20/day-20.rkt new file mode 100644 index 0000000..6dd1070 --- /dev/null +++ b/racket/aoc2022/day-20/day-20.rkt @@ -0,0 +1,48 @@ +#lang racket +(require advent-of-code + threading) + +(define data (port->list read (open-aoc-input (find-session) 2022 20 #:cache #true))) + +(define gps-lst data) +(define gps-len (length gps-lst)) +(define gps-indexed (map cons (inclusive-range 1 gps-len) gps-lst)) + +(define (mix pt data) + (match-define (list left ... (== pt) right ...) data) + (define start (index-of data pt)) + (define move-by (modulo (cdr pt) (sub1 gps-len))) + (cond + [(= 0 move-by) data] + [(<= move-by (length right)) + (match-define-values (new-left new-right) + (split-at (append left right) (modulo (+ move-by start) (sub1 gps-len)))) + (append new-left (list pt) new-right)] + [else + (match-define-values (new-left new-right) + (split-at (append left right) (modulo (+ move-by start) (sub1 gps-len)))) + (append new-left (list pt) new-right)])) + +(define (mix-gps data original) + (for/fold ([pts data]) ([pt original]) + (mix pt pts))) + +(define (cycle-mixed-gps mixed) + (define lst (map cdr mixed)) + (in-sequences (drop lst (index-of lst 0)) (in-cycle lst))) + +(define (calculate-answer seq) + (for/sum ([id '(1000 2000 3000)]) (sequence-ref seq id))) + +;; part 1 +(~> gps-indexed (mix-gps _ gps-indexed) cycle-mixed-gps calculate-answer) + +;; part 2 +(define encrypted-gps-indexed + (for/list ([pt (in-list gps-indexed)] #:do [(match-define (cons i v) pt)]) + (cons i (* 811589153 v)))) + +(~>> encrypted-gps-indexed + ((λ (data) (foldr (λ (_ pts) (mix-gps pts data)) data (range 10)))) + cycle-mixed-gps + calculate-answer) diff --git a/racket/aoc2022/day-21/day-21.rkt b/racket/aoc2022/day-21/day-21.rkt new file mode 100644 index 0000000..fccd6ad --- /dev/null +++ b/racket/aoc2022/day-21/day-21.rkt @@ -0,0 +1,43 @@ +#lang racket + +(require advent-of-code + (only-in relation ->number ->symbol)) + +(struct monkey (name op) #:transparent) +(struct op (f first second) #:transparent) + +(define (parse-monkey str) + (match (string-split str " ") + [(list (app (curryr string-trim ":") name) name1 (app ->symbol f) name2) + (monkey name (op f name1 name2))] + [(list (app (curryr string-trim ":") name) (app ->number int)) + (monkey name (op 'constant int #f))])) + +(define raw-monkeys (port->lines (open-aoc-input (find-session) 2022 21 #:cache #true))) + +(define monkey-table + (for/hash ([m raw-monkeys] #:do [(match-define (monkey name op) (parse-monkey m))]) + (values name op))) + +;; part 1 +(define (evaluate-monkey m-name [guess #f]) + (match-define (op f name1 name2) + (if (and guess (equal? m-name "humn")) (op 'constant guess #f) (hash-ref monkey-table m-name))) + (match f + ['constant name1] + ['+ (+ (evaluate-monkey name1 guess) (evaluate-monkey name2 guess))] + ['- (- (evaluate-monkey name1 guess) (evaluate-monkey name2 guess))] + ['* (* (evaluate-monkey name1 guess) (evaluate-monkey name2 guess))] + ['/ (/ (evaluate-monkey name1 guess) (evaluate-monkey name2 guess))])) + +(evaluate-monkey "root") + +;; part 2 +;; since humn only ever appears once, and it's never the divisor in a division operation, +;; the difference of the branches is linearly proportional to humn +;; therefore, if we find two points we can calculate the root directly +(match-define (op _ branch-1 branch-2) (hash-ref monkey-table "root")) +(define known-side (evaluate-monkey branch-2)) +(define humn-zero (- known-side (evaluate-monkey branch-1 0))) +(define humn-one (- known-side (evaluate-monkey branch-1 1))) +(- (/ humn-zero (- humn-one humn-zero))) diff --git a/racket/aoc2022/day-22/day-22.rkt b/racket/aoc2022/day-22/day-22.rkt new file mode 100644 index 0000000..bcce5f8 --- /dev/null +++ b/racket/aoc2022/day-22/day-22.rkt @@ -0,0 +1,35 @@ +#lang racket + +(require advent-of-code + threading) + +(struct posn (x y) #:transparent) + +(match-define (list raw-map raw-instructions) + (string-split (fetch-aoc-input (find-session) 2022 22 #:cache #true) "\n\n")) + +(define board-map + (for*/hash ([(row y) (in-indexed (string-split raw-map "\n"))] + [(col x) (in-indexed row)] + #:unless (equal? col #\space)) + (define tile + (match col + [#\. 'open] + [#\# 'wall])) + (values (posn x y) tile))) + +(define instructions + (~>> raw-instructions + (regexp-match* #px"(\\d+)|R|L") + (map (match-lambda + [(? string->number n) (string->number n)] + ["R" 'clockwise] + ["L" 'counterclockwise])))) + +(define start-tile + (~>> board-map + hash-keys + (filter (match-lambda + [(posn _ 0) #true] + [_ #false])) + (argmin posn-x)))
\ No newline at end of file diff --git a/racket/aoc2022/day-23/day-23.rkt b/racket/aoc2022/day-23/day-23.rkt new file mode 100644 index 0000000..6069859 --- /dev/null +++ b/racket/aoc2022/day-23/day-23.rkt @@ -0,0 +1,76 @@ +#lang racket + +(require advent-of-code + fancy-app + threading) + +(struct posn (x y) #:transparent) + +(define initial-map + (for*/hash ([(row y) (in-indexed (in-lines (open-aoc-input (find-session) 2022 23 #:cache #true)))] + [(col x) (in-indexed row)] + #:when (equal? col #\#)) + (values (posn x y) #t))) + +(define/match (neighbors-in direction p) + [('north (posn x (app sub1 y*))) (list (posn (sub1 x) y*) (posn x y*) (posn (add1 x) y*))] + [('south (posn x (app add1 y*))) (list (posn (sub1 x) y*) (posn x y*) (posn (add1 x) y*))] + [('east (posn (app add1 x*) y)) (list (posn x* (add1 y)) (posn x* y) (posn x* (sub1 y)))] + [('west (posn (app sub1 x*) y)) (list (posn x* (add1 y)) (posn x* y) (posn x* (sub1 y)))]) + +(define/match (move-to direction p) + [('stay p) p] + [('north (posn x y)) (posn x (sub1 y))] + [('south (posn x y)) (posn x (add1 y))] + [('east (posn x y)) (posn (add1 x) y)] + [('west (posn x y)) (posn (sub1 x) y)]) + +(define (propose-movements elves dirs) + (for/hash ([(elf _) (in-hash elves)]) + (define dir-candidates + (for/list ([dir dirs] + #:do [(define neighbors (neighbors-in dir elf))] + #:unless (ormap (curry hash-has-key? elves) neighbors)) + dir)) + (define chosen-dir + (match dir-candidates + ['() 'stay] + [(== dirs) 'stay] + [(cons dir _) dir])) + (values elf chosen-dir))) + +(define (try-proposed-movements elves) + (define moved-elves (make-hash)) + (for ([(elf dir) (in-hash elves)]) + (hash-update! moved-elves (move-to dir elf) (cons elf _) '())) + (define reconciled-elves (make-hash)) + (for ([(posn elves) (in-hash moved-elves)]) + (match elves + ; if there's only one elf at a coordinate, leave it there + [(list _) (hash-set! reconciled-elves posn #t)] + ; if there's many elves at one coordinate, back them up to their previous spot + [many-elves + (for ([elf (in-list many-elves)]) + (hash-set! reconciled-elves elf #t))])) + reconciled-elves) + +;; part 1 +(define (count-empty-spots elves) + (define elf-posns (hash-keys elves)) + (match-define (list x-min _ ... x-max) (sort (map posn-x elf-posns) <)) + (match-define (list y-min _ ... y-max) (sort (map posn-y elf-posns) <)) + (for*/sum ([y (inclusive-range y-min y-max)] [x (inclusive-range x-min x-max)]) + (if (hash-has-key? elves (posn x y)) 0 1))) + +(for/fold ([elves initial-map] [dirs '(north south west east)] #:result (count-empty-spots elves)) + ([_rnd (in-range 10)]) + (values (~> elves (propose-movements dirs) try-proposed-movements) + (append (cdr dirs) (list (car dirs))))) + +;; part 2 +(for/fold ([elves initial-map] [dirs '(north south west east)] [rnd 1] #:result rnd) + ([_rnd (in-naturals)]) + (define elves-proposed (propose-movements elves dirs)) + ; elves have stopped moving if they all conclude they want to stay put + #:break (~> elves-proposed hash-values remove-duplicates (equal? '(stay))) + (values (try-proposed-movements elves-proposed) (append (cdr dirs) (list (car dirs))) (add1 rnd))) diff --git a/racket/aoc2022/day-25/day-25.rkt b/racket/aoc2022/day-25/day-25.rkt new file mode 100644 index 0000000..078cef4 --- /dev/null +++ b/racket/aoc2022/day-25/day-25.rkt @@ -0,0 +1,24 @@ +#lang racket +(require advent-of-code + threading) + +(define fuel-requirements (port->lines (open-aoc-input (find-session) 2022 25 #:cache #true))) + +(define (snafu->decimal snafu) + (for/sum ([digit (in-list (~> snafu string->list reverse))] [place (in-naturals)]) + (define value + (match digit + [#\= -2] + [#\- -1] + [c (~> c string string->number)])) + (* value (expt 5 place)))) + +(define (decimal->snafu n [acc '()]) + (match n + [0 (list->string acc)] + [n (decimal->snafu (quotient (+ n 2) 5) (cons (string-ref "012=-" (modulo n 5)) acc))])) + +;; part 1 +(~> (for/sum ([fuel (in-list fuel-requirements)]) (snafu->decimal fuel)) decimal->snafu) + +;; no part 2 メリークリスマス
\ No newline at end of file diff --git a/racket/aoc2023/day-01/day-01.rkt b/racket/aoc2023/day-01/day-01.rkt new file mode 100644 index 0000000..b720f79 --- /dev/null +++ b/racket/aoc2023/day-01/day-01.rkt @@ -0,0 +1,38 @@ +#lang racket + +(require advent-of-code + threading) + +(define calibration-values (fetch-aoc-input (find-session) 2023 1)) + +(define (to-number str) + (match (string->number str) + [#false (hash-ref word-to-digit str)] + [n n])) + +(define (parse-calibration-value v valid) + (for/fold ([acc '()] [value v] #:result (+ (to-number (first acc)) (* 10 (to-number (last acc))))) + ([_ (in-naturals)]) + #:break (equal? value "") + (let ([possible-prefix (findf (curry string-prefix? value) valid)]) + (if possible-prefix + (values (cons possible-prefix acc) (substring value 1)) + (values acc (substring value 1)))))) + +(define (total-calibration input valid) + (~> input + (string-trim) + (string-split "\n") + (map (λ~> (parse-calibration-value valid)) _) + (apply + _))) + +;; part 1 + +(define valid-for-part-1 (~> (range 1 10) (map ~a _))) +(total-calibration calibration-values valid-for-part-1) + +;; part 2 +(define word-to-digit + (hash "one" 1 "two" 2 "three" 3 "four" 4 "five" 5 "six" 6 "seven" 7 "eight" 8 "nine" 9)) +(define valid-for-part-2 (append valid-for-part-1 (hash-keys word-to-digit))) +(total-calibration calibration-values valid-for-part-2) diff --git a/racket/aoc2023/day-02/day-02-parser.rkt b/racket/aoc2023/day-02/day-02-parser.rkt new file mode 100644 index 0000000..76cc24f --- /dev/null +++ b/racket/aoc2023/day-02/day-02-parser.rkt @@ -0,0 +1,55 @@ +#lang racket + +(require racket/hash + advent-of-code + data/applicative + data/either + data/monad + megaparsack + megaparsack/text + threading) + +(struct game (id r g b)) + +(define cube/p + (do [n <- integer/p] + space/p + [c <- (or/p (string/p "red") + (string/p "blue") + (string/p "green"))] + (pure (cons c n)))) + +(define draw/p + (do [xs <- (many/p cube/p #:min 1 #:max 3 #:sep (string/p ", "))] + (pure (apply hash (flatten xs))))) + +(define all-draws/p + (do (string/p "Game ") + [id <- integer/p] + (string/p ": ") + [all-draws <- (many/p draw/p #:min 1 #:sep (string/p "; "))] + (define maxima + (foldl (curry hash-union #:combine max) + (hash "red" 0 "green" 0 "blue" 0) + all-draws)) + (pure (game id + (hash-ref maxima "red") + (hash-ref maxima "green") + (hash-ref maxima "blue"))))) + +(define game-maxima + (~>> (open-aoc-input (find-session) 2023 2) + port->lines + (map (λ~>> (parse-string all-draws/p) + from-either)))) + +;; part 1 +(for/sum ([m (in-list game-maxima)] + #:unless (or (> (game-r m) 12) + (> (game-g m) 13) + (> (game-b m) 14))) + (game-id m)) + +;; part 2 +(for/sum ([m (in-list game-maxima)]) + (* (game-r m) (game-g m) (game-b m))) diff --git a/racket/aoc2023/day-02/day-02.rkt b/racket/aoc2023/day-02/day-02.rkt new file mode 100644 index 0000000..973d20c --- /dev/null +++ b/racket/aoc2023/day-02/day-02.rkt @@ -0,0 +1,35 @@ +#lang racket + +(require advent-of-code) + +(struct roll (id red green blue)) + +(define all-games + (for/list ([raw-game (in-list (port->lines (open-aoc-input (find-session) 2023 2)))] + #:do [(define game (string-trim raw-game "Game ")) + (match-define (list id trials) (string-split game ": "))]) + (for/list ([trial (in-list (string-split trials "; "))]) + (for/fold ([acc (roll (string->number id) 0 0 0)]) + ([color (in-list (string-split trial ", "))]) + (match (string-split color) + [(list (app string->number n) "red") (struct-copy roll acc [red n])] + [(list (app string->number n) "green") (struct-copy roll acc [green n])] + [(list (app string->number n) "blue") (struct-copy roll acc [blue n])]))))) + +;; part 1 +(for/sum ([game (in-list all-games)] + #:when (andmap (λ (g) (and ((roll-red g) . <= . 12) + ((roll-green g) . <= . 13) + ((roll-blue g) . <= . 14))) + game)) + (roll-id (first game))) + +;; part 2 +(for/sum ([game (in-list all-games)]) + (define max-cubes + (for/fold ([acc (roll #f 0 0 0)]) ([r (in-list game)]) + (roll #f + (max (roll-red acc) (roll-red r)) + (max (roll-green acc) (roll-green r)) + (max (roll-blue acc) (roll-blue r))))) + (* (roll-red max-cubes) (roll-green max-cubes) (roll-blue max-cubes))) diff --git a/racket/aoc2023/day-03/day-03.rkt b/racket/aoc2023/day-03/day-03.rkt new file mode 100644 index 0000000..60e81a6 --- /dev/null +++ b/racket/aoc2023/day-03/day-03.rkt @@ -0,0 +1,72 @@ +#lang racket + +(require advent-of-code + threading) + +(struct posn (x y) #:transparent) +(struct part (n posns) #:transparent) + +(define (make-board port) + (for*/hash ([(row y) (in-indexed (port->lines port))] + [(col x) (in-indexed (string->list row))] + #:unless (equal? col #\.)) + (define v + (cond + [(string->number (string col))] + [(equal? col #\*) 'gear] + [else 'other])) + (values (posn x y) v))) + +(define board (~> (open-aoc-input (find-session) 2023 3 #:cache #true) make-board)) + +(define (posn<? a b) + (match-define (list (cons (posn a-x a-y) _) (cons (posn b-x b-y) _)) (list a b)) + (if (= a-y b-y) (< a-x b-x) (< a-y b-y))) + +(define (find-cells f b) + (~> (for/hash ([(k v) (in-hash b)] #:when (f v)) + (values k v)) + hash->list + (sort posn<?))) + +(define (group-into-parts cells [acc '()]) + (match* (cells acc) + [('() acc) + acc] + [((list* (cons (and pt (posn x y)) n) cs) + (list* (part n* (and pts (list* (posn x* y) rest-pts))) + rest-acc)) + #:when (= (- x x*) 1) + (group-into-parts cs (cons (part (+ n (* n* 10)) (cons pt pts)) rest-acc))] + [((list* (cons pt n) cs) acc) + (group-into-parts cs (cons (part n (list pt)) acc))])) + +(define (neighbors p) + (for*/list ([dx '(-1 0 1)] + [dy '(-1 0 1)] + #:unless (and (= dx 0) (= dy 0))) + (posn (+ dx (posn-x p)) (+ dy (posn-y p))))) + +(define to-neighbors (λ~>> part-posns (append-map neighbors) remove-duplicates)) +(define (symbol-in-neighbors b pt acc) + (~>> pt + to-neighbors + (ormap (λ (p) (let ([lookup (hash-ref b p #f)]) + (or (equal? lookup 'gear) (equal? lookup 'other))))) + ((λ (bool) (if bool (+ acc (part-n pt)) acc))))) + +;; part 1 +(define parts (~>> board (find-cells integer?) group-into-parts)) +(foldl (curry symbol-in-neighbors board) 0 parts) + +;; part 2 +(define gears (~>> board (find-cells (curry equal? 'gear)) (map car))) +(define parts-with-neighbors (map (λ (pt) (struct-copy part pt [posns (to-neighbors pt)])) parts)) + +(define (find-parts-near-gear pts gear) + (filter-map (λ (pt) (and (findf (curry equal? gear) (part-posns pt)) (part-n pt))) pts)) + +(~>> gears + (filter-map (λ~>> (find-parts-near-gear parts-with-neighbors) + ((λ (ns) (if (= (length ns) 2) (* (first ns) (second ns)) #f))))) + (apply +)) diff --git a/racket/aoc2023/day-04/day-04.rkt b/racket/aoc2023/day-04/day-04.rkt new file mode 100644 index 0000000..7a357c5 --- /dev/null +++ b/racket/aoc2023/day-04/day-04.rkt @@ -0,0 +1,40 @@ +#lang racket + +(require advent-of-code + data/applicative + data/either + data/monad + megaparsack + megaparsack/text + threading) + +(struct card (n wins)) + +(define card/p + (do (string/p "Card") + (many/p space/p) + [n <- integer/p] + (string/p ":") + (many/p space/p) + [winners <- (many-until/p integer/p #:sep (many/p space/p) #:end (try/p (string/p " | ")))] + (many/p space/p) + [has <- (many+/p integer/p #:sep (many/p space/p))] + (pure (card n (set-count (set-intersect (first winners) has)))))) + +(define raw-cards (~> (open-aoc-input (find-session) 2023 4 #:cache #true) port->lines)) + +;; part 1 +(for/sum ([raw-card (in-list raw-cards)] + #:do [(match-define (success (card _ wins)) (parse-string card/p raw-card))] + #:unless (= wins 0)) + (expt 2 (sub1 wins))) + +;; part 2 +(for/fold ([counts (for/hash ([n (in-inclusive-range 1 (length raw-cards))]) + (values n 1))] + #:result (apply + (hash-values counts))) + ([raw-card (in-list raw-cards)] + #:do [(match-define (success (card n wins)) (parse-string card/p raw-card))]) + (define bonus-range (inclusive-range (+ n 1) (+ n wins))) + (define won-cards (hash-ref counts n)) + (foldl (λ (n acc) (hash-update acc n (curry + won-cards))) counts bonus-range)) diff --git a/racket/aoc2023/day-05/day-05.rkt b/racket/aoc2023/day-05/day-05.rkt new file mode 100644 index 0000000..5b9aa52 --- /dev/null +++ b/racket/aoc2023/day-05/day-05.rkt @@ -0,0 +1,91 @@ +#lang racket + +(require advent-of-code + algorithms + threading) + +(struct map-range (start end offset)) +(struct seed-range (start end)) + +(define input (fetch-aoc-input (find-session) 2023 5 #:cache #true)) + +(match-define (list* raw-seeds raw-mappings) (string-split input "\n\n")) + +(define seeds-naive (~> raw-seeds (string-split " ") rest (map string->number _))) +(define mappers + (for/list ([raw-mapping (in-list raw-mappings)]) + (for/lists (map-ranges #:result (sort map-ranges < #:key map-range-start)) + ([raw-map-range (in-list (rest (string-split raw-mapping "\n")))] + #:do [(match-define (list dest source width) + (~> raw-map-range (string-split _ " ") (map string->number _)))]) + (map-range source (+ source width -1) (- dest source))))) + +;; part 1 +(define (in-map-range? n mr) + (<= (map-range-start mr) n (map-range-end mr))) + +(define (transform-value mapper n) + (match mapper + ['() n] + [(list* mr _) + #:when (in-map-range? n mr) + (+ n (map-range-offset mr))] + [(list* _ rest-mapper) (transform-value rest-mapper n)])) + +(for/lists (transforms #:result (apply min transforms)) + ([seed (in-list seeds-naive)]) + (foldl transform-value seed mappers)) + +;; part 2 +(define (remap-range r mapper [acc '()]) + (match-define (seed-range r-start r-end) r) + (match mapper + ; mapper exhausted + ['() (cons r acc)] + ; range to the left - not covered by this mapping, so keep as-is + [(list* (map-range m-start _ _) _) + #:when (< r-end m-start) + (cons r acc)] + ; range to the right - move to next map-range + [(list* (map-range _ m-end _) ms) + #:when (< m-end r-start) + (remap-range r ms acc)] + ; range is inside map-range - transform whole range + [(list* (map-range m-start m-end offset) _) + #:when (and (<= m-start r-start) (<= r-end m-end)) + (cons (seed-range (+ r-start offset) (+ r-end offset)) acc)] + ; range overlaps start only - keep left side, transform right side + [(list* (map-range m-start m-end offset) ms) + #:when (and (< r-start m-start) (<= r-end m-end)) + (remap-range (seed-range (add1 m-end) r-end) + ms + (cons (seed-range (+ m-start offset) (+ r-end offset)) acc))] + ; range overlaps end - transform left side, pass right side + [(list* (map-range m-start m-end offset) ms) + #:when (and (< m-start r-start) (<= m-end r-end)) + (remap-range (seed-range (add1 m-end) r-end) + ms + (cons (seed-range (+ r-start offset) (+ m-end offset)) acc))] + ; range overlaps whole map-range - keep left side, transform middle, pass right side + [(list* (map-range m-start m-end offset) ms) + (remap-range (seed-range (add1 m-end) r-end) + ms + (cons (seed-range (+ m-start offset) (+ m-end offset)) + (cons (seed-range (add1 m-end) r-end) acc)))])) + +(define (remap-ranges rs mappers) + (cond + [(empty? mappers) rs] + [else + (~>> rs (append-map (curryr remap-range (first mappers))) (remap-ranges _ (rest mappers)))])) + +(~> seeds-naive + (chunks-of 2) + (map (λ (xs) + (match-define (list start width) xs) + (~> (list (seed-range start (+ start width -1))) + (remap-ranges mappers) + (argmin seed-range-start _))) + _) + (argmin seed-range-start _) + seed-range-start) diff --git a/racket/aoc2023/day-06/day-06.rkt b/racket/aoc2023/day-06/day-06.rkt new file mode 100644 index 0000000..53ca9ee --- /dev/null +++ b/racket/aoc2023/day-06/day-06.rkt @@ -0,0 +1,32 @@ +#lang racket + +(require advent-of-code + threading) + +(match-define (list times distances) + (~> (open-aoc-input (find-session) 2023 6 #:cache #true) port->lines)) + +;; part 1 +(define get-numbers (λ~> string-split (map string->number _) rest)) +(define (find-bound race-time dist button-time step) + (if (< dist (* button-time (- race-time button-time))) + button-time + (find-bound race-time dist (+ step button-time) step))) + +(define (lower-bound rtime dist) + (find-bound rtime dist 1 1)) +(define (upper-bound rtime dist) + (find-bound rtime dist rtime -1)) + +(for/fold ([acc 1]) + ([race-time (in-list (get-numbers times))] + [distance (in-list (get-numbers distances))]) + (* acc (add1 (- (upper-bound race-time distance) (lower-bound race-time distance))))) + +;; part 2 + +(define unkern (λ~>> get-numbers (apply ~a) string->number)) +(define big-time (unkern times)) +(define big-distance (unkern distances)) + +(add1 (- (upper-bound big-time big-distance) (lower-bound big-time big-distance))) diff --git a/racket/aoc2023/day-07/day-07.rkt b/racket/aoc2023/day-07/day-07.rkt new file mode 100644 index 0000000..30e629b --- /dev/null +++ b/racket/aoc2023/day-07/day-07.rkt @@ -0,0 +1,82 @@ +#lang racket + +(require advent-of-code + threading + memo) + +(struct hand (cards wager)) + +(define/match (card->int card) + [((? char-numeric?)) (~> card string string->number)] + [(#\A) 14] + [(#\K) 13] + [(#\Q) 12] + [(#\J) 11] + [(#\T) 10] + [(#\*) 1]) + +(define (parse-hand str #:jokers [jokers? #f]) + (match-define (list card-str wager-str) (string-split str)) + (define cards + (~> card-str + ((λ (str) (if jokers? (string-replace str "J" "*") str))) + string->list + (map card->int _))) + (define wager (~> wager-str string->number)) + (hand cards wager)) + +(define input (~> (open-aoc-input (find-session) 2023 7 #:cache #true) port->lines)) + +(define/memoize (identify-hand h) + (define freqs (~> h hand-cards (sort <) (group-by identity _) (map length _))) + (match freqs + [(list-no-order 5) 8] + [(list-no-order 1 4) 7] + [(list-no-order 2 3) 6] + [(list-no-order 1 1 3) 5] + [(list-no-order 1 2 2) 4] + [(list-no-order 1 1 1 2) 3] + [(list-no-order 1 1 1 1 1) 2] + [_ 1])) + +(define (compare-first-card cs1 cs2) + (if (= (first cs1) (first cs2)) + (compare-first-card (rest cs1) (rest cs2)) + (< (first cs1) (first cs2)))) + +(define (compare-hands with h1 h2) + (define rank1 (with h1)) + (define rank2 (with h2)) + (if (= rank1 rank2) (compare-first-card (hand-cards h1) (hand-cards h2)) (< rank1 rank2))) + +;; part 1 + +(define (compare-hands-no-wilds h1 h2) + (compare-hands identify-hand h1 h2)) + +(define (total-score in #:jokers [jokers? #false]) + (define sorted-hands + (~> in + (map (curry parse-hand #:jokers jokers?) _) + (sort (if jokers? compare-hands-no-wilds compare-hands-with-wilds)))) + (for/sum ([(h i) + (in-indexed sorted-hands)]) + (* (add1 i) (hand-wager h)))) + +(total-score input) + +;; part 2 + +(define/memoize (find-best-joker-substitution h) + (for/fold ([best-hand (hand '() 0)]) + ([wild (in-inclusive-range 2 14)]) + (define trial-hand + (hand (map (λ (c) (if (= c 1) wild c)) (hand-cards h)) (hand-wager h))) + (if (> (identify-hand trial-hand) (identify-hand best-hand)) + trial-hand + best-hand))) + +(define (compare-hands-with-wilds h1 h2) + (compare-hands (λ~> find-best-joker-substitution identify-hand) h1 h2)) + +(total-score input #:jokers #true) diff --git a/racket/aoc2023/day-08/day-08.rkt b/racket/aoc2023/day-08/day-08.rkt new file mode 100644 index 0000000..06daafa --- /dev/null +++ b/racket/aoc2023/day-08/day-08.rkt @@ -0,0 +1,36 @@ +#lang racket + +(require advent-of-code + threading) + +(struct exits (left right) #:transparent) + +(match-define (list raw-directions raw-maze) + (~> (fetch-aoc-input (find-session) 2023 8 #:cache #true) (string-split "\n\n"))) + +(define directions (string->list raw-directions)) + +(define maze + (for/hash ([line (in-list (string-split raw-maze "\n"))]) + (match (regexp-match #rx"(...) = \\((...), (...)\\)" line) + [(list _ name left right) (values name (exits left right))]))) + +(define (to-next-node start end dirs maze) + (for/fold ([current start] + [acc 0] + #:result acc) + ([dir (in-cycle dirs)]) + #:break (string-suffix? current end) + (define node (hash-ref maze current)) + (case dir + [(#\L) (values (exits-left node) (add1 acc))] + [(#\R) (values (exits-right node) (add1 acc))]))) + +;; part 1 +(to-next-node "AAA" "ZZZ" directions maze) + +;; part 2 +(for/lists (ns #:result (apply lcm ns)) + ([start (in-list (hash-keys maze))] + #:when (string-suffix? start "A")) + (to-next-node start "Z" directions maze)) diff --git a/racket/aoc2023/day-09/day-09-polynomial.rkt b/racket/aoc2023/day-09/day-09-polynomial.rkt new file mode 100644 index 0000000..5bacb1f --- /dev/null +++ b/racket/aoc2023/day-09/day-09-polynomial.rkt @@ -0,0 +1,17 @@ +#lang racket + +(require advent-of-code + threading + simple-polynomial/tools) + +(define histories + (for/list ([raw-history (in-lines (open-aoc-input (find-session) 2023 9 #:cache #true))]) + (~>> raw-history + string-split + (map string->number)))) + +(for/lists (left right #:result (cons (apply + left) (apply + right))) + ([history (in-list histories)]) + (define f (interpolate-at-integer-points history)) + (values (f -1) + (f (length history)))) diff --git a/racket/aoc2023/day-09/day-09.rkt b/racket/aoc2023/day-09/day-09.rkt new file mode 100644 index 0000000..5eda1eb --- /dev/null +++ b/racket/aoc2023/day-09/day-09.rkt @@ -0,0 +1,32 @@ +#lang racket + +(require advent-of-code + threading) + +(define histories + (for/list ([raw-history (in-lines (open-aoc-input (find-session) 2023 9 #:cache #true))]) + (~>> raw-history + string-split + (map string->number)))) + +(define (constant? xs) + (= 1 (length (remove-duplicates xs)))) + +(define/match (derivative xs) + [((list a b)) (list (- b a))] + [((list* a b _)) (cons (- b a) (derivative (rest xs)))]) + +(define (extrapolate xs) + (if (constant? xs) + (car xs) + (+ (last xs) (extrapolate (derivative xs))))) + +;; part 1 +(~>> histories + (map extrapolate) + (apply +)) + +;; part 2 +(~>> histories + (map (λ~> reverse extrapolate)) + (apply +)) diff --git a/racket/aoc2023/day-10/day-10.rkt b/racket/aoc2023/day-10/day-10.rkt new file mode 100644 index 0000000..64d8727 --- /dev/null +++ b/racket/aoc2023/day-10/day-10.rkt @@ -0,0 +1,97 @@ +#lang racket + +(require advent-of-code + threading) + +(struct posn (r c) #:transparent) + +(define/match (add-posns _p1 _p2) + [((posn x1 y1) (posn x2 y2)) (posn (+ x1 x2) (+ y1 y2))]) + +(define go-north (posn -1 0)) +(define go-south (posn 1 0)) +(define go-east (posn 0 1)) +(define go-west (posn 0 -1)) + +(define initial-directions + (list (cons go-north '(#\| #\7 #\F)) + (cons go-south '(#\| #\J #\L)) + (cons go-east '(#\- #\J #\7)) + (cons go-west '(#\- #\F #\L)))) + +(define/match (pipe-neighbors _pipe) + [(#\|) (list go-north go-south)] + [(#\-) (list go-east go-west)] + [(#\L) (list go-north go-east)] + [(#\F) (list go-south go-east)] + [(#\7) (list go-south go-west)] + [(#\J) (list go-north go-west)]) + +(define (make-pipe-grid in) + (for*/hash ([(row r) (in-indexed (string-split in "\n"))] + [(ch c) (in-indexed (string->list row))]) + (values (posn (add1 r) (add1 c)) ch))) + +(define (get-valid-S-neighbors S grid) + (for/list ([dir (in-list initial-directions)] + #:do [(match-define (cons d valid) dir)] + #:do [(define neighbor (add-posns d S))] + #:when (member (hash-ref grid neighbor 'none) valid)) + neighbor)) + +(define (to-next-pipe current previous grid [acc '()]) + (cond + [(equal? (hash-ref grid current #f) #\S) acc] + [else + (define next + (for/first ([d (in-list (pipe-neighbors (hash-ref grid current)))] + #:do [(define neighbor (add-posns d current))] + #:unless (equal? neighbor previous)) + neighbor)) + (~> next (to-next-pipe _ current grid (cons current acc)))])) + +;; part 1 +(define input (fetch-aoc-input (find-session) 2023 10 #:cache #true)) + +(define pipe-grid (make-pipe-grid input)) + +(define S-posn + (for/first ([(k v) (in-hash pipe-grid)] #:when (equal? v #\S)) + k)) + +(define S-neighbors (get-valid-S-neighbors S-posn pipe-grid)) + +(define pipe-loop (to-next-pipe (first S-neighbors) S-posn pipe-grid '())) + +(/ (add1 (length pipe-loop)) 2) + +;; part 2 +(define pipe-loop-set (~> (list->set pipe-loop) (set-add S-posn))) + +(define (trace-rays pt pipes grid) + (cond + [(set-member? pipes pt) #f] + [else (odd? (trace-ray pt pipes grid))])) + +(define (trace-ray pt pipes grid) + (define row (posn-r pt)) + (for/fold ([acc 0] + [corner #f] + #:result acc) + ([col (in-naturals (posn-c pt))] + #:do [(define test-pt (posn row col))] + #:break (not (hash-has-key? grid test-pt)) + #:when (set-member? pipes test-pt)) + (define pipe (hash-ref grid test-pt)) + (match* (corner pipe) + [(#f #\|) (values (add1 acc) #f)] ; vertical crossing + [(#f (or #\F #\L)) (values acc pipe)] + [(#\F #\J) (values (add1 acc) #f)] ; a ┏━┛ shape counts as a vertical crossing + [(#\L #\7) (values (add1 acc) #f)] + [(#\F #\7) (values acc #f)] ; a ┏━┓ shape doesn't count + [(#\L #\J) (values acc #f)] + [(_ _) (values acc corner)]))) + +(~> pipe-grid + hash-keys + (count (λ~> (trace-rays pipe-loop-set pipe-grid)) _)) diff --git a/racket/aoc2023/day-11/day-11.rkt b/racket/aoc2023/day-11/day-11.rkt new file mode 100644 index 0000000..dba617b --- /dev/null +++ b/racket/aoc2023/day-11/day-11.rkt @@ -0,0 +1,40 @@ +#lang racket + +(require advent-of-code + threading) + +(struct posn (x y) #:transparent) + +(define input + (~> (fetch-aoc-input (find-session) 2023 11 #:cache #true) + (string-split "\n") + (map string->list _))) + +(define (get-empty-ranks grid) + (for/list ([(rank n) (in-indexed grid)] #:when (equal? '(#\.) (remove-duplicates rank))) + n)) + +(define (count-prior-empty-ranks rank empty-ranks) + (~> empty-ranks + (takef (curryr < rank)) + length)) + +(define empty-rows (get-empty-ranks input)) +(define empty-columns (get-empty-ranks (apply map list input))) ;; transpose + +(define (sum-of-star-distances in expand-by) + (define stars + (for*/list ([(row x) (in-indexed in)] + [(col y) (in-indexed row)] + #:when (equal? col #\#)) + (posn (+ x (* (sub1 expand-by) (count-prior-empty-ranks x empty-rows))) + (+ y (* (sub1 expand-by) (count-prior-empty-ranks y empty-columns)))))) + (for/sum ([star-pair (in-combinations stars 2)]) + (match-define (list (posn x1 y1) (posn x2 y2)) star-pair) + (+ (abs (- x1 x2)) (abs (- y1 y2))))) + +;; part 1 +(sum-of-star-distances input 2) + +;; part 2 +(sum-of-star-distances input 1000000) diff --git a/racket/aoc2023/day-12/day-12.rkt b/racket/aoc2023/day-12/day-12.rkt new file mode 100644 index 0000000..50b14bb --- /dev/null +++ b/racket/aoc2023/day-12/day-12.rkt @@ -0,0 +1,65 @@ +#lang racket + +(require advent-of-code + threading + memo) + +(struct condition (template spring-set)) + +(define conditions + (for/list ([line (in-lines (open-aoc-input (find-session) 2023 12 #:cache #true))]) + (match (string-split line #px"[ ,]") + [(list* template spring-set) + (condition (string->list template) (map string->number spring-set))]))) + +(define/memoize (do-count template spring-group left-in-group need-gap?) + ;; template: list of spring positions + ;; spring-group: list of remaining contiguous groups of damaged springs + ;; left-in-group: springs remaining in current bad spring group being placed + ;; need-gap?: did we just finish placing a bad spring group + ;; and need at least one undamaged spring before starting the next one? + (match* (template spring-group left-in-group need-gap?) + ;; no springs left to place and no places left to place springs + ;; this is an OK arrangement, count it + [('() '() 0 _) 1] + ;; ambiguous wildcard, try both skipping this spot and starting a damaged spring group here + [((list* #\? t-rest) (list* g g-rest) 0 #f) + (+ (do-count t-rest g-rest (sub1 g) (= g 1)) + (do-count t-rest spring-group 0 #f))] + ;; definitely a place for a good spring (.), move on without consuming any spring groups + [((list* #\? t-rest) '() 0 #f) ; good spring, no more damaged springs to place + (do-count t-rest spring-group 0 #f)] + [((list* #\? t-rest) _ 0 #t) ; good spring right after finishing a group of bad springs + (do-count t-rest spring-group 0 #f)] + [((list* #\. t-rest) _ 0 _) ; known good spring + (do-count t-rest spring-group 0 #f)] + ;; start of bad spring (#) group, use the next spring group and remove 1 from it + [((list* #\# t-rest) (list* g g-rest) 0 #f) (do-count t-rest g-rest (sub1 g) (= g 1))] + ;; continuation of bad spring group, same + [((list* (or #\? #\#) t-rest) g left #f) (do-count t-rest g (sub1 left) (= left 1))] + ;; if nothing above works, this arrangement's invalid + [(_ _ _ _) 0])) + +(define (count-solutions c) + (match-define (condition template spring-set) c) + (do-count template spring-set 0 #f)) + +;; part 1 +(for/sum ([c (in-list conditions)]) + (count-solutions c)) + +;; part 2 +(define expanded-conditions + (for/list ([c (in-list conditions)]) + (condition (~> c + condition-template + (make-list 5 _) + (add-between #\?) + flatten) + (~> c + condition-spring-set + (make-list 5 _) + flatten)))) + +(for/sum ([c* (in-list expanded-conditions)]) + (count-solutions c*)) diff --git a/racket/aoc2023/day-13/day-13.rkt b/racket/aoc2023/day-13/day-13.rkt new file mode 100644 index 0000000..47718f8 --- /dev/null +++ b/racket/aoc2023/day-13/day-13.rkt @@ -0,0 +1,47 @@ +#lang racket + +(require advent-of-code + threading) + +(define input + (~>(fetch-aoc-input (find-session) 2023 13 #:cache #true) + (string-split "\n\n") + (map (λ~> string-split) _))) + +(define (do-symmetric? lefts rights errs) + (cond + [(empty? rights) #f] + [else + (define found-errs + (for/sum ([l (in-string (string-join lefts ""))] + [r (in-string (string-join rights ""))] + #:unless (char=? l r)) + 1)) + (if (= errs found-errs) + (length lefts) + (do-symmetric? (cons (first rights) lefts) + (rest rights) + errs))])) + +(define (symmetric? xss errs) + (do-symmetric? (list (first xss)) (rest xss) errs)) + +(define (transpose strs) + (~> strs + (map string->list _) + (apply map list _) + (map list->string _))) + +(define (find-symmetry-score xss errs) + (cond + [(symmetric? xss errs) => (curry * 100)] + [else (symmetric? (transpose xss) errs)])) + +;; part 1 +(for/sum ([note (in-list input)]) + (find-symmetry-score note 0)) + +;; part 2 +(for/sum ([note (in-list input)]) + (find-symmetry-score note 1)) + diff --git a/racket/aoc2023/day-14/day-14.rkt b/racket/aoc2023/day-14/day-14.rkt new file mode 100644 index 0000000..d0b7cad --- /dev/null +++ b/racket/aoc2023/day-14/day-14.rkt @@ -0,0 +1,49 @@ +#lang racket + +(require advent-of-code + threading + "../../jj-aoc.rkt") + +(define input + (~> (fetch-aoc-input (find-session) 2023 14 #:cache #true) + string-split + (map string->list _) + transpose)) + +(define (roll-boulders board) + (for/list ([col (in-list board)]) + (~> col (chunks-by (curry equal? #\#)) (append-map (curryr sort char>?) _)))) + +(define (score board) + (for*/sum ([col (in-list board)] + [(row n) (in-indexed (reverse col))] + #:when (equal? row #\O)) + (add1 n))) + +;; part 1 +(~> input + roll-boulders + score) + +;; part 2 +(define (rotate-board xss) + (~> xss + (map reverse _) + transpose)) + +(define (full-cycle board) + (foldl (λ (_ acc) (~> acc roll-boulders rotate-board)) board (range 4))) + +(define (spin-to-win board) + (define cache (make-hash)) + (define (do-spin board n) + (match (hash-ref cache board 'not-found) + ['not-found + (hash-set! cache board n) + (do-spin (full-cycle board) (sub1 n))] + [seen + (define to-end (modulo n (- seen n))) + (score (foldl (λ (_ acc) (full-cycle acc)) board (range to-end)))])) + (do-spin board 1000000000)) + +(~> input spin-to-win) diff --git a/racket/aoc2023/day-15/day-15.rkt b/racket/aoc2023/day-15/day-15.rkt new file mode 100644 index 0000000..d049565 --- /dev/null +++ b/racket/aoc2023/day-15/day-15.rkt @@ -0,0 +1,41 @@ +#lang racket + +(require advent-of-code + threading) + +(define input + (~> (fetch-aoc-input (find-session) 2023 15 #:cache #true) string-trim (string-split ","))) + +(define (hash-algorithm str) + (for/fold ([acc 0]) ([c (in-string str)]) + (~> c char->integer (+ acc) (* 17) (modulo _ 256)))) + +;; part 1 +(for/sum ([code (in-list input)]) (hash-algorithm code)) + +;; part 2 +(define (remove-lens boxes label) + (hash-update boxes + (hash-algorithm label) + (λ (lens-set) (remove label lens-set (λ (rem l) (equal? rem (car l))))) + '())) + +(define (insert-lens boxes label focal) + (define new-lens (cons label focal)) + (hash-update boxes + (hash-algorithm label) + (λ (lens-set) + (if (assoc label lens-set) + (map (λ (pair) (if (equal? (car pair) label) new-lens pair)) lens-set) + (append lens-set (list new-lens)))) + (list new-lens))) + +(define (focusing-power boxes) + (for*/sum ([(box-number lenses) (in-hash boxes)] [(lens order) (in-indexed lenses)]) + (* (add1 box-number) (add1 order) (cdr lens)))) + +(for/fold ([boxes (hash)] #:result (focusing-power boxes)) ([code (in-list input)]) + (match code + [(regexp #rx"(.*)=(.*)" (list _ label (app string->number focal))) + (insert-lens boxes label focal)] + [(regexp #rx"(.*)-" (list _ label)) (remove-lens boxes label)])) diff --git a/racket/aoc2023/day-16/day-16.rkt b/racket/aoc2023/day-16/day-16.rkt new file mode 100644 index 0000000..4a70de8 --- /dev/null +++ b/racket/aoc2023/day-16/day-16.rkt @@ -0,0 +1,70 @@ +#lang racket + +(require advent-of-code + threading) + +(struct posn (r c) #:transparent) +(struct light (posn dir) #:transparent) + +(define input (fetch-aoc-input (find-session) 2023 16 #:cache #true)) + +(define grid + (for*/hash ([(row r) (in-indexed (string-split input "\n"))] + [(cell c) (in-indexed (in-string row))]) + (values (posn r c) cell))) + +(define/match (move _l) + [((light (posn r c) 'up)) (light (posn (sub1 r) c) 'up)] + [((light (posn r c) 'right)) (light (posn r (add1 c)) 'right)] + [((light (posn r c) 'left)) (light (posn r (sub1 c)) 'left)] + [((light (posn r c) 'down)) (light (posn (add1 r) c) 'down)]) + +(define/match (transform l _cell) + [(_ #\.) l] + [(_ 'off) '()] + + [((light _ (or 'up 'down)) #\|) l] + [((light _ (or 'left 'right)) #\-) l] + + [((light p 'left) #\/) (light p 'down)] + [((light p 'down) #\/) (light p 'left)] + [((light p 'right) #\/) (light p 'up)] + [((light p 'up) #\/) (light p 'right)] + + [((light p 'left) #\\) (light p 'up)] + [((light p 'up) #\\) (light p 'left)] + [((light p 'right) #\\) (light p 'down)] + [((light p 'down) #\\) (light p 'right)] + + [((light p (or 'left 'right)) #\|) (list (light p 'up) (light p 'down))] + [((light p (or 'up 'down)) #\-) (list (light p 'left) (light p 'right))]) + +;; part 1 +(define (get-energized start) + (for/fold ([energized (set)] + [previously-visited (set)] + [beam-tips (set start)] + #:result (set-count energized)) + ([_ (in-naturals)]) + (define next-positions + (list->set + (flatten (for/list ([b (in-set beam-tips)]) + (~> b move ((λ (b) (transform b (hash-ref grid (light-posn b) 'off))))))))) + (define all-visited (set-union previously-visited next-positions)) + (define next-energized (set-union energized (list->set (set-map next-positions light-posn)))) + #:break (equal? previously-visited all-visited) + (values next-energized all-visited (set-subtract next-positions previously-visited)))) + +(get-energized (light (posn 0 -1) 'right)) + +;; part 2 +(define rows (~> input (string-split "\n") length)) +(define cols (~> input (string-split #rx"\n.*") first string-length)) + +(define all-starting-positions + (append (map (λ (r) (light (posn r -1) 'right)) (range rows)) + (map (λ (r) (light (posn r cols) 'left)) (range rows)) + (map (λ (c) (light (posn -1 c) 'down)) (range cols)) + (map (λ (c) (light (posn rows c) 'up)) (range cols)))) + +(get-energized (argmax get-energized all-starting-positions))
\ No newline at end of file diff --git a/racket/aoc2023/day-17/day-17.rkt b/racket/aoc2023/day-17/day-17.rkt new file mode 100644 index 0000000..05709ad --- /dev/null +++ b/racket/aoc2023/day-17/day-17.rkt @@ -0,0 +1,86 @@ +#lang racket + +(require advent-of-code + threading + data/heap) + +(struct state (p heat-lost previous history)) +(struct posn (r c)) + +(define/match (add _p1 _p2) + [((posn r1 c1) (posn r2 c2)) (posn (+ r1 r2) (+ c1 c2))]) + +(define deltas (list (posn 0 1) (posn 0 -1) (posn 1 0) (posn -1 0))) + +(define input (fetch-aoc-input (find-session) 2023 17 #:cache #true)) + +(define grid + (for*/hash ([(row r) (in-indexed (in-list (string-split input "\n")))] + [(col c) (in-indexed (in-string row))]) + (values (posn r c) (~> col string string->number)))) + +(define goal-posn (~>> grid hash-keys (argmax (λ (p) (+ (posn-r p) (posn-c p)))))) + +(define (make-key s) + (cons (state-p s) (same-dir s))) + +(define (goal? n s) + (and (equal? goal-posn (state-p s)) + (>= (length (same-dir s)) n))) + +(define (same-dir s) + (define history (state-history s)) + (if (empty? history) + '() + (takef history (λ (n) (equal? n (car history)))))) + +(define (find-good-neighbors min-dist max-dist s) + (match-define (state p hl prev hist) s) + + (define (eliminate-bad-neighbors delta) + (define neighbor (add p delta)) + (cond + [(or (equal? neighbor prev) (not (hash-has-key? grid neighbor))) #false] + [else + (define same (same-dir s)) + (define l (length same)) + (cond + [(= max-dist l) (not (equal? delta (car same)))] + [(= l 0) #true] + [(< l min-dist) (equal? delta (car same))] + [else #t])])) + + (define (make-state delta) + (define neighbor (add p delta)) + (define new-loss (+ hl (hash-ref grid neighbor))) + (state neighbor new-loss p (cons delta hist))) + + (~>> deltas (filter eliminate-bad-neighbors) (map make-state))) + +(define (find-path neighbor-fn goal-fn) + (define seen (mutable-set)) + (define queue (make-heap (λ (a b) (<= (state-heat-lost a) (state-heat-lost b))))) + (heap-add! queue (state (posn 0 0) 0 'none '())) + + (let bfs () + (define s (heap-min queue)) + (heap-remove-min! queue) + (define key (make-key s)) + (cond + [(set-member? seen key) (bfs)] + [else + (set-add! seen key) + (define neighbors (neighbor-fn s)) + (define final (findf goal-fn neighbors)) + (if final + (state-heat-lost final) + (begin + (for ([n (in-list neighbors)]) + (heap-add! queue n)) + (bfs)))]))) + +;; part 1 +(find-path (curry find-good-neighbors 0 3) (curry goal? 1)) + +;; part 2 +(find-path (curry find-good-neighbors 4 10) (curry goal? 4))
\ No newline at end of file diff --git a/racket/aoc2023/day-18/day-18.rkt b/racket/aoc2023/day-18/day-18.rkt new file mode 100644 index 0000000..b589e41 --- /dev/null +++ b/racket/aoc2023/day-18/day-18.rkt @@ -0,0 +1,48 @@ +#lang racket +(require advent-of-code + threading) + +(struct coord (x y)) + +(define input (~> (fetch-aoc-input (find-session) 2023 18 #:cache #true))) + +(define (go-to-next-coord c dir dist) + (match-define (coord x y) c) + (match dir + [(or "R" "0") (coord (+ x dist) y)] + [(or "D" "1") (coord x (- y dist))] + [(or "L" "2") (coord (- x dist) y)] + [(or "U" "3") (coord x (+ y dist))])) + +(define/match (triangle-area _coord1 _coord2) + [((coord x1 y1) (coord x2 y2)) (/ (- (* x1 y2) (* x2 y1)) 2)]) + +(define (find-area-using parser) + (for/fold ([area 0] + [perimeter 0] + [current-coord (coord 0 0)] + #:result (+ 1 (abs area) (/ perimeter 2))) + ([dig (in-list (string-split input "\n"))]) + (define-values (dir dist) (parser dig)) + (define next-coord (go-to-next-coord current-coord dir dist)) + (values (+ area (triangle-area current-coord next-coord)) + (+ perimeter dist) next-coord))) + +;; part 1 +(define (parse-front dig) + (match-define (regexp #rx"(.) (.*) \\((.*)\\)" + (list _ dir (app string->number dist) _hex)) + dig) + (values dir dist)) + +(find-area-using parse-front) + +;; part 2 + +(define (parse-hex dig) + (match-define (regexp #rx".*\\(#(.....)(.)\\)" + (list _ (app (curryr string->number 16) dist) dir)) + dig) + (values dir dist)) + +(find-area-using parse-hex) diff --git a/racket/aoc2023/day-19/day-19.rkt b/racket/aoc2023/day-19/day-19.rkt new file mode 100644 index 0000000..f7561f6 --- /dev/null +++ b/racket/aoc2023/day-19/day-19.rkt @@ -0,0 +1,134 @@ +#lang racket + +(require advent-of-code + threading + data/applicative + data/monad + megaparsack + megaparsack/text + racket/struct) + +(struct part (x m a s)) +(struct rule (rating comparison threshold action)) +(struct just (action)) +(struct interval (from to)) + +(match-define (list raw-workflows raw-parts) + (~> (fetch-aoc-input (find-session) 2023 19 #:cache #true) + (string-split "\n\n") + (map (curryr string-split "\n") _))) + +(define/match (to-getter _) + [(#\x) part-x] + [(#\m) part-m] + [(#\a) part-a] + [(#\s) part-s]) + +(define/match (to-comp _) + [(#\>) >] + [(#\<) <]) + +(define/match (to-action _) + [('(#\R)) 'reject] + [('(#\A)) 'accept] + [(name) (apply string name)]) + +(define rule/p + (do (or/p + (try/p (do [rating <- (char-in/p "xmas")] + [comparison <- (char-in/p "<>")] + [threshold <- integer/p] + (char/p #\:) + [action <- (many+/p letter/p)] + (pure (rule (to-getter rating) + (to-comp comparison) + threshold + (to-action action))))) + (do [name <- (many+/p letter/p)] (pure (just (to-action name))))))) +(define rules/p + (do [name <- (many+/p letter/p)] + (char/p #\{) + [rules <- (many+/p rule/p #:sep (char/p #\,))] + (char/p #\}) + (pure (cons (list->string name) rules)))) + +(define rating/p (do letter/p (char/p #\=) integer/p)) +(define parts/p + (do (string/p "{") + [ratings <- (many/p rating/p #:sep (char/p #\,) #:min 4 #:max 4)] + (string/p "}") + (pure (apply part ratings)))) + +(define workflows + (~>> raw-workflows + (map (λ~>> (parse-string rules/p) parse-result!)) + make-immutable-hash)) +(define parts (map (λ~>> (parse-string parts/p) parse-result!) raw-parts)) + +;; part 1 + +(define (evaluate-workflow p [workflow-name "in"]) + (define rules (hash-ref workflows workflow-name)) + (match (evaluate-rules p rules) + ['accept (~> p struct->list (apply + _))] + ['reject 0] + [name (evaluate-workflow p name)])) + +(define (evaluate-rules p rules) + (match rules + [(list* (just action) _) action] + [(list* (rule rating comparison threshold action) _) + #:when (comparison (rating p) threshold) + action] + [(list* _ tail) (evaluate-rules p tail)])) + +(for/sum ([p (in-list parts)]) (evaluate-workflow p)) + +;; part 2 + +(define (part-update-range pr rating i) + (match rating + [(== part-x) (struct-copy part pr (x i))] + [(== part-m) (struct-copy part pr (m i))] + [(== part-a) (struct-copy part pr (a i))] + [(== part-s) (struct-copy part pr (s i))])) + +(define (evaluate-workflow-on-range pr [workflow-name "in"]) + (define rules (hash-ref workflows workflow-name)) + (evaluate-rules-on-range pr rules)) + +(define (evaluate-rules-on-range pr rules) + (match rules + [(list* (just 'accept) _) + (~> pr struct->list + (map (λ (i) (add1 (- (interval-to i) (interval-from i)))) _) + (apply * _))] + [(list* (just 'reject) _) 0] + [(list* (just name) _) (evaluate-workflow-on-range pr name)] + [(list* (rule rating (== <) threshold action) tail) + (match-define (interval i-min i-max) (rating pr)) + (split-range pr + rating + (interval i-min (sub1 threshold)) + action + (interval threshold i-max) + tail)] + [(list* (rule rating (== >) threshold action) tail) + (match-define (interval i-min i-max) (rating pr)) + (split-range pr + rating + (interval (add1 threshold) i-max) + action + (interval i-min threshold) + tail)])) + +(define (split-range pr rating keep action pass rules) + (+ (evaluate-rules-on-range (part-update-range pr rating keep) + (list (just action))) + (evaluate-rules-on-range (part-update-range pr rating pass) + rules))) + +(define start-interval (interval 1 4000)) + +(evaluate-workflow-on-range + (part start-interval start-interval start-interval start-interval)) diff --git a/racket/aoc2023/day-20/day-20.rkt b/racket/aoc2023/day-20/day-20.rkt new file mode 100644 index 0000000..2e3852d --- /dev/null +++ b/racket/aoc2023/day-20/day-20.rkt @@ -0,0 +1,144 @@ +#lang racket + +(require advent-of-code + threading + data/applicative + data/monad + megaparsack + megaparsack/text) + +(struct broadcaster ()) +(struct flipflop (state received)) +(struct conjunction (recieved)) +(struct cable (type dests)) +(struct nothing ()) + +(define charlist->symbol (λ~>> (apply string) string->symbol)) + +(define input (fetch-aoc-input (find-session) 2023 20 #:cache true)) + +(define module/p + (do (or/p (do (char/p #\%) + [name <- (many+/p letter/p)] + (pure (cons (charlist->symbol name) (flipflop 'off '())))) + (do (char/p #\&) + [name <- (many+/p letter/p)] + (pure (cons (charlist->symbol name) (conjunction (hash))))) + (do [name <- (many+/p letter/p)] + (pure (cons (charlist->symbol name) (broadcaster))))))) + +(define cable/p + (do [mod <- module/p] + (string/p " -> ") + [names <- (many/p (many+/p letter/p) #:sep (string/p ", "))] + (pure (cable mod (map charlist->symbol names))))) + +(define cables (~> input (string-split "\n") (map (λ~>> (parse-string cable/p) parse-result!) _))) + +(define destinations + (for/hash ([cable (in-list cables)]) + (values (car (cable-type cable)) (cable-dests cable)))) + +(define (set-conjunction-initial-state c) + (cond + [(conjunction? (cdr c)) + (~>> destinations + hash-keys + (filter (λ (k) (member (car c) (hash-ref destinations k)))) + (map (λ (k) (cons k 'low))) + (make-immutable-hash) + conjunction + (cons (car c)))] + [else c])) + +(define (make-initial-conditions-hash cables) + (~>> cables + (map cable-type) + (map set-conjunction-initial-state) + make-immutable-hash)) + +(define (receive mod from tone) + (match mod + [(flipflop state queue) (flipflop state (append queue (list tone)))] + [(conjunction received) (conjunction (hash-set received from tone))] + [(nothing) (nothing)])) + +; needed for part 2 +(define to-rx '(rk cd zf qx)) +(define sentry-tones (make-hash (for/list ([node to-rx]) (cons node 0)))) + +(define (press-button-once current-state this-round) + (for/fold ([queue '(broadcaster)] + [all-cables-state current-state] + [high 0] + [low 0] + #:result (values all-cables-state high low)) + ([_i (in-naturals)] #:break (empty? queue)) + (match-define (list* hd tl) queue) + (define to (hash-ref destinations hd (nothing))) + (match (hash-ref all-cables-state hd) + [(broadcaster) + (define state* + (foldl (λ (r acc) (hash-update acc r (λ~> (receive hd 'low)) (nothing))) + all-cables-state + to)) + (values (hash-ref destinations 'broadcaster) state* high (+ (length to) (add1 low)))] + [(flipflop 'off (list* 'low q)) + (define state* + (~> all-cables-state + (foldl (λ (r acc) + (when (member r to-rx) + (println (~a r " received high tone at " this-round))) + (hash-update acc r (λ~> (receive hd 'high)) (nothing))) + _ + to) + (hash-set _ hd (flipflop 'on q)))) + (values (append tl to) state* (+ (length to) high) low)] + [(flipflop 'on (list* 'low q)) + (define state* + (~> all-cables-state + (foldl (λ (r acc) (hash-update acc r (λ~> (receive hd 'low)) (nothing))) _ to) + (hash-set _ hd (flipflop 'off q)))) + (values (append tl to) state* high (+ (length to) low))] + [(flipflop on-or-off (list* 'high q)) + (define state* (~> all-cables-state (hash-set _ hd (flipflop on-or-off q)))) + (values tl state* high low)] + [(conjunction received) + #:when (or (empty? (hash-values received)) (member 'low (hash-values received))) + + (when (member hd to-rx) + (hash-set! sentry-tones hd this-round)) + (define state* + (foldl (λ (r acc) + (hash-update acc r (λ~> (receive hd 'high)) (nothing))) + all-cables-state + to)) + (values (append to tl) state* (+ (length to) high) low)] + [(conjunction _) + (define state* + (foldl (λ (r acc) (hash-update acc r (λ~> (receive hd 'low)) (nothing))) + all-cables-state + to)) + (values (append tl to) state* high (+ (length to) low))] + [(nothing) (values tl all-cables-state high low)]))) + +;; part 1 +(for/fold ([starting-state (make-initial-conditions-hash cables)] + [high 0] + [low 0] + #:result (* high low)) + ([i (in-range 1000)]) + (define-values (next-state this-high this-low) (press-button-once starting-state i)) + (values next-state (+ high this-high) (+ low this-low))) + +;; part 2 +;; rx receives a tone from gh, which receives four tones itself +;; those tones arrive on regular synced cycles so it's just the LCM of those cycle lengths +;; and since those cycle lengths are prime, it reduces to the product of the lengths +;; this is a really hacky mutable solution, I'm sure there's better ways of flagging these cycles + +(for/fold ([starting-state (make-initial-conditions-hash cables)] + #:result (apply * (hash-values sentry-tones))) + ([i (in-range 1 5000)]) + (define-values (next-state _high _low) (press-button-once starting-state i)) + (values next-state))
\ No newline at end of file diff --git a/racket/aoc2023/day-21/day-21.rkt b/racket/aoc2023/day-21/day-21.rkt new file mode 100644 index 0000000..b5478eb --- /dev/null +++ b/racket/aoc2023/day-21/day-21.rkt @@ -0,0 +1,69 @@ +#lang racket + +(require advent-of-code + threading + simple-polynomial + racket/hash) + +(define input (fetch-aoc-input (find-session) 2023 21 #:cache #true)) + +(define initial-garden + (~> (for*/list ([(row r) (in-indexed (string-split input "\n"))] + [(col c) (in-indexed row)] + #:unless (equal? col #\#)) + (cons (cons r c) (if (equal? col #\S) 'on 'off))) + make-hash)) + +(define (neighbors p) + (match-define (cons r c) p) + (list (cons (add1 r) c) (cons (sub1 r) c) (cons r (add1 c)) (cons r (sub1 c)))) + +(define (make-n-steps garden n) + (define g (hash-copy garden)) + (define (make-one-step) + (define update (make-hash)) + (for ([(cons state) (in-hash g)] #:when (equal? state 'on)) + (hash-set! update cons 'off) + (for ([neighbor (in-list (neighbors cons))] #:when (hash-has-key? g neighbor)) + (hash-set! update neighbor 'on))) + (hash-union! g update #:combine/key (λ (_k _v v) v))) + (for/fold ([_ void] + #:result (~>> g hash-values (count (curry equal? 'on)))) + ([i (in-range n)]) + (displayln i) + (make-one-step))) + +;; part 1 +(make-n-steps initial-garden 64) + +;; part 2 +;; the growth of the steps pattern is regular and quadratic +;; the rock pattern has aisles in it that allow the steps pattern to expand freely +;; such that it will cross from one side to another in X steps +;; where X is the height/width of the repeated section + +(define grid-size (~> input (string-split "\n") length)) +(define half-size (/ (sub1 grid-size) 2)) + +(define expanded-garden + (~> (for*/list (#:do [(define rows (string-split input "\n"))] + #:do [(define height (length rows))] + [(row r) (in-indexed rows)] + #:do [(define width (string-length row))] + [(col c) (in-indexed row)] + #:unless (equal? col #\#) + [n (in-inclusive-range -3 3)] + [m (in-inclusive-range -3 3)]) + + (cons (cons (+ r (* n height)) (+ c (* m width))) + (if (and (= n m 0) (equal? col #\S)) 'on 'off))) + make-hash)) + +(define fitting-points + (for/list ([n (in-range 3)] #:do [(define size (+ half-size (* n grid-size)))]) + (cons n (make-n-steps expanded-garden size)))) + +(define end-cycle 26501365) +(define x (/ (- end-cycle half-size) grid-size)) + +((points->polynomial fitting-points) x)
\ No newline at end of file diff --git a/racket/aoc2023/day-22/day-22.rkt b/racket/aoc2023/day-22/day-22.rkt new file mode 100644 index 0000000..53668c0 --- /dev/null +++ b/racket/aoc2023/day-22/day-22.rkt @@ -0,0 +1,109 @@ +#lang racket + +(require advent-of-code + threading + data/applicative + data/monad + megaparsack + megaparsack/text + racket/hash) + +(struct posn (x y z)) +(struct block (n from to)) + +(define input (fetch-aoc-input (find-session) 2023 22 #:cache #true)) + +(define coordinate/p + (do [coords <- (many/p integer/p #:sep (char/p #\,) #:min 3 #:max 3)] + (pure (apply posn coords)))) + +(define block/p + (do [from <- coordinate/p] + (char/p #\~) + [to <- coordinate/p] + (pure (cons from to)))) + +(define starting-blocks + (~> (for/list ([line (in-list (string-split input "\n"))] + [n (in-naturals)]) + (match-define (cons from to) (parse-result! (parse-string block/p line))) + (block n from to)) + (sort < #:key (λ~> block-from posn-z)))) + +(define (all-in-cross-section-at-level b z) + (match-define (block _ (posn x1 y1 _) (posn x2 y2 _)) b) + (for*/list ([x (in-inclusive-range x1 x2)] + [y (in-inclusive-range y1 y2)]) + (posn x y z))) + +(define (place-block-at-level b h dz) + (match-define (block n (posn x1 y1 z1) (posn x2 y2 z2)) b) + (define now-occupied + (for*/hash ([x (in-inclusive-range x1 x2)] + [y (in-inclusive-range y1 y2)] + [z (in-inclusive-range dz (+ dz (- z2 z1)))]) + (values (posn x y z) n))) + (hash-union h now-occupied)) + +(define (find-lowest-level b h [z (~> b block-from posn-z)]) + (cond + [(= z 0) + (place-block-at-level b h 1)] + [(findf (curry hash-has-key? h) (all-in-cross-section-at-level b z)) + (place-block-at-level b h (add1 z))] + [else + (find-lowest-level b h (sub1 z))])) + +(define blocks-in-space (foldl find-lowest-level (hash) starting-blocks)) +(define block-positions + (for/fold ([placed-blocks (hash)]) + ([(p n) (in-hash blocks-in-space)]) + (hash-update placed-blocks n (curryr set-add p) (set)))) + +(define (down-one p) + (match p + [(posn x y z) (posn x y (sub1 z))])) + +(define supporting-blocks + (for/hash ([(n-id n-posns) (in-hash block-positions)]) + (values n-id + (for*/set ([(m-id m-posns) (in-hash block-positions)] + #:unless (= n-id m-id) + [m-posn (in-set m-posns)] + #:when (set-member? n-posns (down-one m-posn))) + m-id)))) + +(define supported-by-blocks + (for/hash ([n-id (in-hash-keys supporting-blocks)]) + (define supporters + (~> (for*/set + ([(m-id m-supporting) (in-hash supporting-blocks)] + #:unless (= n-id m-id) + #:when (set-member? m-supporting n-id)) + m-id) + ((λ (s) (if (set-empty? s) (set 'ground) s))))) + (values n-id supporters))) + +;; part 1 +(define vulnerable-blocks + (for/list ([n-id (in-range (length starting-blocks))] + #:when (for/or ([m-supported-by (in-hash-values supported-by-blocks)]) + (set-empty? (set-remove m-supported-by n-id)))) + n-id)) +(- (length starting-blocks) (length vulnerable-blocks)) + +;; part 2 +(for/sum ([n (in-list vulnerable-blocks)]) + (for/fold ([fallen (set n)] + [bricks (set n)] + #:result (~> fallen set-count sub1)) + ([_ (in-naturals)]) + #:break (set-empty? bricks) + (define bricks-above + (for*/set + ([brick (in-set bricks)] + [supporting (in-set (hash-ref supporting-blocks brick))] + #:when (for/and ([supports (in-set (hash-ref supported-by-blocks supporting))]) + (set-member? fallen supports))) + supporting)) + (values (set-union fallen bricks-above) bricks-above)))
\ No newline at end of file diff --git a/racket/aoc2023/day-23/day-23.rkt b/racket/aoc2023/day-23/day-23.rkt new file mode 100644 index 0000000..c048013 --- /dev/null +++ b/racket/aoc2023/day-23/day-23.rkt @@ -0,0 +1,89 @@ +#lang racket + +(require advent-of-code + threading + graph) + +(define input (fetch-aoc-input (find-session) 2023 23 #:cache #true)) +(define trails + (for*/hash ([(row r) (in-indexed (string-split input "\n"))] + [(col c) (in-indexed row)] + #:when (not (equal? col #\#))) + ; for now, we don't actually need to detect paths and slopes, just not-rocks + ; in part 1, all forks in the road go right or down, so we can just infer the path + ; direction from the shape of the junction and form a network of junctions from there + ; in part 2, the slopes are removed anyway + (values (cons (add1 r) (add1 c)) col))) + +(define max-row (~> input (string-split "\n") length)) + +(define start (findf (λ (p) (= (car p) 1)) (hash-keys trails))) +(define end (findf (λ (p) (= (car p) max-row)) (hash-keys trails))) + +(define (get-neighbors posn type) + (match-define (cons r c) posn) + (match type + ['junction + (~> (set (cons (add1 r) c) (cons r (add1 c))))] + [_ + (~> (list (cons (add1 r) c) (cons (sub1 r) c) (cons r (add1 c)) (cons r (sub1 c))) + (filter (curry hash-has-key? trails) _) + list->set)])) + +(define junction-points + (for/set ([(k v) (in-hash trails)] + #:when (not (= (set-count (get-neighbors k v)) 2))) + k)) + +(define trails-with-junctions + (for/hash ([k (in-hash-keys trails)]) + (cond + [(set-member? junction-points k) (values k 'junction)] + [else (values k 'trail)]))) + +(define (walk-to-next-junction start current [length 1] [seen (set start)]) + (define next (~> current + (get-neighbors _ 'trail) + (set-subtract seen) set-first)) + (cond + [(equal? (hash-ref trails-with-junctions next) 'junction) + (list (- (add1 length)) start next)] ; weird format is due to graph library + [else + (walk-to-next-junction start next (add1 length) (set-add seen current))])) + +(define routes-to-junctions + (for*/list ([j (in-set junction-points)] + [neighbor (in-set (get-neighbors j 'junction))] + #:when (hash-has-key? trails neighbor)) + (walk-to-next-junction j neighbor))) + +;; part 1 -- using graph library for Bellman-Ford on negative weighted graph +;; Bellman-Ford finds the shortest path, but negating all the path weights +;; will give us the longest path instead +;; +;; unlike Dijkstra which can't handle negative path lengths, Bellman-Ford +;; works as long as the graph is currently directed and acyclic +(define slippery-trail (weighted-graph/directed routes-to-junctions)) +(match-define-values (distances _) (bellman-ford slippery-trail start)) +(- (hash-ref distances end)) + +;; part 2 -- rolling my own DFS that can reject seen junctions and dead ends +(define routes-to-junctions-with-traction + (for/fold ([trails (hash)]) ([route (in-list routes-to-junctions)]) + (match-define (list (app - weight) from to) route) + (~> trails + (hash-update _ from (curry cons (cons to weight)) '()) + (hash-update _ to (curry cons (cons from weight)) '())))) + +(define (dfs g from to [acc 0] [seen (set from)]) + (cond + [(equal? from to) acc] + [else + (define choices (filter (λ (path) (not (set-member? seen (car path)))) (hash-ref g from))) + (if (empty? choices) 0 ; long dead-ends don't count + (for/fold ([best acc]) + ([path (in-list choices)] + #:do [(match-define (cons next dist) path)]) + (max best (dfs g next to (+ acc dist) (set-add seen next)))))])) + +(dfs routes-to-junctions-with-traction start end)
\ No newline at end of file diff --git a/racket/aoc2023/day-24/day-24a.rkt b/racket/aoc2023/day-24/day-24a.rkt new file mode 100644 index 0000000..31f526d --- /dev/null +++ b/racket/aoc2023/day-24/day-24a.rkt @@ -0,0 +1,51 @@ +#lang rosette + +(require advent-of-code + threading) + +(struct hail (posn vel) #:transparent) +(struct posn (x y z) #:transparent) +(struct vel (x y z) #:transparent) + +(define input (fetch-aoc-input (find-session) 2023 24 #:cache #true)) + +(define LOWER-BOUND 200000000000000) +(define UPPER-BOUND 400000000000000) + +(define (->struct f str) + (~> str (string-split _ ",") (map (λ~> string-trim string->number) _) (apply f _))) + +(define (parse-hail-record str) + (match-define (list p v) (string-split str " @ ")) + (hail (->struct posn p) + (->struct vel v))) + +(define hail-paths + (for/list ([hail (in-list (string-split input "\n"))]) + (parse-hail-record hail))) + +;; part 1 +(define (valid-intersection? h1 h2) + (match-define (hail (posn x1 y1 _) (vel vx1 vy1 _)) h1) + (match-define (hail (posn x2 y2 _) (vel vx2 vy2 _)) h2) + (cond + [(= (* vy1 vx2) (* vx1 vy2)) #f] + [else + (define t1 (/ (- (* vy2 (- x1 x2)) (* vx2 (- y1 y2))) + (- (* vy1 vx2) (* vx1 vy2)))) + (define t2 (/ (- (* vy1 (- x2 x1)) (* vx1 (- y2 y1))) + (- (* vy2 vx1) (* vx2 vy1)))) + + (define x (+ x1 (* t1 vx1))) + (define y (+ y1 (* t1 vy1))) + + (and (<= LOWER-BOUND x UPPER-BOUND) + (<= LOWER-BOUND y UPPER-BOUND) + (<= 0 t1) + (<= 0 t2))])) + +(for/sum ([(trial-paths) (in-combinations hail-paths 2)] ; + #:when (apply valid-intersection? trial-paths)) + 1) + +;; part 2 - see day-24b.rkt diff --git a/racket/aoc2023/day-24/day-24b.rkt b/racket/aoc2023/day-24/day-24b.rkt new file mode 100644 index 0000000..b106b30 --- /dev/null +++ b/racket/aoc2023/day-24/day-24b.rkt @@ -0,0 +1,37 @@ +#lang rosette + +(require advent-of-code + threading) + +(struct hail (posn vel)) +(struct posn (x y z)) +(struct vel (x y z)) + +(define input (fetch-aoc-input (find-session) 2023 24 #:cache #true)) + +(define (->struct f str) + (~> str (string-split _ ",") (map (λ~> string-trim string->number) _) (apply f _))) + +(define (parse-hail-record str) + (match-define (list p v) (string-split str " @ ")) + (hail (->struct posn p) (->struct vel v))) + +(define hail-paths + (for/list ([hail (in-list (string-split input "\n"))] ; + [_ (in-range 3)]) + (parse-hail-record hail))) + +;; part 1 - see day-24a.rkt +;; part 2 + +(define-symbolic px py pz vx vy vz integer?) + +(define sol + (solve ; + (for ([path (in-list hail-paths)]) + (define-symbolic* t integer?) + (assert (= (+ px (* vx t)) (+ (~> path hail-posn posn-x) (* (~> path hail-vel vel-x) t)))) + (assert (= (+ py (* vy t)) (+ (~> path hail-posn posn-y) (* (~> path hail-vel vel-y) t)))) + (assert (= (+ pz (* vz t)) (+ (~> path hail-posn posn-z) (* (~> path hail-vel vel-z) t))))))) + +(evaluate (+ px py pz) sol) diff --git a/racket/aoc2023/day-25/day-25.rkt b/racket/aoc2023/day-25/day-25.rkt new file mode 100644 index 0000000..aa32e43 --- /dev/null +++ b/racket/aoc2023/day-25/day-25.rkt @@ -0,0 +1,43 @@ +#lang racket + +(require advent-of-code + threading + graph) + +(define input + (~> (fetch-aoc-input (find-session) 2023 25 #:cache #true) + (string-split "\n") + (map (curryr string-split ": ") _))) + +(define all-wires + (for*/list ([wire-diagram (in-list input)] [devices (in-list (string-split (second wire-diagram)))]) + (list (car wire-diagram) devices))) + +;; instead of trying to solve the minimum cut problem, I generated the graph and +;; rendered it in graphviz: + +; (define out (open-output-file "graphviz")) +; (~> all-wires +; unweighted-graph/undirected +; graphviz +; (display out)) +; (close-output-port out) + +;; the bottleneck is very obvious on the graph -- +;; there's two large clusters of nodes, connected by just three edges +;; +;; from the graphviz output, the three critical wires are +;; cpq-hlx +;; hqp-spk +;; chr-zlx + +(define remove-these-three '(("cpq" "hlx") ("hqp" "spk") ("chr" "zlx"))) +(define cut-wires + (for/list ([wire (in-list all-wires)] #:unless (member (sort wire string<?) remove-these-three)) + wire)) + +(~> cut-wires + unweighted-graph/undirected + scc + (map length _) + (apply * _))
\ No newline at end of file diff --git a/racket/leetcode/lc-1018-binary-prefix.rkt b/racket/leetcode/lc-1018-binary-prefix.rkt new file mode 100644 index 0000000..fa82681 --- /dev/null +++ b/racket/leetcode/lc-1018-binary-prefix.rkt @@ -0,0 +1,11 @@ +#lang racket +(define/contract (prefixes-div-by5 A) + (-> (listof exact-integer?) (listof boolean?)) + (define ns (make-vector (length A) #false)) + (for/fold ([acc 0]) + ([b (in-list A)] + [i (in-naturals)]) + (let ([test-val (remainder (+ (* 2. acc) b) 5)]) + (when (= 0 test-val) (vector-set! ns i #true)) + test-val)) + (vector->list ns))
\ No newline at end of file diff --git a/racket/leetcode/lc-1037-boomerang.rkt b/racket/leetcode/lc-1037-boomerang.rkt new file mode 100644 index 0000000..fd95695 --- /dev/null +++ b/racket/leetcode/lc-1037-boomerang.rkt @@ -0,0 +1,21 @@ +#lang racket +(define/contract (is-boomerang points) + (-> (listof (listof exact-integer?)) boolean?) + (match points + [(list-no-order a b c) #:when (equal? a b) #false] ; Are any two points the same? + [(list (list x _) (list x _) (list x _)) #false] ; Are they on a horizontal line? + [(list (list _ y) (list _ y) (list _ y)) #false] ; Are they on a vertical line? + [(list-no-order (list x1 _) (list x2 _) (list x3 _)) ; Are two points on a horizontal line, + #:when (and (= x1 x2) ; but the third point isn't? + (not (= x1 x3))) #true] + [(list-no-order (list _ y1) (list _ y2) (list _ y3)) ; Are two points on a vertical line, + #:when (and (= y1 y2) ; but the third point isn't? + (not (= y1 y3))) #true] + [(list (list x1 y1) (list x2 y2) (list x3 y3)) ; If none of the special cases apply, + (let ([m (/ (- y2 y1) (- x2 x1))]) ; calculate the slope between two points + (not (= y3 (+ y1 (* m (- x3 x1))))))])) ; and see if the line passes through the third + +(is-boomerang '((1 1) (2 3) (3 2))) +(is-boomerang '((1 1) (2 2) (3 3))) +(is-boomerang '((0 0) (0 2) (2 1))) +(is-boomerang '((0 0) (1 1) (1 1)))
\ No newline at end of file diff --git a/racket/leetcode/lc-1185-day-of-week.rkt b/racket/leetcode/lc-1185-day-of-week.rkt new file mode 100644 index 0000000..c90a626 --- /dev/null +++ b/racket/leetcode/lc-1185-day-of-week.rkt @@ -0,0 +1,18 @@ +#lang racket +(require racket/date) + +(define day-names + (for/hash ([day-number (in-range 0 7)] + [day-name (in-list '("Sunday" + "Monday" + "Tuesday" + "Thursday" + "Friday" + "Saturday"))]) + (values day-number day-name))) + +(define/contract (day-of-the-week day month year) + (-> exact-integer? exact-integer? exact-integer? string?) + (hash-ref day-names (date-week-day + (seconds->date + (find-seconds 0 0 0 day month year))))) diff --git a/racket/leetcode/lc-1207-unique-occurences.rkt b/racket/leetcode/lc-1207-unique-occurences.rkt new file mode 100644 index 0000000..1b4d107 --- /dev/null +++ b/racket/leetcode/lc-1207-unique-occurences.rkt @@ -0,0 +1,10 @@ +#lang racket +(define/contract (unique-occurrences arr) + (-> (listof exact-integer?) boolean?) + (define occurrences (make-hash)) + (for ([n (in-list arr)]) + (hash-update! occurrences n add1 1)) + (equal? (hash-values occurrences) + (remove-duplicates (hash-values occurrences)))) + +(unique-occurrences '(1 2 2 1 1 3))
\ No newline at end of file diff --git a/racket/leetcode/lc-1221-split-a-string-balanced.rkt b/racket/leetcode/lc-1221-split-a-string-balanced.rkt new file mode 100644 index 0000000..4c75770 --- /dev/null +++ b/racket/leetcode/lc-1221-split-a-string-balanced.rkt @@ -0,0 +1,19 @@ +#lang racket +(require rackunit) + +(define/contract (balanced-string-split s) + (-> string? exact-integer?) + (for/fold ([acc 0] + [count 0] + #:result count) + ([c (string->list s)]) + (let* ([increment (case c + [(#\R) 1] + [(#\L) -1])] + [new-acc (+ increment acc)] + [new-count (case new-acc + [(0) (add1 count)] + [else count])]) + (values new-acc new-count)))) + +(check-eq? (balanced-string-split "RLRRLLRLRL") 4)
\ No newline at end of file diff --git a/racket/leetcode/lc-125-valid-palindrome.rkt b/racket/leetcode/lc-125-valid-palindrome.rkt new file mode 100644 index 0000000..ed91d08 --- /dev/null +++ b/racket/leetcode/lc-125-valid-palindrome.rkt @@ -0,0 +1,12 @@ +#lang racket + +(define/contract (is-palindrome s) + (-> string? boolean?) + (define clean-string + (string-downcase (string-replace s #rx"[^A-Za-z0-9]" ""))) + (string-prefix? (apply string-append (map string (reverse (string->list clean-string)))) + (substring clean-string + 0 + (ceiling (/ (string-length clean-string) 2))))) + +(is-palindrome "A man, a plan, a canal: Panama")
\ No newline at end of file diff --git a/racket/leetcode/lc-1295-even-number-of-digits.rkt b/racket/leetcode/lc-1295-even-number-of-digits.rkt new file mode 100644 index 0000000..9e88454 --- /dev/null +++ b/racket/leetcode/lc-1295-even-number-of-digits.rkt @@ -0,0 +1,4 @@ +#lang racket +(define/contract (find-numbers nums) + (-> (listof exact-integer?) exact-integer?) + (count (λ (n) (odd? (order-of-magnitude n))) nums))
\ No newline at end of file diff --git a/racket/leetcode/lc-1299-replace-with-greatest-to-right.rkt b/racket/leetcode/lc-1299-replace-with-greatest-to-right.rkt new file mode 100644 index 0000000..34d3eae --- /dev/null +++ b/racket/leetcode/lc-1299-replace-with-greatest-to-right.rkt @@ -0,0 +1,8 @@ +#lang racket +(define/contract (replace-elements arr) + (-> (listof exact-integer?) (listof exact-integer?)) + (cond [(= 1 (length arr)) '(-1)] + [else (cons (apply max (cdr arr)) + (replace-elements (cdr arr)))])) + +(replace-elements '(17 18 5 4 6 1))
\ No newline at end of file diff --git a/racket/leetcode/lc-1304-find-n-unique-integers.rkt b/racket/leetcode/lc-1304-find-n-unique-integers.rkt new file mode 100644 index 0000000..9b810a0 --- /dev/null +++ b/racket/leetcode/lc-1304-find-n-unique-integers.rkt @@ -0,0 +1,5 @@ +#lang racket +(define/contract (sum-zero n) + (-> exact-integer? (listof exact-integer?)) + (cond [(even? n) (remove 0 (range (/ n -2) (add1 (/ n 2))))] + [(odd? n) (range (/ (- n 1) -2) (add1 (/ (- n 1) 2)))]))
\ No newline at end of file diff --git a/racket/leetcode/lc-1436-destination-city.rkt b/racket/leetcode/lc-1436-destination-city.rkt new file mode 100644 index 0000000..ce82f08 --- /dev/null +++ b/racket/leetcode/lc-1436-destination-city.rkt @@ -0,0 +1,14 @@ +#lang racket +(define/contract (dest-city paths) + (-> (listof (listof string?)) string?) + (define city-pairs (make-hash paths)) + (define (go-to-next-city origin) + (let ([destination (hash-ref city-pairs origin #false)]) + (if destination + (go-to-next-city (car destination)) + origin))) + (go-to-next-city (caar paths))) + +(dest-city '(("London" "New York") + ("New York" "Lima") + ("Lima" "Sao Paolo")))
\ No newline at end of file diff --git a/racket/leetcode/lc-1450-students-doing-homework.rkt b/racket/leetcode/lc-1450-students-doing-homework.rkt new file mode 100644 index 0000000..14ff079 --- /dev/null +++ b/racket/leetcode/lc-1450-students-doing-homework.rkt @@ -0,0 +1,12 @@ +#lang racket +(define/contract (busy-student start-time end-time query-time) + (-> (listof exact-integer?) (listof exact-integer?) exact-integer? exact-integer?) + (count (λ (start end) + (and (start . <= . query-time) + (query-time . <= . end))) start-time end-time)) + +(busy-student '(1 2 3) '(3 2 7) 4) +(busy-student '(4) '(4) 4) +(busy-student '(9 8 7 6 5 4 3 2 1) + '(10 10 10 10 10 10 10 10 10) + 5)
\ No newline at end of file diff --git a/racket/leetcode/lc-1460-make-two-arrays-equal.rkt b/racket/leetcode/lc-1460-make-two-arrays-equal.rkt new file mode 100644 index 0000000..584ac97 --- /dev/null +++ b/racket/leetcode/lc-1460-make-two-arrays-equal.rkt @@ -0,0 +1,4 @@ +#lang racket +(define/contract (can-be-equal target arr) + (-> (listof exact-integer?) (listof exact-integer?) boolean?) + (equal? (sort target <) (sort arr <)))
\ No newline at end of file diff --git a/racket/leetcode/lc-1496-path-crossing.rkt b/racket/leetcode/lc-1496-path-crossing.rkt new file mode 100644 index 0000000..9c1941d --- /dev/null +++ b/racket/leetcode/lc-1496-path-crossing.rkt @@ -0,0 +1,25 @@ +#lang racket +(define/contract (is-path-crossing path) + (-> string? boolean?) + (for/fold ([current-x 0] + [current-y 0] + [trail (set '(0 0))] + [check #false] + #:result check) + ([step (in-list (string->list path))] + #:break check) + (let*-values + ([(new-x new-y) + (case step + [(#\N) (values current-x (add1 current-y))] + [(#\S) (values current-x (sub1 current-y))] + [(#\E) (values (add1 current-x) current-y)] + [(#\W) (values (sub1 current-x) current-y)])] + [(new-trail-point) (list new-x new-y)]) + (cond [(set-member? trail new-trail-point) + (values void void void #true)] + [else + (values new-x + new-y + (set-add trail new-trail-point) + #false)]))))
\ No newline at end of file diff --git a/racket/leetcode/lc-1700-students-unable-to-eat.rkt b/racket/leetcode/lc-1700-students-unable-to-eat.rkt new file mode 100644 index 0000000..75cc243 --- /dev/null +++ b/racket/leetcode/lc-1700-students-unable-to-eat.rkt @@ -0,0 +1,36 @@ +#lang racket +(define/contract (count-students students sandwiches) + (-> (listof exact-integer?) (listof exact-integer?) exact-integer?) + (for/fold ([sandwich-pile sandwiches] + [student-line students] + [remaining-students (length students)] + [break? #false] + #:result remaining-students) + ([i (in-naturals)] + #:break break?) + (cond [(and (empty? sandwich-pile) + (empty? student-line)) + (values void + void + remaining-students + #true)] + [(equal? (car sandwich-pile) + (car student-line)) + (values (cdr sandwich-pile) + (cdr student-line) + (sub1 remaining-students) + #false)] + [(and (not (equal? (list (car sandwich-pile)) + (remove-duplicates student-line))) + (= 1 (length (remove-duplicates student-line)))) + (values void + void + remaining-students + #true)] + [else + (values sandwich-pile + (append (cdr student-line) (list (car student-line))) + remaining-students + #false)]))) + +(count-students '(1 1 0 0) '(0 1 0 1)) diff --git a/racket/leetcode/lc-1812-chessboard-square.rkt b/racket/leetcode/lc-1812-chessboard-square.rkt new file mode 100644 index 0000000..206392c --- /dev/null +++ b/racket/leetcode/lc-1812-chessboard-square.rkt @@ -0,0 +1,7 @@ +#lang racket +(define/contract (square-is-white coordinates) + (-> string? boolean?) + (define file (first (string->list coordinates))) + (define rank (second (string->list coordinates))) + (or (and (odd? (char->integer file)) (even? (char->integer rank))) + (and (even? (char->integer file)) (odd? (char->integer rank)))))
\ No newline at end of file diff --git a/racket/leetcode/lc-1844-replace-all-digits-with-characters.rkt b/racket/leetcode/lc-1844-replace-all-digits-with-characters.rkt new file mode 100644 index 0000000..96aba6e --- /dev/null +++ b/racket/leetcode/lc-1844-replace-all-digits-with-characters.rkt @@ -0,0 +1,18 @@ +#lang racket +(define/contract (replace-digits s) + (-> string? string?) + (define/contract (shift-letter c x) + (-> char? char? char?) + (integer->char (+ (string->number (string x)) (char->integer c)))) + (define letters (string->list (string-replace s #rx"[0-9]" ""))) + (define digits (string->list (string-replace s #rx"[a-z]" ""))) + (foldl (λ (c x acc) + (if (equal? x #\X) + (string-append acc (string c)) + (string-append acc (string c) (string (shift-letter c x))))) + "" + letters + (if (= (length digits) (length letters)) + digits + (append digits '(#\X))) + ))
\ No newline at end of file diff --git a/racket/leetcode/lc-1854-max-pop-year.rkt b/racket/leetcode/lc-1854-max-pop-year.rkt new file mode 100644 index 0000000..75104f1 --- /dev/null +++ b/racket/leetcode/lc-1854-max-pop-year.rkt @@ -0,0 +1,17 @@ +#lang racket +(define/contract (maximum-population logs) + (-> (listof (listof exact-integer?)) exact-integer?) + ; make a hash table of every year encountered between the birth and death years + (define population (make-hash)) + ; for each person in the logs, + (for/list ([person (in-list logs)]) + ; for every year from birth to the year before death, + (for/list ([year (in-range (first person) (second person))]) + ; look up the year in the hash table and add 1 to its key, + ; or add the key and set its value to 1 if it doesn't exist yet + (hash-update! population year add1 1))) + ; convert the hash table to a list, + ; sort the list by year, + ; find the first element that maximizes the count, + ; and return the associated year + (car (argmax cdr (sort (hash->list population) < #:key car))))
\ No newline at end of file diff --git a/racket/leetcode/lc-2-add-two-numbers.rkt b/racket/leetcode/lc-2-add-two-numbers.rkt new file mode 100644 index 0000000..8062817 --- /dev/null +++ b/racket/leetcode/lc-2-add-two-numbers.rkt @@ -0,0 +1,35 @@ +#lang racket +; Definition for singly-linked list: + + +; val : integer? +; next : (or/c list-node? #f) +(struct list-node + (val next) #:mutable #:transparent) + +; constructor +(define (make-list-node val [next-node #f]) + (list-node val next-node)) + + +(define/contract (add-two-numbers l1 l2) + (-> (or/c list-node? #f) (or/c list-node? #f) (or/c list-node? #f)) + (define (process-list node [acc '()]) + (if (list-node-next node) + (process-list (list-node-next node) (cons (list-node-val node) acc)) + (cons (list-node-val node) acc))) + (define sum-of-lists (+ (string->number (apply ~a (process-list l1))) + (string->number (apply ~a (process-list l2))))) + (define sum-list-digits + (reverse + (map (λ (x) (string->number (string x))) + (string->list (number->string sum-of-lists))))) + (define (build-list l) + (if (empty? l) + #f + (make-list-node (car l) (build-list (cdr l))))) + (build-list sum-list-digits)) + +(define list1 (make-list-node 2 (make-list-node 4 (make-list-node 3)))) +(define list2 (make-list-node 5 (make-list-node 6 (make-list-node 4)))) +(add-two-numbers list1 list2)
\ No newline at end of file diff --git a/racket/leetcode/lc-217-contains-duplicate.rkt b/racket/leetcode/lc-217-contains-duplicate.rkt new file mode 100644 index 0000000..ca8d193 --- /dev/null +++ b/racket/leetcode/lc-217-contains-duplicate.rkt @@ -0,0 +1,12 @@ +#lang racket +(define/contract (contains-duplicate nums) + (-> (listof exact-integer?) boolean?) + (define nums-hash (make-hash)) + (define (check-next-number nums) + (cond [(empty? nums) #false] + [(hash-ref nums-hash (car nums) #false) #true] + [else (hash-set! nums-hash (car nums) #true) + (check-next-number (cdr nums))])) + (check-next-number nums)) + +(contains-duplicate '(1 2 3))
\ No newline at end of file diff --git a/racket/leetcode/lc-228-summary-ranges.rkt b/racket/leetcode/lc-228-summary-ranges.rkt new file mode 100644 index 0000000..9140895 --- /dev/null +++ b/racket/leetcode/lc-228-summary-ranges.rkt @@ -0,0 +1,27 @@ +#lang racket +(define (summary-ranges nums) + (define range-pairs + (cond + [(empty? nums) '()] + [(empty? (cdr nums)) (list (cons (car nums) (car nums)))] + [else (for/fold ([ranges '()] + [open-pair (first nums)] + [prev-num (first nums)] + #:result (append ranges (list (cons open-pair prev-num)))) + ([i (cdr nums)]) + (cond [(= (add1 prev-num) i) + (values ranges + open-pair + i)] + [else + (values (append ranges (list (cons open-pair prev-num))) + i + i)]))])) + (for/list ([p (in-list range-pairs)]) + (cond [(= (car p) (cdr p)) (format "~a" (car p))] + [else (format "~a->~a" (car p) (cdr p))]))) + +(summary-ranges '(0 1 2 4 5 7)) +(summary-ranges '(0 2 3 4 6 8 9)) +(summary-ranges '()) +(summary-ranges '(0))
\ No newline at end of file diff --git a/racket/leetcode/lc-290-word-pattern.rkt b/racket/leetcode/lc-290-word-pattern.rkt new file mode 100644 index 0000000..77cdba0 --- /dev/null +++ b/racket/leetcode/lc-290-word-pattern.rkt @@ -0,0 +1,23 @@ +#lang racket +(define match-string "abba") +(define a "dog") +(define b "cat") +(define Σ "dog cat cat dog") + +(define/contract (word-pattern pattern s) + (-> string? string? boolean?) + (define pattern-list (map string (string->list pattern))) + (define s-list (string-split s)) + (define match-hash (make-hash)) + (if (= (length pattern-list) (length s-list)) + (for/and ([pattern-part pattern-list] + [s-part s-list]) + (cond [(and (not (hash-has-key? match-hash pattern-part)) + (member s-part (hash-values match-hash))) #f] + [(not (hash-has-key? match-hash pattern-part)) + (hash-set! match-hash pattern-part s-part) #t] + [(string=? (hash-ref match-hash pattern-part) s-part) #t] + [else #f])) + #f)) + +(word-pattern match-string Σ)
\ No newline at end of file diff --git a/racket/leetcode/lc-345-reverse-vowels.rkt b/racket/leetcode/lc-345-reverse-vowels.rkt new file mode 100644 index 0000000..c05bf2d --- /dev/null +++ b/racket/leetcode/lc-345-reverse-vowels.rkt @@ -0,0 +1,9 @@ +#lang racket + +(define/contract (reverse-vowels s) + (-> string? string?) + (define vowels-only + (string-replace s #rx"[^aeiouAEIOU]" "")) + (define consonants-with-placeholders + (string-replace s #rx"[aeiouAEIOU]" "~a")) + (apply format consonants-with-placeholders (reverse (string->list vowels-only))))
\ No newline at end of file diff --git a/racket/leetcode/lc-349-intersection-of-2-arrays.rkt b/racket/leetcode/lc-349-intersection-of-2-arrays.rkt new file mode 100644 index 0000000..14d56ca --- /dev/null +++ b/racket/leetcode/lc-349-intersection-of-2-arrays.rkt @@ -0,0 +1,5 @@ +#lang racket + +(define/contract (intersection nums1 nums2) + (-> (listof exact-integer?) (listof exact-integer?) (listof exact-integer?)) + (set-intersect nums1 nums2))
\ No newline at end of file diff --git a/racket/leetcode/lc-36-valid-sudoku.rkt b/racket/leetcode/lc-36-valid-sudoku.rkt new file mode 100644 index 0000000..915b533 --- /dev/null +++ b/racket/leetcode/lc-36-valid-sudoku.rkt @@ -0,0 +1,33 @@ +#lang racket + +(define (pos board r c) + (list-ref (list-ref board r) c)) + +(define (scan-for-duplicates array) + (andmap (λ (row) (not (check-duplicates row))) + (map (curry filter-not (curry equal? ".")) array))) + +(define (check-rows board) + (scan-for-duplicates board)) + +(define (check-cols board) + (scan-for-duplicates (apply map list board))) + +(define (check-boxes board) + (define boxes-to-lists + (for*/list ([r (in-list '(0 3 6))] + [c (in-list '(0 3 6))]) + (for*/list ([box-r (in-range r (+ r 3))] + [box-c (in-range c (+ c 3))] + #:unless (equal? "." (pos board box-r box-c))) + (pos board box-r box-c)))) + (scan-for-duplicates boxes-to-lists)) + +(define/contract (is-valid-sudoku board) + (-> (listof (listof string?)) boolean?) + (and (check-rows board) + (check-cols board) + (check-boxes board))) + +(define valid-sudoku '[["5" "3" "." "." "7" "." "." "." "."] ["6" "." "." "1" "9" "5" "." "." "."] ["." "9" "8" "." "." "." "." "6" "."] ["8" "." "." "." "6" "." "." "." "3"] ["4" "." "." "8" "." "3" "." "." "1"] ["7" "." "." "." "2" "." "." "." "6"] ["." "6" "." "." "." "." "2" "8" "."] ["." "." "." "4" "1" "9" "." "." "5"] ["." "." "." "." "8" "." "." "7" "9"]]) +(define invalid-sudoku '[["8" "3" "." "." "7" "." "." "." "."] ["6" "." "." "1" "9" "5" "." "." "."] ["." "9" "8" "." "." "." "." "6" "."] ["8" "." "." "." "6" "." "." "." "3"] ["4" "." "." "8" "." "3" "." "." "1"] ["7" "." "." "." "2" "." "." "." "6"] ["." "6" "." "." "." "." "2" "8" "."] ["." "." "." "4" "1" "9" "." "." "5"] ["." "." "." "." "8" "." "." "7" "9"]])
\ No newline at end of file diff --git a/racket/leetcode/lc-415-add-strings.rkt b/racket/leetcode/lc-415-add-strings.rkt new file mode 100644 index 0000000..e140155 --- /dev/null +++ b/racket/leetcode/lc-415-add-strings.rkt @@ -0,0 +1,28 @@ +#lang racket + +(define/contract (add-strings num1 num2) + (-> string? string? string?) + (define (char->integer c) + ((compose string->number string) c)) + (define pad-length + (add1 (apply max (map string-length (list num1 num2))))) + (define (pad-with-zeroes n) + (~a n + #:align 'right + #:min-width pad-length + #:pad-string "0")) + (define (string-reverse s) + ((compose list->string reverse string->list) s)) + (define raw-sum + (for/fold ([sum-string ""] + [carry 0] + #:result sum-string) + ([n1 (string-reverse (pad-with-zeroes num1))] + [n2 (string-reverse (pad-with-zeroes num2))]) + (let* ([digit-sum (+ carry (char->integer n1) (char->integer n2))] + [sum-place (number->string (modulo digit-sum 10))] + [sum-carry (quotient digit-sum 10)]) + (values (string-append sum-place sum-string) + sum-carry)))) + (cond [(equal? raw-sum "00") "0"] + [else (string-trim raw-sum "0" #:repeat? #t #:right? #f)]))
\ No newline at end of file diff --git a/racket/leetcode/lc-43-multiply-strings.rkt b/racket/leetcode/lc-43-multiply-strings.rkt new file mode 100644 index 0000000..dac8c31 --- /dev/null +++ b/racket/leetcode/lc-43-multiply-strings.rkt @@ -0,0 +1,28 @@ +#lang racket + +(define/contract (char-digit->integer c) + (-> char? integer?) + (- (char->integer c) 48)) + +(define/contract (integer->string-digit n) + (-> integer? string?) + (string (integer->char (+ n 48)))) + +(define/contract (number->string1 n [acc ""]) + (->* (integer?) (string?) string?) + (cond [(and (= n 0) (equal? acc "")) "0"] + [(= n 0) acc] + [else (number->string1 + (quotient n 10) + (string-append (integer->string-digit (remainder n 10)) acc))])) + +(define/contract (multiply num1 num2) + (-> string? string? string?) + (define multiplication-steps + (for/list ([n1 (in-string num1)] + [place1 (in-range (sub1 (string-length num1)) -1 -1)]) + (for/list ([n2 (in-string num2)] + [place2 (in-range (sub1 (string-length num2)) -1 -1)]) + (apply * (append (map char-digit->integer (list n1 n2)) + (list (expt 10 place1) (expt 10 place2))))))) + (number->string1 (apply + (flatten multiplication-steps))))
\ No newline at end of file diff --git a/racket/leetcode/lc-476-number-complement.rkt b/racket/leetcode/lc-476-number-complement.rkt new file mode 100644 index 0000000..724bb47 --- /dev/null +++ b/racket/leetcode/lc-476-number-complement.rkt @@ -0,0 +1,11 @@ +#lang racket + +(define (flip-bit bit) + (cond [(char=? bit #\1) #\0] + [(char=? bit #\0) #\1])) + +(define/contract (find-complement num) + (-> exact-integer? exact-integer?) + (define num-binary-list + (string->list (number->string num 2))) + (string->number (apply ~a (map flip-bit num-binary-list)) 2))
\ No newline at end of file diff --git a/racket/leetcode/lc-500-keyboard-row.rkt b/racket/leetcode/lc-500-keyboard-row.rkt new file mode 100644 index 0000000..5f13143 --- /dev/null +++ b/racket/leetcode/lc-500-keyboard-row.rkt @@ -0,0 +1,23 @@ +#lang racket + +(define keyboard-rows (list "qwertyuiop" + "asdfghjkl" + "zxcvbnm")) + +(define keyboard-row-sets + (for/list ([row keyboard-rows]) + (list->set (map string (string->list row))))) + +(define/contract (find-words words) + (-> (listof string?) (listof string?)) + (define word-checks + (for/list ([w words]) + (define word-set + (list->set (map string (string->list (string-downcase w))))) + (if (for/or ([row keyboard-row-sets]) + (subset? word-set row)) + w + '()))) + (filter-not empty? word-checks)) + +(find-words '("Hello" "Alaska" "Dad" "Peace"))
\ No newline at end of file diff --git a/racket/leetcode/lc-504-base7.rkt b/racket/leetcode/lc-504-base7.rkt new file mode 100644 index 0000000..3e75052 --- /dev/null +++ b/racket/leetcode/lc-504-base7.rkt @@ -0,0 +1,16 @@ +#lang racket + +(define/contract (convert-to-base7 num) + (-> exact-integer? string?) + (define (max-base-power n base [pow 1]) + (cond [(n . = . (expt base pow)) pow] + [(n . < . (expt base pow)) (sub1 pow)] + [else (max-base-power n base (add1 pow))])) + (define (add-next-digit n pow acc) + (cond [(= pow 0) (string-append acc (number->string n))] + [else (add-next-digit (remainder n (expt 7 pow)) + (sub1 pow) + (string-append acc + (number->string (quotient n (expt 7 pow)))))])) + (string-append (if (negative? num) "-" "") + (add-next-digit (abs num) (max-base-power (abs num) 7) "")))
\ No newline at end of file diff --git a/racket/leetcode/lc-520-detect-capital.rkt b/racket/leetcode/lc-520-detect-capital.rkt new file mode 100644 index 0000000..80b5f7e --- /dev/null +++ b/racket/leetcode/lc-520-detect-capital.rkt @@ -0,0 +1,12 @@ +#lang racket + +(define/contract (detect-capital-use word) + (-> string? boolean?) + (if + (member word (list (string-upcase word) + (string-downcase word) + (string-titlecase word))) + #true + #false)) + +(detect-capital-use "Google")
\ No newline at end of file diff --git a/racket/leetcode/lc-551-student-attendance-record-1.rkt b/racket/leetcode/lc-551-student-attendance-record-1.rkt new file mode 100644 index 0000000..c5f1456 --- /dev/null +++ b/racket/leetcode/lc-551-student-attendance-record-1.rkt @@ -0,0 +1,11 @@ +#lang racket + +(define/contract (check-record s) + (-> string? boolean?) + (define s-list (map string (string->list s))) + (cond [(<= 2 (count (curry string=? "A") s-list)) #false] + [(string-contains? s "LLL") #false] + [else #true])) + +(check-record "PPALLP") +(check-record "PPALLL")
\ No newline at end of file diff --git a/racket/leetcode/lc-58-length-of-last-word.rkt b/racket/leetcode/lc-58-length-of-last-word.rkt new file mode 100644 index 0000000..716df90 --- /dev/null +++ b/racket/leetcode/lc-58-length-of-last-word.rkt @@ -0,0 +1,7 @@ +#lang racket + +(define/contract (length-of-last-word s) + (-> string? exact-integer?) + (if (empty? (string-split s)) + 0 + (string-length (last (string-split s)))))
\ No newline at end of file diff --git a/racket/leetcode/lc-645-set-mismatch.rkt b/racket/leetcode/lc-645-set-mismatch.rkt new file mode 100644 index 0000000..a9d9a61 --- /dev/null +++ b/racket/leetcode/lc-645-set-mismatch.rkt @@ -0,0 +1,24 @@ +#lang racket + +(define/contract (find-error-nums nums) + (-> (listof exact-integer?) (listof exact-integer?)) + (define nums-set (list->set nums)) + (define range-set (apply set (range 1 (+ 2 (set-count nums-set))))) + (define missing-num (first (set->list (set-subtract range-set nums-set)))) + (define necessary-num + (if (set-member? nums-set (- missing-num 1)) + (+ missing-num 1) + (- missing-num 1))) + (list missing-num necessary-num)) + +(find-error-nums '(1 2 2 4)) + +(define fact-stream + (letrec ([f (lambda (x y) + (cond + [(zero? (modulo (- y 1) 3)) (cons (* 3 x) (lambda() (f (* x + y) (+ y 1))))] + [else (cons x (lambda() (f (* x y) (+ y 1))))]) + [else (cons x (lambda() (f (* x y) (+ y 1))))] + (lambda () (f 1 2)) + )])))
\ No newline at end of file diff --git a/racket/leetcode/lc-657-robot-return.rkt b/racket/leetcode/lc-657-robot-return.rkt new file mode 100644 index 0000000..908605a --- /dev/null +++ b/racket/leetcode/lc-657-robot-return.rkt @@ -0,0 +1,17 @@ +#lang racket + +(define/contract (judge-circle moves) + (-> string? boolean?) + (equal? '(0 0) + (for/fold ([y-pos 0] + [x-pos 0] + #:result (list y-pos x-pos)) + ([move (map string (string->list moves))]) + (values (case move + [("U") (add1 y-pos)] + [("D") (sub1 y-pos)] + [else y-pos]) + (case move + [("L") (add1 x-pos)] + [("R") (sub1 x-pos)] + [else x-pos])))))
\ No newline at end of file diff --git a/racket/leetcode/lc-68-justification.rkt b/racket/leetcode/lc-68-justification.rkt new file mode 100644 index 0000000..537e2c5 --- /dev/null +++ b/racket/leetcode/lc-68-justification.rkt @@ -0,0 +1,44 @@ +#lang racket +(define/contract (full-justify words max-width) + (-> (listof string?) exact-integer? (listof string?)) + + (define/contract (justify-line line [last-line #f]) + (->* ((listof string?)) (boolean?) string?) + (define gaps (sub1 (length line))) + (cond [last-line + (~a (string-join line " ") #:min-width max-width)] ; Right-pad the last line + [(= 1 (length line)) + (~a (first line) #:min-width max-width)] ; Right-pad single-word lines + [else + (let* ([words-length (apply + (map string-length line))] + [spacing-length (- max-width words-length)] ; How many spaces do we need? + [spacing-list (make-list gaps 1)] ; Every gap needs to be at least + [distribute (- spacing-length gaps)] ; 1 space long, so we need to + [distributed-spaces ; distribute the excess + (for/list ([space (in-list spacing-list)] + [i (in-naturals 1)]) + (+ space + (quotient distribute gaps) ; Add an equal number of spaces + (if (<= i ; to each gap, then add the + (modulo distribute gaps)) 1 0)))]) ; remainder at the front + (apply string-append + (append (map (λ (w s) + (string-append ; Knit together the first (n-1) + w (make-string s #\space))) ; words and gaps, then append + (drop-right line 1) ; the final word at the end + distributed-spaces) + (take-right line 1))))])) + + (for/fold ([lines '()] ; List of justified lines + [line-acc '()] ; Words to fit into the next line + #:result (append lines ; Only return the list of lines + (list (justify-line line-acc #t)))) ; and append the final line + ([word (in-list words)]) + (let* ([candidate-acc (append line-acc (list word))] + [candidate-length + (string-length (string-join candidate-acc " "))]) + (if (candidate-length . <= . max-width) ; If the word fits into the line, + (values lines ; keep the current line list + candidate-acc) ; and add it to the accumulator + (values (append lines (list (justify-line line-acc))) ; Otherwise, wrap up this line + (list word)))))) ; and start a new accumulator
\ No newline at end of file diff --git a/racket/leetcode/lc-690-employee-importance.rkt b/racket/leetcode/lc-690-employee-importance.rkt new file mode 100644 index 0000000..1fb3fcc --- /dev/null +++ b/racket/leetcode/lc-690-employee-importance.rkt @@ -0,0 +1,14 @@ +#lang racket + +(define/contract (sum-even-after-queries A queries) + (-> (listof exact-integer?) + (listof (listof exact-integer?)) + (listof exact-integer?)) + (define array (list->vector A)) + (for/list ([query (in-list queries)]) + (vector-set! array + (second query) + (+ (first query) (vector-ref array (second query)))) + (for/sum ([element (vector-filter even? array)]) element))) + +(sum-even-after-queries '[1 2 3 4] '[[1 0] [-3 1] [-4 0] [2 3]])
\ No newline at end of file diff --git a/racket/leetcode/lc-717-1bit-and-2bit.rkt b/racket/leetcode/lc-717-1bit-and-2bit.rkt new file mode 100644 index 0000000..d9988ec --- /dev/null +++ b/racket/leetcode/lc-717-1bit-and-2bit.rkt @@ -0,0 +1,12 @@ +#lang racket + +(define/contract (is-one-bit-character bits) + (-> (listof exact-integer?) boolean?) + (define/match (check-next-character x . xs) + [(0 '()) #true] + [(1 (list _ ..2)) (apply check-next-character (cdr xs))] + [(0 (list _ ..1)) (apply check-next-character xs)] + [(_ _) #false]) + (apply check-next-character bits)) + +(is-one-bit-character (list 1 1 0 1 0))
\ No newline at end of file diff --git a/racket/leetcode/lc-745-prefix-suffix.rkt b/racket/leetcode/lc-745-prefix-suffix.rkt new file mode 100644 index 0000000..b01e3bb --- /dev/null +++ b/racket/leetcode/lc-745-prefix-suffix.rkt @@ -0,0 +1,27 @@ +#lang racket +(define word-filter% + (class object% + (super-new) + + ; words : (listof string?) + (init-field + words) ; Take in the provided dictionary. + (define word-ends-hash (make-hash)) ; Make an empty hash table. + + (for/list ([w (in-list words)] ; For each word in the dictionary, + [index (in-naturals)]) ; and its corresponding index, + (define len (string-length w)) ; calculate its length, + (for*/list ([head (in-range 1 (min 11 (add1 len)))] ; and for every combination of head length + [tail (in-range 1 (min 11 (add1 len)))]) ; and tail length + (hash-set! word-ends-hash ; from 1 to the max. affix length, + (list (substring w 0 head) ; set the key to the list containing + (substring w (- len tail) len)) ; the prefix and suffix + index))) ; and map it to the word's index. + + ; f : string? string? -> exact-integer? + (define/public (f prefix suffix) ; Return the mapped value for the affixes + (hash-ref word-ends-hash (list prefix suffix) -1)))) ; or -1 if it doesn't exist. + +;; Your word-filter% object will be instantiated and called as such: +;; (define obj (new word-filter% [words words])) +;; (define param_1 (send obj f prefix suffix))
\ No newline at end of file diff --git a/racket/leetcode/lc-747-largest-number-twice.rkt b/racket/leetcode/lc-747-largest-number-twice.rkt new file mode 100644 index 0000000..cea931b --- /dev/null +++ b/racket/leetcode/lc-747-largest-number-twice.rkt @@ -0,0 +1,15 @@ +#lang racket +(define/contract (dominant-index nums) + (-> (listof exact-integer?) exact-integer?) + (if (empty? (cdr nums)) + 0 + (let* ([indexed-list + (map cons nums (range (length nums)))] + [sorted-indexed-list + (sort indexed-list > #:key car)]) + (if ((car (first sorted-indexed-list)) . >= . (* 2 (car (second sorted-indexed-list)))) + (cdr (first sorted-indexed-list)) + -1)))) + +(dominant-index '(3 6 1 0)) +(dominant-index '(0))
\ No newline at end of file diff --git a/racket/leetcode/lc-766-toeplitz-matrix.rkt b/racket/leetcode/lc-766-toeplitz-matrix.rkt new file mode 100644 index 0000000..5606d2a --- /dev/null +++ b/racket/leetcode/lc-766-toeplitz-matrix.rkt @@ -0,0 +1,9 @@ +#lang racket + +(define/contract (is-toeplitz-matrix matrix) + (-> (listof (listof exact-integer?)) boolean?) + (cond [(empty? (cdr matrix)) #true] + [(equal? (drop-right (car matrix) 1) + (drop (cadr matrix) 1)) + (is-toeplitz-matrix (cdr matrix))] + [else #false]))
\ No newline at end of file diff --git a/racket/leetcode/lc-771-jewels-and-stones.rkt b/racket/leetcode/lc-771-jewels-and-stones.rkt new file mode 100644 index 0000000..40ccf14 --- /dev/null +++ b/racket/leetcode/lc-771-jewels-and-stones.rkt @@ -0,0 +1,6 @@ +#lang racket + +(define/contract (num-jewels-in-stones jewels stones) + (-> string? string? exact-integer?) + (for/sum ([jewel (in-string jewels)]) + (count (curry char=? jewel) (string->list stones))))
\ No newline at end of file diff --git a/racket/leetcode/lc-788-rotated-digits.rkt b/racket/leetcode/lc-788-rotated-digits.rkt new file mode 100644 index 0000000..79400b8 --- /dev/null +++ b/racket/leetcode/lc-788-rotated-digits.rkt @@ -0,0 +1,17 @@ +#lang racket + +(define/contract (rotated-digits max-n) + (-> exact-integer? exact-integer?) + (for/fold ([good-number-count 0]) + ([n (in-range 1 (add1 max-n))]) + (if (is-good? n) + (add1 good-number-count) + good-number-count))) + +(define/contract (is-good? test-number) + (-> exact-integer? boolean?) + (define test-string (number->string test-number)) + (match test-string + [(regexp #rx"^[018]*$") #false] + [(regexp #rx"^[0125689]*$") #true] + [_ #false]))
\ No newline at end of file diff --git a/racket/leetcode/lc-796-rotate-string.rkt b/racket/leetcode/lc-796-rotate-string.rkt new file mode 100644 index 0000000..b51d9e3 --- /dev/null +++ b/racket/leetcode/lc-796-rotate-string.rkt @@ -0,0 +1,6 @@ +#lang racket/base +(define/contract (rotate-string A B) + (-> string? string? boolean?) + (define doubled-A (string-append A A)) + (and (= (string-length A) (string-length B)) + (string-contains? doubled-A B)))
\ No newline at end of file diff --git a/racket/leetcode/lc-819-most-common-word.rkt b/racket/leetcode/lc-819-most-common-word.rkt new file mode 100644 index 0000000..68a89c3 --- /dev/null +++ b/racket/leetcode/lc-819-most-common-word.rkt @@ -0,0 +1,14 @@ +#lang racket +(define/contract (most-common-word paragraph banned) + (-> string? (listof string?) string?) + (define word-count-hash (make-hash)) + (define banned-word-hash + (apply hash (flatten (map (λ (w) (cons w 'banned)) banned)))) + (define word-list + ((compose string-split string-downcase) + (string-replace paragraph #px"[^A-Za-z[:space:]]" " "))) + (for/list ([word (in-list word-list)]) + (cond [(hash-has-key? banned-word-hash word) void] + [else (hash-update! word-count-hash word add1 0)])) + (car (argmax cdr (hash->list word-count-hash)))) + diff --git a/racket/leetcode/lc-836-rectangle-overlap.rkt b/racket/leetcode/lc-836-rectangle-overlap.rkt new file mode 100644 index 0000000..ecdfb56 --- /dev/null +++ b/racket/leetcode/lc-836-rectangle-overlap.rkt @@ -0,0 +1,14 @@ +#lang racket +(define (rectangle-area lst) + (match-define (list x1 y1 x2 y2) lst) + (* (- x2 x1) (- y2 y1))) + +(define/contract (is-rectangle-overlap rec1 rec2) + (-> (listof exact-integer?) (listof exact-integer?) boolean?) + (cond [(or (= 0 (rectangle-area rec1)) + (= 0 (rectangle-area rec2))) #false] + [(not (or ((list-ref rec1 2) . <= . (list-ref rec2 0)) + ((list-ref rec1 3) . <= . (list-ref rec2 1)) + ((list-ref rec1 0) . >= . (list-ref rec2 2)) + ((list-ref rec1 1) . >= . (list-ref rec2 3)))) #true] + [else #false]))
\ No newline at end of file diff --git a/racket/leetcode/lc-844-backspace-string-compare.rkt b/racket/leetcode/lc-844-backspace-string-compare.rkt new file mode 100644 index 0000000..a07ec3c --- /dev/null +++ b/racket/leetcode/lc-844-backspace-string-compare.rkt @@ -0,0 +1,17 @@ +#lang racket + +(define/contract (process-backspace-string strng) + (-> string? string?) + (apply ~a (for/fold ([str-out '()] + #:result (reverse str-out)) + ([character (in-string strng)]) + (case character + [(#\#) (if (empty? str-out) + str-out + (cdr str-out))] + [else (cons character str-out)])))) + +(define/contract (backspace-compare s t) + (-> string? string? boolean?) + (equal? (process-backspace-string s) + (process-backspace-string t)))
\ No newline at end of file diff --git a/racket/leetcode/lc-896-monotonic-array.rkt b/racket/leetcode/lc-896-monotonic-array.rkt new file mode 100644 index 0000000..fa43244 --- /dev/null +++ b/racket/leetcode/lc-896-monotonic-array.rkt @@ -0,0 +1,16 @@ +#lang racket +(define/contract (is-monotonic test-list) + (-> (listof exact-integer?) boolean?) + (cond [(empty? (cdr test-list)) #true] + [((car test-list) . > . (cadr test-list)) + (is-monotonic-direction test-list >=)] + [((car test-list) . < . (cadr test-list)) + (is-monotonic-direction test-list <=)] + [else (is-monotonic (cdr test-list))])) + +(define/contract (is-monotonic-direction test-list dir) + (-> (listof exact-integer?) procedure? boolean?) + (cond [(empty? (cdr test-list)) #true] + [((car test-list) . dir . (cadr test-list)) + (is-monotonic-direction (cdr test-list) dir)] + [else #false]))
\ No newline at end of file diff --git a/racket/leetcode/lc-9-palindromic-number.rkt b/racket/leetcode/lc-9-palindromic-number.rkt new file mode 100644 index 0000000..1fccbef --- /dev/null +++ b/racket/leetcode/lc-9-palindromic-number.rkt @@ -0,0 +1,38 @@ +#lang racket +(define/contract (is-palindrome x) + (-> exact-integer? boolean?) + ; get the easy cases out of the way first + ; negative numbers are not palindromes, single-digit numbers are + (cond [(x . < . 0) #false] + [(x . < . 10) #true] + [else + ; order-of-magnitude returns the scientific notation exponent + ; so add 1 to get the number of digits + (define digits + (add1 (order-of-magnitude x))) + ; figure out how many digits we need to trim to find the mirrored halves + ; if there are an even number of digits 2n, we will remove n of them from the right + ; if there are an odd number of digits 2n+1, we will remove n+1 of them from the right + (define half-digits + (cond [(even? digits) (/ digits 2)] + [else (/ (add1 digits) 2)])) + ; divide x by a power of 10 to get the digits to match + (define front-half + (quotient x (expt 10 half-digits))) + ; reverse the back half with repeated divisions by 10 + (define back-half-reversed + (for/fold ([reversed 0] + [remaining (remainder x (expt 10 half-digits))] + ; build up the sum of the digits in reversed and return it at the end + #:result reversed) + ; if we have an odd number of digits, we don't need to match the middle one + ([n (in-range (if (even? digits) + half-digits + (sub1 half-digits)))]) + ; shift all the accumulated digits in the mirror to the left one place + ; and add the next one to the right, + ; then chop the right-most digit off the original + (values (+ (* 10 reversed) (remainder remaining 10)) + (quotient remaining 10)))) + ; finally, check to see if the mirrored right is equal to the original left + (= front-half back-half-reversed)])) diff --git a/racket/leetcode/lc-905-sort-by-parity.rkt b/racket/leetcode/lc-905-sort-by-parity.rkt new file mode 100644 index 0000000..7c736f3 --- /dev/null +++ b/racket/leetcode/lc-905-sort-by-parity.rkt @@ -0,0 +1,7 @@ +#lang racket +(define/contract (sort-array-by-parity A) + (-> (listof exact-integer?) (listof exact-integer?)) + ((compose flatten (if (odd? (car A)) reverse identity)) + (group-by (λ (n) (modulo n 2)) A))) + +(sort-array-by-parity '(1 1 3 4))
\ No newline at end of file diff --git a/racket/leetcode/lc-944-delete-columns.rkt b/racket/leetcode/lc-944-delete-columns.rkt new file mode 100644 index 0000000..f9714b7 --- /dev/null +++ b/racket/leetcode/lc-944-delete-columns.rkt @@ -0,0 +1,7 @@ +#lang racket +(define/contract (min-deletion-size strs) + (-> (listof string?) exact-integer?) + (define transposed-strs (apply map list (map string->list strs))) + (count (λ (s) (not (equal? s (sort s char<?)))) transposed-strs)) + +(min-deletion-size '["cba" "daf" "ghi"]) diff --git a/racket/leetcode/lc-953-alien-dictionary.rkt b/racket/leetcode/lc-953-alien-dictionary.rkt new file mode 100644 index 0000000..f72a895 --- /dev/null +++ b/racket/leetcode/lc-953-alien-dictionary.rkt @@ -0,0 +1,26 @@ +#lang racket + +(define/contract (is-alien-sorted words order) + (-> (listof string?) string? boolean?) + (define alpha-order + (make-hash (map (λ (a b) (cons a b)) + (map string (string->list order)) + (build-list (string-length order) identity)))) + (hash-set! alpha-order #\* -1) + (define (alien<? s1 s2) + (cond [(equal? s1 "") #true] + [(equal? s2 "") #false] + [(equal? (hash-ref alpha-order (substring s1 0 1)) + (hash-ref alpha-order (substring s2 0 1))) + (alien<? (substring s1 1 (string-length s1)) + (substring s2 1 (string-length s2)))] + [(< (hash-ref alpha-order (substring s1 0 1)) + (hash-ref alpha-order (substring s2 0 1))) #true] + [else false])) + (equal? words + (sort words alien<?))) + +(is-alien-sorted '["hello" "leetcode"] "hlabcdefgijkmnopqrstuvwxyz") +(is-alien-sorted '["word" "world" "row"] "worldabcefghijkmnpqstuvxyz") +(is-alien-sorted '["apple" "app"] "abcdefghijklmnopqrstuvwxyz") +(is-alien-sorted '["iekm" "tpnhnbe"] "loxbzapnmstkhijfcuqdewyvrg")
\ No newline at end of file diff --git a/racket/leetcode/lc-989-add-to-array-form.rkt b/racket/leetcode/lc-989-add-to-array-form.rkt new file mode 100644 index 0000000..c88bdc9 --- /dev/null +++ b/racket/leetcode/lc-989-add-to-array-form.rkt @@ -0,0 +1,24 @@ +#lang racket +(define/contract (add-to-array-form num k) + (-> (listof exact-integer?) exact-integer? (listof exact-integer?)) + (define rev-num (reverse num)) + (define raw-array-form + (cond [(= k 0) num] + [else + (for/fold ([sum-list '()] + [addend k] + [carry 0] + #:result sum-list) + ([place (append rev-num + (make-list (add1 (order-of-magnitude k)) 0))]) + (let* ([place-sum (+ place carry (modulo addend 10))] + [to-carry (quotient place-sum 10)] + [new-place (modulo place-sum 10)]) + (values (cons new-place sum-list) + (quotient addend 10) + to-carry)))])) + (match-define-values + (result _) (drop-common-prefix raw-array-form + (make-list (length raw-array-form) 0))) + (cond [(empty? result) '(0)] + [else result]))
\ No newline at end of file diff --git a/racket/leetcode/lc-999-available-captures.rkt b/racket/leetcode/lc-999-available-captures.rkt new file mode 100644 index 0000000..1b1a3a9 --- /dev/null +++ b/racket/leetcode/lc-999-available-captures.rkt @@ -0,0 +1,28 @@ +#lang racket +(define/contract (num-rook-captures board) + (-> (listof (listof string?)) exact-integer?) + + (define (get-rook-space [board-state board]) + (for/or ([board-rank (in-list board-state)] + [rank (in-range 0 (length board-state))] + #:when (index-of board-rank "R")) + (list rank (index-of board-rank "R")))) + + (define (check-for-capturable-pawns spaces) + (match spaces + [(list _ ... "p" "." ... "R" "." ... "p" _ ...) 2] + [(list _ ... "R" "." ... "p" _ ...) 1] + [(list _ ... "p" "." ... "R" _ ...) 1] + [_ 0])) + + (define (check-rank rank [board-state board]) + (let ([spaces (list-ref board-state rank)]) + (check-for-capturable-pawns spaces))) + + (define (check-file file [board-state board]) + (let ([spaces (map (curryr list-ref file) board)]) + (check-for-capturable-pawns spaces))) + + (match (get-rook-space board) + [(list rank file) (+ (check-rank rank) + (check-file file))]))
\ No newline at end of file |