Advent of Code 2020 1-5

9/11/2024

Preamble

I've started doing some of the older (year 2020) advent of code problems for fun / preparation for this years AoC. Here are my solutions to days 1-5. I've also added notes as to what makes a solution interesting

Day 1

(require racket threading advent-of-code)

(define expense-reports (~>> (fetch-aoc-input (find-session) 2020 1)
                             string-split
                             (map string->number)))

;; part 1
;; find the 2 entries that sum to 2020 and then multiply them together
(define (balance-report expense-reports num-pairs summation)
  (for/first ([pair (in-combinations expense-reports num-pairs)]
              #:when (equal? (apply + pair) summation))
    (apply * pair)))

(balance-report expense-reports 2 2020)

;; part 2
(balance-report expense-reports 3 2020)

Notes

Racket has nice syntax for handling streams. In this case, note the stream for combinations, in-combinations

Day 2

(require racket threading advent-of-code)


(define pws (~> (fetch-aoc-input (find-session) 2020 2)
                (string-split "
")
                ((curry map (curryr string-split ": ")))))

(define (try func)
  (λ (x)
    (let ([result (func x)])
      (if result result x))))

;; part 1
(define (valid-password? rule pw)
  (let* ([rule-chars (string->list rule)]
         [pw-chars (string->list pw)]
         [chosen-letter (last rule-chars)]
         [min-max-counts (regexp-match #px"^([0-9]+)-([0-9]+)" rule)]
         [min-count (string->number (list-ref min-max-counts 1))]
         [max-count (string->number (list-ref min-max-counts 2))]
         [num-letters-in-pw (count (curry eq? chosen-letter) pw-chars)])
    (and (<= min-count num-letters-in-pw)
         (>= max-count num-letters-in-pw))))

(for/sum ([pw pws]
          #:when (match-let ([(list rule pw) pw])
                   (valid-password? rule pw)))
  1)

;; part 2
(define (valid-password? rule pw)
  (match-let* ([(list _ min-index max-index) (map (try string->number)
                                                  (regexp-match #px"^([0-9]+)-([0-9]+)" rule))]
               [(list _ ... rule-char) (string->list rule)])
    (let ([check-point
           (λ (rule-idx)
             (equal? rule-char (list-ref (string->list pw) (sub1 rule-idx))))])
      (xor (check-point min-index) (check-point max-index)))))


(for/sum ([pw pws]
          #:when (match-let ([(list rule pw) pw])
                   (valid-password? rule pw)))
  1)

Notes

There are a few nice racket-y things here:

  • the higher-order function try which wraps a function, tries to run it, and if it fails it moves on. This is also available in R's purrr package as safely

  • (list _ ... rule-char)

    • This matches against a list, doesn't care what the first n items are, but matches the final item in the list. Racket also has the special form list* which lets you treat the end of the list as a variable.

Day 3

#lang racket
(require racket threading advent-of-code)

(define tree-grid (~> (fetch-aoc-input (find-session) 2020 3) (string-split "
")))

(define (get-slope-count right down)
  (for/fold ([points '()]
             [curr-point right]
             [idx 0]
             #:result (count (curry equal? ##) points))
            ([tree-line (drop tree-grid down)]
             #:do [(define trees (string->list tree-line))])
    (if (zero? (remainder idx down))
        (values (cons (list-ref trees curr-point) points)
                (remainder (+ curr-point right) (length trees))
                (add1 idx))
        (values points curr-point (add1 idx)))))

;; part 1
(get-slope-count 3 1)

;; part 2
(* (get-slope-count 1 1)
   (get-slope-count 3 1)
   (get-slope-count 5 1)
   (get-slope-count 7 1)
   (get-slope-count 1 2))

Notes

There is a neat little syntax-saver with the #:do keyword.

Day 4

#lang racket
(require racket threading advent-of-code)

(define passports (~> (fetch-aoc-input (find-session) 2020 4) (string-split "

")))

;; part 1
(define structured-passports
  (for/list ([passport passports])
    (for/hash ([split-passport (string-split passport)])
      (apply values (string-split split-passport ":")))))

(define (check-all-fields-exist ht)
  (define passport-fields
    (set "byr" "iyr" "eyr" "hgt" "hcl" "ecl" "pid" "cid"))

  (set-empty?
   (apply set-symmetric-difference
          (map (curryr set-remove "cid")
               (list passport-fields (apply set (hash-keys ht)))))))

(define (validate-fields ht)
  (define (validate-number num low high)
    (let ([num (string->number num)])
      (and (number? num) (<= low num high))))

  (define (validate-regex value pattern)
    (regexp-match? pattern value))

  (define (validate name value)
    (match name
      ["byr" (validate-number value 1920 2002)]
      ["iyr" (validate-number value 2010 2020)]
      ["eyr" (validate-number value 2020 2030)]
      ["hgt" (cond
               [(regexp-match? #px"^([0-9]+)cm$" value)
                (validate-number (second (regexp-match #px"^([0-9]+)cm$" value)) 150 193)]
               [(regexp-match? #px"^([0-9]+)in$" value)
                (validate-number (second (regexp-match #px"^([0-9]+)in$" value)) 59 76)]
               [else #f])]
      ["hcl" (validate-regex value #px"^#[0-9a-f]{6}$")]
      ["ecl" (member value '("amb" "blu" "brn" "gry" "grn" "hzl" "oth"))]
      ["pid" (validate-regex value #px"^[0-9]{9}$")]
      ;; "cid" is ignored, so always valid
      ["cid" #t]
      [_ #f]))

  (andmap
   (lambda (key)
     (validate key (hash-ref ht key "")))
   '("byr" "iyr" "eyr" "hgt" "hcl" "ecl" "pid")))

(define (valid? ht validators)
  (andmap (λ (fn) (fn ht)) validators))

;; part 1
(for/sum ([passport structured-passports]
          #:when (valid? passport (list check-all-fields-exist)))
  1)

;; part 2
(for/sum ([passport structured-passports]
          #:when (valid? passport (list check-all-fields-exist validate-fields)))
  1)

Notes

  • There is the equivalent of python's nested list / dict comprehension in structured-passports
  • valid? takes a hash-table and a list of validator functions. andmap is pretty useful for these algorithmic-type questions.
  • match is always nice to use. This isn't doing anything crazy, but I enjoy the clarity when reading it.

Day 5

#lang racket
(require racket threading advent-of-code)

(define seats (~> (fetch-aoc-input (find-session) 2020 5) (string-split "
")))

(define (check seats lval rval fchar schar)
  (for/fold ([left lval] [right rval]
                      #:result left)
            ([seat (string->list seats)])
    (let ([mid-point (quotient (+ left right) 2)])
    (match seat
      [(== fchar) (values left mid-point)]
      [(== schar) (values (add1 mid-point) right)]
      [_ (values mid-point mid-point)]))))

(define (check-pass seats)
  (match-define (list rows cols)
    ((compose flatten list)
     (regexp-replace #rx"[LR].*" seats "")
     (regexp-match #rx"[LR].*" seats)))
  (list (check rows 0 127 #F #B)
        (check cols 0 7 #L #R)))

(map (compose (curry apply (λ (a b) (+ (* a 8) b))) check-pass)
     seats)

;; part 1
(apply max seat-ids)

;; part 2
(for/first ([seat (sort seat-ids <)]
            [idx (in-naturals (apply min seat-ids))]
            #:when (not (equal? idx seat)))
  idx)

Notes

This could also be done using functions that co-recursively call each other, but my train stop is coming up 😁