190 lines
5.6 KiB
Racket
190 lines
5.6 KiB
Racket
#lang racket
|
|
|
|
(require "../lib.rkt")
|
|
|
|
;; raw is one of:
|
|
;; - number?
|
|
;; - (list raw? raw?)
|
|
|
|
;; raw?
|
|
(define input
|
|
(for/list ([line (problem-input 18)])
|
|
(read (open-input-string (string-replace line "," " ")))))
|
|
|
|
;; type: (or/c 'root 'left 'right)
|
|
;; parent: (or/c pair? #f)
|
|
;; left right: (or/c number? pair?)
|
|
(struct pair (type parent left right) #:mutable #:transparent)
|
|
|
|
;; (or/c number? pair?) (or/c number? pair?) -> pair?
|
|
;; Combine two pairs into a root pair
|
|
(define (combine left right)
|
|
(define root (pair 'root #f left right))
|
|
(when (pair? left)
|
|
(set-pair-parent! left root)
|
|
(set-pair-type! left 'left))
|
|
(when (pair? right)
|
|
(set-pair-parent! right root)
|
|
(set-pair-type! right 'right))
|
|
root)
|
|
|
|
;; raw? -> pair?
|
|
(define (raw->pair x)
|
|
(match x
|
|
[(? number? n) n]
|
|
[(list left right)
|
|
(define left-pair (raw->pair left))
|
|
(define right-pair (raw->pair right))
|
|
(combine left-pair right-pair)]))
|
|
|
|
;; pair? -> string?
|
|
(define (pair->string p)
|
|
(if (number? p)
|
|
(format "~a" p)
|
|
(format "[~a,~a]"
|
|
(pair->string (pair-left p))
|
|
(pair->string (pair-right p)))))
|
|
|
|
;; EXPLODE ;;
|
|
|
|
;; number? pair? -> void?
|
|
;; Effect: Adds given number to deepest left node
|
|
(define (add-leftmost! n p)
|
|
(define left (pair-left p))
|
|
(if (number? left)
|
|
(set-pair-left! p (+ left n))
|
|
(add-leftmost! n left)))
|
|
|
|
;; number? pair? -> void?
|
|
;; Effect: Adds given number to deepest right node
|
|
(define (add-rightmost! n p)
|
|
(define right (pair-right p))
|
|
(if (number? right)
|
|
(set-pair-right! p (+ right n))
|
|
(add-rightmost! n right)))
|
|
|
|
;; number? pair? -> void?
|
|
;; Effect: Adds given number to deepest left node in right node
|
|
(define (right-add-leftmost! n p)
|
|
(define right (pair-right p))
|
|
(if (number? right)
|
|
(set-pair-right! p (+ right n))
|
|
(add-leftmost! n right)))
|
|
|
|
;; number? pair? -> void?
|
|
;; Effect: Adds given number to deepest right node in left node
|
|
(define (left-add-rightmost! n p)
|
|
(define left (pair-left p))
|
|
(if (number? left)
|
|
(set-pair-left! p (+ left n))
|
|
(add-rightmost! n left)))
|
|
|
|
;; number? pair? -> void?
|
|
;; Effect: Adds given number to deepest left node of closest right sibling,
|
|
;; or does nothing if there is no right sibling
|
|
(define (up-add-leftmost! n p)
|
|
(match (pair-type p)
|
|
['root (void)]
|
|
['left (right-add-leftmost! n (pair-parent p))]
|
|
['right (up-add-leftmost! n (pair-parent p))]))
|
|
|
|
;; number? pair? -> void?
|
|
;; Effect: Adds given number to deepest right node of closest left sibling,
|
|
;; or does nothing if there is no left sibling
|
|
(define (up-add-rightmost! n p)
|
|
(match (pair-type p)
|
|
['root (void)]
|
|
['right (left-add-rightmost! n (pair-parent p))]
|
|
['left (up-add-rightmost! n (pair-parent p))]))
|
|
|
|
;; number? pair? -> (or/c void? #f)
|
|
;; Explodes pairs at depth 4 or greater or returns false
|
|
(define (explode depth p)
|
|
(match p
|
|
[(pair 'left parent (? number? left) (? number? right))
|
|
#:when (>= depth 4)
|
|
(right-add-leftmost! right parent)
|
|
(up-add-rightmost! left parent)
|
|
(set-pair-left! parent 0)]
|
|
[(pair 'right parent (? number? left) (? number? right))
|
|
#:when (>= depth 4)
|
|
(left-add-rightmost! left parent)
|
|
(up-add-leftmost! right parent)
|
|
(set-pair-right! parent 0)]
|
|
[(pair type parent (? number? left) (? number? right)) #f]
|
|
[(pair type parent (? number? left) (? pair? right))
|
|
(explode (add1 depth) right)]
|
|
[(pair type parent (? pair? left) (? number? right?))
|
|
(explode (add1 depth) left)]
|
|
[(pair type parent (? pair? left) (? pair? right))
|
|
(or (explode (add1 depth) left)
|
|
(explode (add1 depth) right))]))
|
|
|
|
;; SPLIT ;;
|
|
|
|
;; number? pair? -> void?
|
|
;; Effect: Sets left node to new pair consisting of halved number rounded down and up
|
|
(define (split-left! left p)
|
|
(define halved (/ left 2))
|
|
(set-pair-left! p (pair 'left p (floor halved) (ceiling halved))))
|
|
|
|
;; number? pair? -> void?
|
|
;; Effect: Sets right node to new pair consisting of halved number rounded down and up
|
|
(define (split-right! right p)
|
|
(define halved (/ right 2))
|
|
(set-pair-right! p (pair 'right p (floor halved) (ceiling halved))))
|
|
|
|
;; pair? -> (or/c void? #f)
|
|
;; Splits leftmost number greater than 10 into a pair of halved numbers or returns false
|
|
(define (split p)
|
|
(match p
|
|
[(pair type parent (? number? left) (? number? right))
|
|
(or (and (>= left 10) (split-left! left p))
|
|
(and (>= right 10) (split-right! right p)))]
|
|
[(pair type parent (? number? left) (? pair? right))
|
|
(if (>= left 10) (split-left! left p) (split right))]
|
|
[(pair type parent (? pair? left) (? number? right))
|
|
(or (split left) (and (>= right 10) (split-right! right p)))]
|
|
[(pair type parent (? pair? left) (? pair? right))
|
|
(or (split left) (split right))]))
|
|
|
|
;; REDUCE ;;
|
|
|
|
;; pair? -> void?
|
|
;; Effect: Explodes then splits pair until a fixed point is reached
|
|
(define (reduce p)
|
|
(let loop ([exploded #t])
|
|
(if exploded
|
|
(loop (explode 0 p))
|
|
(when (split p)
|
|
(loop (explode 0 p))))))
|
|
|
|
;; MAGNITUDE ;;
|
|
|
|
;; (or/c number? pair?) -> number?
|
|
(define (magnitude p)
|
|
(match p
|
|
[(? number? n) n]
|
|
[(pair _ _ left right)
|
|
(+ (* 3 (magnitude left))
|
|
(* 2 (magnitude right)))]))
|
|
|
|
;; SOLUTION ;;
|
|
|
|
(define part1
|
|
(for/fold ([left (raw->pair (first input))]
|
|
#:result (magnitude left))
|
|
([right (rest input)])
|
|
(define p (combine left (raw->pair right)))
|
|
(reduce p)
|
|
p))
|
|
|
|
(define part2
|
|
(for*/fold ([sum 0])
|
|
([left input]
|
|
[right input])
|
|
(define p (combine (raw->pair left) (raw->pair right)))
|
|
(reduce p)
|
|
(max sum (magnitude p))))
|
|
|
|
(show-solution part1 part2) |