Compare commits

...
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

85 Commits

Author SHA1 Message Date
Matthew Butterick 1d42f9963d fix deps 9 years ago
Matthew Butterick db8bc2e355 day25 9 years ago
Matthew Butterick 379b3d5d4b typo 9 years ago
Matthew Butterick 37cd3db58f day24 9 years ago
Matthew Butterick a58fdd8148 name change 9 years ago
Matthew Butterick c6c3bcf0c6 day23 9 years ago
Matthew Butterick 33d50b16c4 colonize 9 years ago
Matthew Butterick a9c41325d2 fix day22 9 years ago
Matthew Butterick 1825071a1b continue day23 9 years ago
Matthew Butterick 6232b6f21c start day23 9 years ago
Matthew Butterick 1307d8358b day22 9 years ago
Matthew Butterick 120c8c6780 day21 9 years ago
Matthew Butterick b327219fd2 more 9 years ago
Matthew Butterick d9df122ec7 typos 9 years ago
Matthew Butterick 1f0130d2e6 day20 9 years ago
Matthew Butterick 9ca361dd61 day19 9 years ago
Matthew Butterick 403355f623 zeroify 9 years ago
Matthew Butterick f45137d923 typo 9 years ago
Matthew Butterick c2150544b6 typo 9 years ago
Matthew Butterick a62113cf30 typo 9 years ago
Matthew Butterick f9a8409b2b fix deps 9 years ago
Matthew Butterick f7a69985a7 day18 9 years ago
Matthew Butterick 021ea51dc6 fix it more better 9 years ago
Matthew Butterick 1ef1d73001 fix input file linking 9 years ago
Matthew Butterick 072b061a6b Update README.md 9 years ago
Matthew Butterick 93b09dc139 Update README.md 9 years ago
Matthew Butterick d780768e48 update 9 years ago
Matthew Butterick 8cd5b28673 Update README.md 9 years ago
Matthew Butterick 7d01377caa update info 9 years ago
Matthew Butterick 45a0811230 note 9 years ago
Matthew Butterick 1289992494 rearrange 9 years ago
Matthew Butterick b72f0249bc about 9 years ago
Matthew Butterick 572d222cb1 day17 update 9 years ago
Matthew Butterick 9f7abb114c day17 9 years ago
Matthew Butterick c56738152d update index 9 years ago
Matthew Butterick 81306c3994 day16 9 years ago
Matthew Butterick bc5da102ba day15 9 years ago
Matthew Butterick 16c0b1f198 Update README.md 9 years ago
Matthew Butterick 410c7ad3bb ypo 9 years ago
Matthew Butterick ce2dbf0db3 day14 9 years ago
Matthew Butterick 75f2168e25 typo 9 years ago
Matthew Butterick d37adca51e note 9 years ago
Matthew Butterick 5d300ae8d9 typo 9 years ago
Matthew Butterick 49b169602e clarity 9 years ago
Matthew Butterick ec676b9b01 typo 9 years ago
Matthew Butterick 3569b6bccb rkt-ize 9 years ago
Matthew Butterick 9e9f30b27c tweak 9 years ago
Matthew Butterick 371aee7567 typo 9 years ago
Matthew Butterick cea582d3c6 tweak 9 years ago
Matthew Butterick efac3343b7 math tweak 9 years ago
Matthew Butterick 04df2c3653 tweak 9 years ago
Matthew Butterick 42d0edb520 typo 9 years ago
Matthew Butterick 461791f158 tweak 9 years ago
Matthew Butterick e6c0c384f1 day12 and day13 9 years ago
Matthew Butterick 755017a06c typo 9 years ago
Matthew Butterick 294acdea40 day11 9 years ago
Matthew Butterick 8975267073 typo 9 years ago
Matthew Butterick b73b988213 typo 9 years ago
Matthew Butterick e486128438 typo 9 years ago
Matthew Butterick adf41e9eaf day10 9 years ago
Matthew Butterick 31a9f73acb day9 9 years ago
Matthew Butterick 69fdd8b2f5 day8 9 years ago
Matthew Butterick d5ea650a25 adjustment 9 years ago
Matthew Butterick 43f2c69245 adjustments 9 years ago
Matthew Butterick 796ec231f5 adjustments 9 years ago
Matthew Butterick fdba08b97a day7 9 years ago
Matthew Butterick 09883ce619 rename 9 years ago
Matthew Butterick 1346f932d8 update 9 years ago
Matthew Butterick 184d6a1651 update 9 years ago
Matthew Butterick 9498021941 update 9 years ago
Matthew Butterick 6199324ebc typo 9 years ago
Matthew Butterick bc06eb4f54 typos 9 years ago
Matthew Butterick 8827154ace day6 9 years ago
Matthew Butterick e74fee0bb5 day5 9 years ago
Matthew Butterick e5bae451df typo 9 years ago
Matthew Butterick 8539c8f9af finish day4, reorg directory 9 years ago
Matthew Butterick 339b2c7d1d finish day3 9 years ago
Matthew Butterick ea9eb79e3f start day3 9 years ago
Matthew Butterick 117a1a4401 day2 9 years ago
Matthew Butterick 08d30beb03 tidy day1 9 years ago
Matthew Butterick 213bf28eab Update README.md 9 years ago
Matthew Butterick d4239d5120 typo 9 years ago
Matthew Butterick ee9c496c44 typo 9 years ago
Matthew Butterick 3bd775b014 Create README.md 9 years ago
Matthew Butterick 0d50a35923 day 1 9 years ago

21
.gitignore vendored

@ -0,0 +1,21 @@
# for Racket
compiled/
*~
# for Mac OS X
.DS_Store
.AppleDouble
.LSOverride
Icon
# Thumbnails
._*
# Files that might appear on external disk
.Spotlight-V100
.Trashes
# generated documentation
*.js
*.css
*.html

@ -1,2 +1,9 @@
# aoc-racket Racket solutions & explanations for the [Advent of Code](http://adventofcode.com) puzzles. Written in Racket's literate-programming dialect, `scribble/lp2`.
Racket solutions & explanations for the Advent of Code puzzles
Install from the command line:
raco pkg install aoc-racket
Explanations will be installed automatically as part of the Scribble documentation.
[Or just read the code and explanations online, right now.](http://pkg-build.racket-lang.org/doc/aoc-racket/)

@ -0,0 +1,48 @@
#lang scribble/manual
@(require (for-label racket rackunit sugar/list))
@title{Advent of Code: solutions & explanations}
@author[(author+email "Matthew Butterick" "mb@mbtype.com")]
@defmodule[aoc-racket]
@italic{Dedicated to curious characters everywhere, especially those learning Racket.}
@link["http://adventofcode.com"]{Advent of Code} is a series of programming puzzles designed by @link["http://was.tl"]{Eric Wastl}.
I find that programming puzzles are a good way of learning something new about a programming language, or learning how to do certain things better. Documenting these solutions helped me nail down some discoveries.
Thank you to Eric Wastl. If you like Advent of Code, please @link["http://adventofcode.com/about"]{pay him for it}.
You can install this package (if you haven't already) with
@tt{raco pkg install aoc-racket}
@local-table-of-contents[]
@include-section[(submod "day01.rkt" doc)]
@include-section[(submod "day02.rkt" doc)]
@include-section[(submod "day03.rkt" doc)]
@include-section[(submod "day04.rkt" doc)]
@include-section[(submod "day05.rkt" doc)]
@include-section[(submod "day06.rkt" doc)]
@include-section[(submod "day07.rkt" doc)]
@include-section[(submod "day08.rkt" doc)]
@include-section[(submod "day09.rkt" doc)]
@include-section[(submod "day10.rkt" doc)]
@include-section[(submod "day11.rkt" doc)]
@include-section[(submod "day12.rkt" doc)]
@include-section[(submod "day13.rkt" doc)]
@include-section[(submod "day14.rkt" doc)]
@include-section[(submod "day15.rkt" doc)]
@include-section[(submod "day16.rkt" doc)]
@include-section[(submod "day17.rkt" doc)]
@include-section[(submod "day18.rkt" doc)]
@include-section[(submod "day19.rkt" doc)]
@include-section[(submod "day20.rkt" doc)]
@include-section[(submod "day21.rkt" doc)]
@include-section[(submod "day22.rkt" doc)]
@include-section[(submod "day23.rkt" doc)]
@include-section[(submod "day24.rkt" doc)]
@include-section[(submod "day25.rkt" doc)]

File diff suppressed because one or more lines are too long

@ -0,0 +1,113 @@
#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@aoc-title[1]
@defmodule[aoc-racket/day01]
@link["http://adventofcode.com/day/01"]{The puzzle}. Our @link-rp["day01-input.txt"]{input} is a string of parentheses that controls an elevator. A left parenthesis @litchar{(} means go up one floor, and a right parenthesis @litchar{)} means go down.
@chunk[<day01>
<day01-setup>
<day01-q1>
<day01-q1-alt>
<day01-q2>
<day01-q2-alt>
<day01-test>]
@section{Where does the elevator land?}
The building has an indefinite number of floors in both directions. So the ultimate destination is just the number of up movements minus the number of down movements. In other words, a left parenthesis = @racket[1] and a right parenthesis = @racket[-1], and we sum them.
@racket[regexp-match*] will return a list of all occurrences of one string within another. The length of this list is the number of occurrences. Therefore, we can use it to count the ups and downs.
@chunk[<day01-setup>
(require racket rackunit)
(provide (all-defined-out))
(define up-char #\()
(define down-char #\))
(define (make-matcher c)
(λ(str) (length (regexp-match* (regexp (format "\\~a" c)) str))))
(define get-ups (make-matcher up-char))
(define get-downs (make-matcher down-char))
(define (get-destination str) (- (get-ups str) (get-downs str)))]
@chunk[<day01-q1>
(define (q1 str)
(get-destination str))]
@subsection{Alternate approach: numerical conversion}
Rather than counting matches with @racket[regexp-match*], we could also convert the string of parentheses directly into a list of numbers.
@chunk[<day01-q1-alt>
(define (elevator-string->ints str)
(for/list ([c (in-string str)])
(if (equal? c up-char)
1
-1)))
(define (q1-alt str)
(apply + (elevator-string->ints str)))]
@section[#:tag "q2"]{At what point does the elevator enter the basement?}
The elevator is in the basement whenever it's at a negative-valued floor. So instead of looking at its ultimate destination, we need to follow the elevator along its travels, computing its intermediate destinations, and stop as soon as it reaches a negative floor.
We could characterize this as a problem of tracking @italic{cumulative values} or @italic{state}. Either way, @racket[for/fold] is the weapon of choice. We'll determine the relative movement at each step, and collect these in a list. (The @racket[get-destination] function is used within the loop to convert each parenthesis into a relative movement, either @racket[1] or @racket[-1].) On each loop, @racket[for/fold] checks the cumulative value of these positions, and stops when they imply a basement value. The length of this list is our answer.
@margin-note{Nothing wrong with @racket[foldl] and @racket[foldr], but @racket[for/fold] is more flexible, and makes more readable code.}
@chunk[<day01-q2>
(define (in-basement? movements)
(negative? (apply + movements)))
(define (q2 str)
(define relative-movements
(for/fold ([movements-so-far empty])
([c (in-string str)]
#:break (in-basement? movements-so-far))
(cons (get-destination (~a c)) movements-so-far)))
(length relative-movements))]
@subsection{Alternate approaches: @tt{for/first} or @tt{for/or}}
When you need to stop a loop the first time a condition occurs, you can also consider @racket[for/first] or @racket[for/or]. The difference is that @racket[for/first] ends after the first evaluation of the body, but @racket[for/or] evaluates the body every time, and ends the first time the body is not @racket[#f].
The two are similar. The choice comes down to readability and efficiency  meaning, if each iteration of the loop is expensive, you'll probably want to cache intermediate values, which means you might as well use @racket[for/fold].
@chunk[<day01-q2-alt>
(define (q2-for/first str)
(define basement-position
(let ([ints (elevator-string->ints str)])
(for/first ([idx (in-range (length ints))]
#:when (negative? (apply + (take ints idx))))
idx)))
basement-position)
(define (q2-for/or str)
(define basement-position
(let ([ints (elevator-string->ints str)])
(for/or ([idx (in-range (length ints))])
(and (negative? (apply + (take ints idx))) idx))))
basement-position)]
@section{Testing Day 1}
@chunk[<day01-test>
(module+ test
(define input-str (file->string "day01-input.txt"))
(check-equal? (q1 input-str) 74)
(check-equal? (q1-alt input-str) 74)
(check-equal? (q2 input-str) 1795)
(check-equal? (q2-for/first input-str) 1795)
(check-equal? (q2-for/or input-str) 1795))]

File diff suppressed because it is too large Load Diff

@ -0,0 +1,68 @@
#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@aoc-title[2]
@defmodule[aoc-racket/day02]
@link["http://adventofcode.com/day/2"]{The puzzle}. Our @link-rp["day02-input.txt"]{input} is a list of strings that represent dimensions of rectangular boxes.
@chunk[<day02>
<day02-setup>
<day02-q1>
<day02-test>]
@section{How much paper is needed to wrap the boxes?}
According to the problem, the paper needed to wrap a present is the surface area of the box (= the sum of the areas of the sides) plus the area of the smallest side.
First we need to parse our input file into a list of box dimensions. We'll model each box as a list of three dimensions. (The question doesn't need us to keep height / width / depth straight, so we won't worry about it.)
Then we have a traditional setup for the devastating one-two punch of @racket[map] and @racket[apply]. We'll write a function to compute surface area from box dimensions. Then we'll @racket[map] that function across the list of boxes, and finally @racket[apply] the @racket[+] operator to our list of results to get the answer.
@chunk[<day02-setup>
(require racket rackunit)
(provide (all-defined-out))
(define (string->boxes str)
(for/list ([ln (in-list (string-split str "\n"))])
(map string->number (string-split ln "x"))))]
@chunk[<day02-q1>
(define (box->paper box)
(match-define (list x y z) box)
(define sides (list (* x y) (* y z) (* x z)))
(+ (* 2 (apply + sides)) (apply min sides)))
(define (q1 str)
(define boxes (string->boxes str))
(apply + (map box->paper boxes)))]
@section{How much ribbon is needed to wrap the boxes?}
According to the problem, the ribbon needed is the perimeter of the smallest side plus the volume of the box.
We take the same approach, with a new @racket[box->ribbon] function.
@chunk[<day02-q1>
(define (box->ribbon box)
(match-define (list x y z) box)
(define (perimeter dim1 dim2) (* 2 (+ dim1 dim2)))
(define perimeters
(list (perimeter x y) (perimeter y z) (perimeter x z)))
(+ (apply min perimeters) (* x y z)))
(define (q2 str)
(define boxes (string->boxes str))
(apply + (map box->ribbon boxes)))]
@section{Testing Day 2}
@chunk[<day02-test>
(module+ test
(define input-str (file->string "day02-input.txt"))
(check-equal? (q1 input-str) 1586300)
(check-equal? (q2 input-str) 3737498))]

File diff suppressed because one or more lines are too long

@ -0,0 +1,108 @@
#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@aoc-title[3]
@defmodule[aoc-racket/day03]
@link["http://adventofcode.com/day/3"]{The puzzle}. Our @link-rp["day03-input.txt"]{input} is a string made of the characters @litchar{^v<>} that represent north, south, west, and east. Taken together, the string represents a path through an indefinitely large grid.
In essence, this a two-dimensional version of the elevator problem in @secref{Day_1}.
@chunk[<day03>
<day03-setup>
<day03-q1>
<day03-q1-complex>
<day03-q2>
<day03-test>]
@section{How many grid cells are visited?}
In the elevator problem, we modeled the parentheses that represented up and down as @racket[1] and @racket[-1]. We'll proceed the same way here, but we'll assign Cartesian coordinates to each possible move — @racket['(0 1)] for north, @racket['(-1 0)] for west, and so on.
For dual-valued data, whether to use @seclink["pairs" #:doc '(lib "scribblings/guide/guide.scrbl")]{pairs or lists} is largely a stylistic choice. Ask: what will you do with the data next? That will often suggest the most natural representation. In this case, the way we create each cell in the path is by adding the x and y coordinates of the current cell to the next move. So it ends up being convenient to model these cells as lists rather than pairs, so we can add them with a simple @racket[(map + current-cell next-move)]. (Recall that when you use @racket[map] with multiple lists, it pulls one element from each list in parallel.)
Once the whole cell path is computed, the answer is found by removing duplicate cells and counting how many remain.
@chunk[<day03-setup>
(require racket rackunit)
(provide (all-defined-out))
]
@chunk[<day03-q1>
(define (string->cells str)
(define start '(0 0))
(match-define (list east north west south) '((1 0) (0 1) (-1 0) (0 -1)))
(define moves (for/list ([s (in-list (regexp-match* #rx"." str))])
(case s
[(">") east]
[("^") north]
[("<") west]
[("v") south])))
(for/fold ([cells-so-far (list start)])
([next-move (in-list moves)])
(define current-cell (car cells-so-far))
(define next-cell (map + current-cell next-move))
(cons next-cell cells-so-far)))
(define (q1 str)
(length (remove-duplicates (string->cells str))))]
@subsection{Alternate approach: complex numbers}
Rather than use Cartesian coordinates, we could rely on Racket's built-in support for complex numbers to trace the path in the complex plane. Complex numbers have a real and an imaginary part  e.g, @racket[3+4i]  and thus, represent points in a plane just as well as Cartesian coordinates. The advantage is that complex numbers are atomic values, not lists. We can add them normally, without resort to @racket[map]. (It's not essential for this problem, but math jocks might remember that complex numbers can be rotated 90 degrees counterclockwise by multiplying by @tt{+i}.)
Again, the problem has nothing to do with complex numbers inherently. Like pairs and lists, they're just another option for encoding dual-valued data.
@chunk[ <day03-q1-complex>
(define (string->complex-cells str)
(define start 0)
(define east 1)
(define moves (for/list ([s (in-list (regexp-match* #rx"." str))])
(* east (expt +i (case s
[(">") 0]
[("^") 1]
[("<") 2]
[("v") 3])))))
(for/fold ([cells-so-far (list start)])
([next-move (in-list moves)])
(define current-cell (car cells-so-far))
(define next-cell (+ current-cell next-move))
(cons next-cell cells-so-far)))
(define (q1-complex str)
(length (remove-duplicates (string->complex-cells str))))
]
@section{How many grid cells are visited if the path is split?}
By ``split'', the puzzle envisions two people starting at the origin, with one following the odd-numbered moves, and the other following the even-numbered moves. So there are two paths instead of one. The question remains the same: how many cells are visited by one path or the other?
The solution works the same as before the only new task is to split the input into two strings, and then send them through our existing @racket[string->cells] function.
@chunk[<day03-q2>
(define (split-odds-and-evens str)
(define-values (odd-chars even-chars)
(for/fold ([odds-so-far empty][evens-so-far empty])
([c (in-string str)][i (in-naturals)])
(if (even? i)
(values odds-so-far (cons c evens-so-far))
(values (cons c odds-so-far) evens-so-far))))
(values (string-append* (map ~a (reverse odd-chars)))
(string-append* (map ~a (reverse even-chars)))))
(define (q2 str)
(define-values (odd-str even-str) (split-odds-and-evens str))
(length (remove-duplicates
(append (string->cells odd-str) (string->cells even-str)))))
]
@section{Testing Day 3}
@chunk[<day03-test>
(module+ test
(define input-str (file->string "day03-input.txt"))
(check-equal? (q1 input-str) 2565)
(check-equal? (q1-complex input-str) 2565)
(check-equal? (q2 input-str) 2639))]

@ -0,0 +1 @@
iwrupvqb

@ -0,0 +1,54 @@
#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@(require (for-label openssl/md5))
@aoc-title[4]
@defmodule[aoc-racket/day04]
@link["http://adventofcode.com/day/4"]{The puzzle}. Our @link-rp["day04-input.txt"]{input} is a string of eight characters that represents part of a key for making an MD5 hash.
@chunk[<day04>
<day04-setup>
<day04-q1>
<day04-q2>
<day04-test>]
@section{What is the lowest-numbered MD5 hash starting with five zeroes?}
We're asked to create an MD5 hash from an input key that consists of our eight-character input joined to a decimal number. The puzzle asks us to find the lowest decimal number that, when joined to our input, produces an MD5 hash that starts with five zeroes.
Whether or not you already know what an MD5 hash is, you can search the Racket docs and will soon find the @racketmodname[openssl/md5] module and the @racket[md5] function. Then, this puzzle is easy: starting at @racket[0], make new input keys with each integer, and stop when we find one that results in the MD5 hash we want. (The approach is similar to the second part of @secref{Day_1}.)
@chunk[<day04-setup>
(require racket rackunit openssl/md5)
(provide (all-defined-out))
]
@chunk[<day04-q1>
(define (q1 str)
(for/or ([i (in-naturals)])
(define md5-key (string-append str (~a i)))
(define md5-hash (md5 (open-input-string md5-key)))
(and (string-prefix? md5-hash "00000") i)))
]
@section{How about six zeroes?}
Exactly the same, except we test for a string of six zeroes. It is likely, however, to take quite a bit longer to run, as the sixth zero essentially makes the criterion 10 times more stringent.
@chunk[<day04-q2>
(define (q2 str)
(for/or ([i (in-naturals)])
(define md5-key (string-append str (~a i)))
(define md5-hash (md5 (open-input-string md5-key)))
(and (string-prefix? md5-hash "000000") i)))]
@section{Testing Day 4}
@chunk[<day04-test>
(module+ test
(define input-str (file->string "day04-input.txt"))
(check-equal? (q1 input-str) 346386)
(check-equal? (q2 input-str) 9958218))]

File diff suppressed because it is too large Load Diff

@ -0,0 +1,80 @@
#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@aoc-title[5]
@defmodule[aoc-racket/day05]
@link["http://adventofcode.com/day/5"]{The puzzle}. Our @link-rp["day05-input.txt"]{input} is a list of random-looking but not really random text strings.
@chunk[<day05>
<day05-setup>
<day05-q1>
<day05-q2>
<day05-test>]
@section{How many strings are ``nice''?}
A string is ``nice'' if it meets certain criteria:
@itemlist[
@item{Contains three vowels (= @litchar{aeiou}).}
@item{Contains a double letter.}
@item{Does not contain @litchar{ab}, @litchar{cd}, @litchar{pq}, or @litchar{xy}.}
]
This is a job for @racket[regexp-match]. There's nothing tricky here (except for remembering that certain matching functions require the @racket[pregexp] pattern prefix rather than @racket[regexp]).
@chunk[<day05-setup>
(require racket rackunit)
(provide (all-defined-out))
]
@chunk[<day05-q1>
(define (nice? str)
(define (three-vowels? str)
(>= (length (regexp-match* #rx"[aeiou]" str)) 3))
(define (double-letter? str)
(regexp-match #px"(.)\\1" str))
(define (no-kapu? str)
(not (regexp-match #rx"ab|cd|pq|xy" str)))
(and (three-vowels? str)
(double-letter? str)
(no-kapu? str)))
(define (q1 words)
(length (filter nice? words)))
]
@section{How many strings are ``nice'' under new rules?}
This time a string is ``nice`` if it:
@itemlist[
@item{Contains a pair of two letters that appears twice without overlapping}
@item{Contains a letter that repeats with at least one letter in between}
]
Again, a test of your regexp-writing skills.
@chunk[<day05-q2>
(define (nicer? str)
(define (nonoverlapping-pair? str)
(regexp-match #px"(..).*\\1" str))
(define (separated-repeater? str)
(regexp-match #px"(.).\\1" str))
(and (nonoverlapping-pair? str)
(separated-repeater? str) #t))
(define (q2 words)
(length (filter nicer? words)))]
@section{Testing Day 5}
@chunk[<day05-test>
(module+ test
(define input-str (file->lines "day05-input.txt"))
(check-equal? (q1 input-str) 238)
(check-equal? (q2 input-str) 69))]

@ -0,0 +1,300 @@
turn off 660,55 through 986,197
turn off 341,304 through 638,850
turn off 199,133 through 461,193
toggle 322,558 through 977,958
toggle 537,781 through 687,941
turn on 226,196 through 599,390
turn on 240,129 through 703,297
turn on 317,329 through 451,798
turn on 957,736 through 977,890
turn on 263,530 through 559,664
turn on 158,270 through 243,802
toggle 223,39 through 454,511
toggle 544,218 through 979,872
turn on 313,306 through 363,621
toggle 173,401 through 496,407
toggle 333,60 through 748,159
turn off 87,577 through 484,608
turn on 809,648 through 826,999
toggle 352,432 through 628,550
turn off 197,408 through 579,569
turn off 1,629 through 802,633
turn off 61,44 through 567,111
toggle 880,25 through 903,973
turn on 347,123 through 864,746
toggle 728,877 through 996,975
turn on 121,895 through 349,906
turn on 888,547 through 931,628
toggle 398,782 through 834,882
turn on 966,850 through 989,953
turn off 891,543 through 914,991
toggle 908,77 through 916,117
turn on 576,900 through 943,934
turn off 580,170 through 963,206
turn on 184,638 through 192,944
toggle 940,147 through 978,730
turn off 854,56 through 965,591
toggle 717,172 through 947,995
toggle 426,987 through 705,998
turn on 987,157 through 992,278
toggle 995,774 through 997,784
turn off 796,96 through 845,182
turn off 451,87 through 711,655
turn off 380,93 through 968,676
turn on 263,468 through 343,534
turn on 917,936 through 928,959
toggle 478,7 through 573,148
turn off 428,339 through 603,624
turn off 400,880 through 914,953
toggle 679,428 through 752,779
turn off 697,981 through 709,986
toggle 482,566 through 505,725
turn off 956,368 through 993,516
toggle 735,823 through 783,883
turn off 48,487 through 892,496
turn off 116,680 through 564,819
turn on 633,865 through 729,930
turn off 314,618 through 571,922
toggle 138,166 through 936,266
turn on 444,732 through 664,960
turn off 109,337 through 972,497
turn off 51,432 through 77,996
turn off 259,297 through 366,744
toggle 801,130 through 917,544
toggle 767,982 through 847,996
turn on 216,507 through 863,885
turn off 61,441 through 465,731
turn on 849,970 through 944,987
toggle 845,76 through 852,951
toggle 732,615 through 851,936
toggle 251,128 through 454,778
turn on 324,429 through 352,539
toggle 52,450 through 932,863
turn off 449,379 through 789,490
turn on 317,319 through 936,449
toggle 887,670 through 957,838
toggle 671,613 through 856,664
turn off 186,648 through 985,991
turn off 471,689 through 731,717
toggle 91,331 through 750,758
toggle 201,73 through 956,524
toggle 82,614 through 520,686
toggle 84,287 through 467,734
turn off 132,367 through 208,838
toggle 558,684 through 663,920
turn on 237,952 through 265,997
turn on 694,713 through 714,754
turn on 632,523 through 862,827
turn on 918,780 through 948,916
turn on 349,586 through 663,976
toggle 231,29 through 257,589
toggle 886,428 through 902,993
turn on 106,353 through 236,374
turn on 734,577 through 759,684
turn off 347,843 through 696,912
turn on 286,699 through 964,883
turn on 605,875 through 960,987
turn off 328,286 through 869,461
turn off 472,569 through 980,848
toggle 673,573 through 702,884
turn off 398,284 through 738,332
turn on 158,50 through 284,411
turn off 390,284 through 585,663
turn on 156,579 through 646,581
turn on 875,493 through 989,980
toggle 486,391 through 924,539
turn on 236,722 through 272,964
toggle 228,282 through 470,581
toggle 584,389 through 750,761
turn off 899,516 through 900,925
turn on 105,229 through 822,846
turn off 253,77 through 371,877
turn on 826,987 through 906,992
turn off 13,152 through 615,931
turn on 835,320 through 942,399
turn on 463,504 through 536,720
toggle 746,942 through 786,998
turn off 867,333 through 965,403
turn on 591,477 through 743,692
turn off 403,437 through 508,908
turn on 26,723 through 368,814
turn on 409,485 through 799,809
turn on 115,630 through 704,705
turn off 228,183 through 317,220
toggle 300,649 through 382,842
turn off 495,365 through 745,562
turn on 698,346 through 744,873
turn on 822,932 through 951,934
toggle 805,30 through 925,421
toggle 441,152 through 653,274
toggle 160,81 through 257,587
turn off 350,781 through 532,917
toggle 40,583 through 348,636
turn on 280,306 through 483,395
toggle 392,936 through 880,955
toggle 496,591 through 851,934
turn off 780,887 through 946,994
turn off 205,735 through 281,863
toggle 100,876 through 937,915
turn on 392,393 through 702,878
turn on 956,374 through 976,636
toggle 478,262 through 894,775
turn off 279,65 through 451,677
turn on 397,541 through 809,847
turn on 444,291 through 451,586
toggle 721,408 through 861,598
turn on 275,365 through 609,382
turn on 736,24 through 839,72
turn off 86,492 through 582,712
turn on 676,676 through 709,703
turn off 105,710 through 374,817
toggle 328,748 through 845,757
toggle 335,79 through 394,326
toggle 193,157 through 633,885
turn on 227,48 through 769,743
toggle 148,333 through 614,568
toggle 22,30 through 436,263
toggle 547,447 through 688,969
toggle 576,621 through 987,740
turn on 711,334 through 799,515
turn on 541,448 through 654,951
toggle 792,199 through 798,990
turn on 89,956 through 609,960
toggle 724,433 through 929,630
toggle 144,895 through 201,916
toggle 226,730 through 632,871
turn off 760,819 through 828,974
toggle 887,180 through 940,310
toggle 222,327 through 805,590
turn off 630,824 through 885,963
turn on 940,740 through 954,946
turn on 193,373 through 779,515
toggle 304,955 through 469,975
turn off 405,480 through 546,960
turn on 662,123 through 690,669
turn off 615,238 through 750,714
turn on 423,220 through 930,353
turn on 329,769 through 358,970
toggle 590,151 through 704,722
turn off 884,539 through 894,671
toggle 449,241 through 984,549
toggle 449,260 through 496,464
turn off 306,448 through 602,924
turn on 286,805 through 555,901
toggle 722,177 through 922,298
toggle 491,554 through 723,753
turn on 80,849 through 174,996
turn off 296,561 through 530,856
toggle 653,10 through 972,284
toggle 529,236 through 672,614
toggle 791,598 through 989,695
turn on 19,45 through 575,757
toggle 111,55 through 880,871
turn off 197,897 through 943,982
turn on 912,336 through 977,605
toggle 101,221 through 537,450
turn on 101,104 through 969,447
toggle 71,527 through 587,717
toggle 336,445 through 593,889
toggle 214,179 through 575,699
turn on 86,313 through 96,674
toggle 566,427 through 906,888
turn off 641,597 through 850,845
turn on 606,524 through 883,704
turn on 835,775 through 867,887
toggle 547,301 through 897,515
toggle 289,930 through 413,979
turn on 361,122 through 457,226
turn on 162,187 through 374,746
turn on 348,461 through 454,675
turn off 966,532 through 985,537
turn on 172,354 through 630,606
turn off 501,880 through 680,993
turn off 8,70 through 566,592
toggle 433,73 through 690,651
toggle 840,798 through 902,971
toggle 822,204 through 893,760
turn off 453,496 through 649,795
turn off 969,549 through 990,942
turn off 789,28 through 930,267
toggle 880,98 through 932,434
toggle 568,674 through 669,753
turn on 686,228 through 903,271
turn on 263,995 through 478,999
toggle 534,675 through 687,955
turn off 342,434 through 592,986
toggle 404,768 through 677,867
toggle 126,723 through 978,987
toggle 749,675 through 978,959
turn off 445,330 through 446,885
turn off 463,205 through 924,815
turn off 417,430 through 915,472
turn on 544,990 through 912,999
turn off 201,255 through 834,789
turn off 261,142 through 537,862
turn off 562,934 through 832,984
turn off 459,978 through 691,980
turn off 73,911 through 971,972
turn on 560,448 through 723,810
turn on 204,630 through 217,854
turn off 91,259 through 611,607
turn on 877,32 through 978,815
turn off 950,438 through 974,746
toggle 426,30 through 609,917
toggle 696,37 through 859,201
toggle 242,417 through 682,572
turn off 388,401 through 979,528
turn off 79,345 through 848,685
turn off 98,91 through 800,434
toggle 650,700 through 972,843
turn off 530,450 through 538,926
turn on 428,559 through 962,909
turn on 78,138 through 92,940
toggle 194,117 through 867,157
toggle 785,355 through 860,617
turn off 379,441 through 935,708
turn off 605,133 through 644,911
toggle 10,963 through 484,975
turn off 359,988 through 525,991
turn off 509,138 through 787,411
toggle 556,467 through 562,773
turn on 119,486 through 246,900
turn on 445,561 through 794,673
turn off 598,681 through 978,921
turn off 974,230 through 995,641
turn off 760,75 through 800,275
toggle 441,215 through 528,680
turn off 701,636 through 928,877
turn on 165,753 through 202,780
toggle 501,412 through 998,516
toggle 161,105 through 657,395
turn on 113,340 through 472,972
toggle 384,994 through 663,999
turn on 969,994 through 983,997
turn on 519,600 through 750,615
turn off 363,899 through 948,935
turn on 271,845 through 454,882
turn off 376,528 through 779,640
toggle 767,98 through 854,853
toggle 107,322 through 378,688
turn off 235,899 through 818,932
turn on 445,611 through 532,705
toggle 629,387 through 814,577
toggle 112,414 through 387,421
toggle 319,184 through 382,203
turn on 627,796 through 973,940
toggle 602,45 through 763,151
turn off 441,375 through 974,545
toggle 871,952 through 989,998
turn on 717,272 through 850,817
toggle 475,711 through 921,882
toggle 66,191 through 757,481
turn off 50,197 through 733,656
toggle 83,575 through 915,728
turn on 777,812 through 837,912
turn on 20,984 through 571,994
turn off 446,432 through 458,648
turn on 715,871 through 722,890
toggle 424,675 through 740,862
toggle 580,592 through 671,900
toggle 296,687 through 906,775

@ -0,0 +1,142 @@
#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@aoc-title[6]
@defmodule[aoc-racket/day06]
@link["http://adventofcode.com/day/6"]{The puzzle}. Our @link-rp["day06-input.txt"]{input} is a list of instructions for turning on (or off) the bulbs in a @racket[(* 1000 1000)] grid of lights.
@chunk[<day06>
<day06-setup>
<day06-q1>
<day06-q2>
<day06-refactored>
<day06-test>]
@section{How many lights are lit after following the instructions?}
We need to a) create a data structure to hold our grid of lights, then b) step through the instructions on the list, and then c) count how many lights are lit at the end.
When you need random access to a fixed-size set of items, you should think @secref["vectors" #:doc '(lib "scribblings/guide/guide.scrbl")]. (We could do this problem with a @seclink["hash-tables" #:doc '(lib "scribblings/guide/guide.scrbl")]{hash table}, but it would be a lot slower.) The grid-ness of the problem might suggest a two-dimensional vector  e.g., a 1000-unit vector where each slot holds another 1000-unit vector. But this doesn't buy us any convenience. We'll just use a single @racket[(* 1000 1000)]-unit vector, and translate our Cartesian coordinates into linear vector indexes by treating a coordinate like @tt{(246, 139)} as @racket[246139].
Each instruction consists of two pieces. First, an operation: either @italic{turn on}, @italic{turn off}, or @italic{toggle} (meaning, invert the current state of the bulb). Second, a definition of a rectangular segment of the grid that the operation will be applied to (e.g., @italic{333,60 through 748,159}). Therefore, a natural way to model each instruction is as a Racket function followed by four numerical arguments.
@chunk[<day06-q1>
(define (str->instruction str)
(match-define (list* _ action coordinates)
(regexp-match #px"^(.*?)(\\d+),(\\d+) through (\\d+),(\\d+)$" str))
(define (action->bulb-func action)
(case action
[("turn on") (thunk* 1)]
[("turn off") (thunk* 0)]
[else (λ(bulb) (if (= bulb 1) 0 1))]))
(list* (action->bulb-func (string-trim action))
(map string->number coordinates)))
(define (q1 strs)
(define lights (make-vector (* 1000 1000) 0))
(for ([instruction (in-list (map str->instruction strs))])
(set-lights lights instruction))
(count-lights lights))
]
We'll define our functions for setting and counting the lights separately, since we'll be able to resuse them for the second part.
@chunk[<day06-setup>
(require racket rackunit)
(provide (all-defined-out))
(define (set-lights lights arglist)
(match-define (list bulb-func x1 y1 x2 y2) arglist)
(for* ([x (in-range x1 (add1 x2))][y (in-range y1 (add1 y2))])
(define vector-loc (+ (* 1000 x) y))
(define current-light (vector-ref lights vector-loc))
(vector-set! lights vector-loc (bulb-func current-light))))
(define (count-lights lights)
(for/sum ([light (in-vector lights)]
#:when (positive? light))
light))]
@section{What is the total brightness of the lights if the rules are reinterpreted?}
The second part redefines the meaning of the three instructions, and introduces a notion of ``brightness'':
@itemlist[
@item{@italic{Turn on} now means increase brightness by 1.}
@item{@italic{Turn off} now means reduce brightness by 1, to a minimum of 0.}
@item{@italic{Toggle} now means increase brightness by 2.}
]
This part is the same as the last, except we change the definitions of our bulb functions to match the new rules.
@chunk[<day06-q2>
(define (str->instruction-2 str)
(match-define (list* _ action coordinates)
(regexp-match #px"^(.*?)(\\d+),(\\d+) through (\\d+),(\\d+)$" str))
(define (action->bulb-func action)
(case action
[("turn on") (λ(bulb) (add1 bulb))]
[("turn off") (λ(bulb) (max 0 (sub1 bulb)))]
[else (λ(bulb) (+ bulb 2))]))
(list* (action->bulb-func (string-trim action))
(map string->number coordinates)))
(define (q2 strs)
(define lights (make-vector (* 1000 1000) 0))
(for ([instruction (in-list (map str->instruction-2 strs))])
(set-lights lights instruction))
(count-lights lights))]
@section{Refactored solution}
Since the only part that changes between the solutions is the bulb functions, we could refactor the solutions to avoid repetition.
@chunk[<day06-refactored>
(define (day06-solve strs bulb-func-converter)
(define lights (make-vector (* 1000 1000) 0))
(for ([instruction (in-list (map (make-str-converter bulb-func-converter) strs))])
(set-lights lights instruction))
(count-lights lights))
(define (make-str-converter bulb-func-converter)
(λ (str)
(match-define (list* _ action coordinates)
(regexp-match #px"^(.*?)(\\d+),(\\d+) through (\\d+),(\\d+)$" str))
(list* (bulb-func-converter (string-trim action))
(map string->number coordinates))))
(define q1-bulb-func-converter
(λ(action) (case action
[("turn on") (thunk* 1)]
[("turn off") (thunk* 0)]
[else (λ(bulb) (if (= bulb 1) 0 1))])))
(define q2-bulb-func-converter
(λ(action) (case action
[("turn on") (λ(bulb) (add1 bulb))]
[("turn off") (λ(bulb) (max 0 (sub1 bulb)))]
[else (λ(bulb) (+ bulb 2))])))
]
@section{Testing Day 6}
@chunk[<day06-test>
(module+ test
(define input-strs (file->lines "day06-input.txt"))
(check-equal? (q1 input-strs) 400410)
(check-equal? (q2 input-strs) 15343601)
(check-equal? (day06-solve input-strs q1-bulb-func-converter) 400410)
(check-equal? (day06-solve input-strs q2-bulb-func-converter) 15343601))]

@ -0,0 +1,339 @@
bn RSHIFT 2 -> bo
lf RSHIFT 1 -> ly
fo RSHIFT 3 -> fq
cj OR cp -> cq
fo OR fz -> ga
t OR s -> u
lx -> a
NOT ax -> ay
he RSHIFT 2 -> hf
lf OR lq -> lr
lr AND lt -> lu
dy OR ej -> ek
1 AND cx -> cy
hb LSHIFT 1 -> hv
1 AND bh -> bi
ih AND ij -> ik
c LSHIFT 1 -> t
ea AND eb -> ed
km OR kn -> ko
NOT bw -> bx
ci OR ct -> cu
NOT p -> q
lw OR lv -> lx
NOT lo -> lp
fp OR fv -> fw
o AND q -> r
dh AND dj -> dk
ap LSHIFT 1 -> bj
bk LSHIFT 1 -> ce
NOT ii -> ij
gh OR gi -> gj
kk RSHIFT 1 -> ld
lc LSHIFT 1 -> lw
lb OR la -> lc
1 AND am -> an
gn AND gp -> gq
lf RSHIFT 3 -> lh
e OR f -> g
lg AND lm -> lo
ci RSHIFT 1 -> db
cf LSHIFT 1 -> cz
bn RSHIFT 1 -> cg
et AND fe -> fg
is OR it -> iu
kw AND ky -> kz
ck AND cl -> cn
bj OR bi -> bk
gj RSHIFT 1 -> hc
iu AND jf -> jh
NOT bs -> bt
kk OR kv -> kw
ks AND ku -> kv
hz OR ik -> il
b RSHIFT 1 -> v
iu RSHIFT 1 -> jn
fo RSHIFT 5 -> fr
be AND bg -> bh
ga AND gc -> gd
hf OR hl -> hm
ld OR le -> lf
as RSHIFT 5 -> av
fm OR fn -> fo
hm AND ho -> hp
lg OR lm -> ln
NOT kx -> ky
kk RSHIFT 3 -> km
ek AND em -> en
NOT ft -> fu
NOT jh -> ji
jn OR jo -> jp
gj AND gu -> gw
d AND j -> l
et RSHIFT 1 -> fm
jq OR jw -> jx
ep OR eo -> eq
lv LSHIFT 15 -> lz
NOT ey -> ez
jp RSHIFT 2 -> jq
eg AND ei -> ej
NOT dm -> dn
jp AND ka -> kc
as AND bd -> bf
fk OR fj -> fl
dw OR dx -> dy
lj AND ll -> lm
ec AND ee -> ef
fq AND fr -> ft
NOT kp -> kq
ki OR kj -> kk
cz OR cy -> da
as RSHIFT 3 -> au
an LSHIFT 15 -> ar
fj LSHIFT 15 -> fn
1 AND fi -> fj
he RSHIFT 1 -> hx
lf RSHIFT 2 -> lg
kf LSHIFT 15 -> kj
dz AND ef -> eh
ib OR ic -> id
lf RSHIFT 5 -> li
bp OR bq -> br
NOT gs -> gt
fo RSHIFT 1 -> gh
bz AND cb -> cc
ea OR eb -> ec
lf AND lq -> ls
NOT l -> m
hz RSHIFT 3 -> ib
NOT di -> dj
NOT lk -> ll
jp RSHIFT 3 -> jr
jp RSHIFT 5 -> js
NOT bf -> bg
s LSHIFT 15 -> w
eq LSHIFT 1 -> fk
jl OR jk -> jm
hz AND ik -> im
dz OR ef -> eg
1 AND gy -> gz
la LSHIFT 15 -> le
br AND bt -> bu
NOT cn -> co
v OR w -> x
d OR j -> k
1 AND gd -> ge
ia OR ig -> ih
NOT go -> gp
NOT ed -> ee
jq AND jw -> jy
et OR fe -> ff
aw AND ay -> az
ff AND fh -> fi
ir LSHIFT 1 -> jl
gg LSHIFT 1 -> ha
x RSHIFT 2 -> y
db OR dc -> dd
bl OR bm -> bn
ib AND ic -> ie
x RSHIFT 3 -> z
lh AND li -> lk
ce OR cd -> cf
NOT bb -> bc
hi AND hk -> hl
NOT gb -> gc
1 AND r -> s
fw AND fy -> fz
fb AND fd -> fe
1 AND en -> eo
z OR aa -> ab
bi LSHIFT 15 -> bm
hg OR hh -> hi
kh LSHIFT 1 -> lb
cg OR ch -> ci
1 AND kz -> la
gf OR ge -> gg
gj RSHIFT 2 -> gk
dd RSHIFT 2 -> de
NOT ls -> lt
lh OR li -> lj
jr OR js -> jt
au AND av -> ax
0 -> c
he AND hp -> hr
id AND if -> ig
et RSHIFT 5 -> ew
bp AND bq -> bs
e AND f -> h
ly OR lz -> ma
1 AND lu -> lv
NOT jd -> je
ha OR gz -> hb
dy RSHIFT 1 -> er
iu RSHIFT 2 -> iv
NOT hr -> hs
as RSHIFT 1 -> bl
kk RSHIFT 2 -> kl
b AND n -> p
ln AND lp -> lq
cj AND cp -> cr
dl AND dn -> do
ci RSHIFT 2 -> cj
as OR bd -> be
ge LSHIFT 15 -> gi
hz RSHIFT 5 -> ic
dv LSHIFT 1 -> ep
kl OR kr -> ks
gj OR gu -> gv
he RSHIFT 5 -> hh
NOT fg -> fh
hg AND hh -> hj
b OR n -> o
jk LSHIFT 15 -> jo
gz LSHIFT 15 -> hd
cy LSHIFT 15 -> dc
kk RSHIFT 5 -> kn
ci RSHIFT 3 -> ck
at OR az -> ba
iu RSHIFT 3 -> iw
ko AND kq -> kr
NOT eh -> ei
aq OR ar -> as
iy AND ja -> jb
dd RSHIFT 3 -> df
bn RSHIFT 3 -> bp
1 AND cc -> cd
at AND az -> bb
x OR ai -> aj
kk AND kv -> kx
ao OR an -> ap
dy RSHIFT 3 -> ea
x RSHIFT 1 -> aq
eu AND fa -> fc
kl AND kr -> kt
ia AND ig -> ii
df AND dg -> di
NOT fx -> fy
k AND m -> n
bn RSHIFT 5 -> bq
km AND kn -> kp
dt LSHIFT 15 -> dx
hz RSHIFT 2 -> ia
aj AND al -> am
cd LSHIFT 15 -> ch
hc OR hd -> he
he RSHIFT 3 -> hg
bn OR by -> bz
NOT kt -> ku
z AND aa -> ac
NOT ak -> al
cu AND cw -> cx
NOT ie -> if
dy RSHIFT 2 -> dz
ip LSHIFT 15 -> it
de OR dk -> dl
au OR av -> aw
jg AND ji -> jj
ci AND ct -> cv
dy RSHIFT 5 -> eb
hx OR hy -> hz
eu OR fa -> fb
gj RSHIFT 3 -> gl
fo AND fz -> gb
1 AND jj -> jk
jp OR ka -> kb
de AND dk -> dm
ex AND ez -> fa
df OR dg -> dh
iv OR jb -> jc
x RSHIFT 5 -> aa
NOT hj -> hk
NOT im -> in
fl LSHIFT 1 -> gf
hu LSHIFT 15 -> hy
iq OR ip -> ir
iu RSHIFT 5 -> ix
NOT fc -> fd
NOT el -> em
ck OR cl -> cm
et RSHIFT 3 -> ev
hw LSHIFT 1 -> iq
ci RSHIFT 5 -> cl
iv AND jb -> jd
dd RSHIFT 5 -> dg
as RSHIFT 2 -> at
NOT jy -> jz
af AND ah -> ai
1 AND ds -> dt
jx AND jz -> ka
da LSHIFT 1 -> du
fs AND fu -> fv
jp RSHIFT 1 -> ki
iw AND ix -> iz
iw OR ix -> iy
eo LSHIFT 15 -> es
ev AND ew -> ey
ba AND bc -> bd
fp AND fv -> fx
jc AND je -> jf
et RSHIFT 2 -> eu
kg OR kf -> kh
iu OR jf -> jg
er OR es -> et
fo RSHIFT 2 -> fp
NOT ca -> cb
bv AND bx -> by
u LSHIFT 1 -> ao
cm AND co -> cp
y OR ae -> af
bn AND by -> ca
1 AND ke -> kf
jt AND jv -> jw
fq OR fr -> fs
dy AND ej -> el
NOT kc -> kd
ev OR ew -> ex
dd OR do -> dp
NOT cv -> cw
gr AND gt -> gu
dd RSHIFT 1 -> dw
NOT gw -> gx
NOT iz -> ja
1 AND io -> ip
NOT ag -> ah
b RSHIFT 5 -> f
NOT cr -> cs
kb AND kd -> ke
jr AND js -> ju
cq AND cs -> ct
il AND in -> io
NOT ju -> jv
du OR dt -> dv
dd AND do -> dq
b RSHIFT 2 -> d
jm LSHIFT 1 -> kg
NOT dq -> dr
bo OR bu -> bv
gk OR gq -> gr
he OR hp -> hq
NOT h -> i
hf AND hl -> hn
gv AND gx -> gy
x AND ai -> ak
bo AND bu -> bw
hq AND hs -> ht
hz RSHIFT 1 -> is
gj RSHIFT 5 -> gm
g AND i -> j
gk AND gq -> gs
dp AND dr -> ds
b RSHIFT 3 -> e
gl AND gm -> go
gl OR gm -> gn
y AND ae -> ag
hv OR hu -> hw
1674 -> b
ab AND ad -> ae
NOT ac -> ad
1 AND ht -> hu
NOT hn -> ho

@ -0,0 +1,137 @@
#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@aoc-title[7]
@defmodule[aoc-racket/day07]
@link["http://adventofcode.com/day/7"]{The puzzle}. Our @link-rp["day07-input.txt"]{input} describes an electrical circuit, with each line of the file describing the signal provided to a particular wire.
@chunk[<day07>
<day07-setup>
<day07-ops>
<day07-q1>
<day07-q2>
<day07-test>]
@section{What's the signal on wire @tt{a}?}
The first question we should ask is  how do we model a wire? We're told that it's a thing with inputs that can be evaluated to get a value. So it sounds a lot like a function. Thus, what we'll do is convert our wire descriptions into functions, and then run the function called @racket[a].
In other languages, creating functions from text strings would be a difficult trick. But this facility is built into Racket with @racket[define-syntax]. Essentially our program will run in two phases: in the syntax-transformation phase, we'll read in the list of wire descriptions and expand them into code that represents functions. In the second phase, the program including our new functions, created via syntax transformation will compile & run as usual.
The @racket[convert-input-to-wire-functions] transformer takes the input strings and first converts each into a @italic{datum}  that is, a fragment of Racket code. So an input string like this:
@racket["bn RSHIFT 2 -> bo"]
becomes a datum like this:
@racket[(wire bn RSHIFT 2 -> bo)]
Next, this transformer converts the datums into @italic{syntax}, a process that adds contextual information (for instance, the meanings of identifiers) so the code can be evaluated.
Then the @racket[wire] transformer moves the arguments around to define functions, by matching the three definition patterns that appear in the input. Thus, syntax like this:
@racket[(wire bn RSHIFT 2 -> bo)]
becomes:
@racket[(define (bo) (RSHIFT (evaluate-arg bn) (evaluate-arg 2)))]
@racket[evaluate-arg] lets us handle the fact that some of the arguments for our wires are other wires, and some arguments are numbers. Rather than detect these differences during the syntax-transformation phase, we'll just wrap every input argument with @racket[evaluate-arg], which will do the right thing in the next phase.
(@racket[wire-value-cache] is just a performance enhancement, so that wire values don't have to be computed multiple times.)
One gotcha when using syntax transformers is that identifiers introduced by a transformer can silently override others (in the same way that identifiers defined inside a @racket[let] will override those with the same name outside the @racket[let]). For instance, one of the wires in our input is named @tt{if}. When our syntax transformer defines the @tt{if} function, it will override the usual meaning of @racket[if]. There are plenty of elegant ways to prevent these name collisions. (The most important of which is called @italic{syntax hygiene}, and permeates the design of Racket's syntax-transformation system.) But because this is a puzzle, we'll take the cheap way out: we won't use @racket[if] elsewhere in our code, and instead use @racket[cond].
@chunk[<day07-setup>
(require racket rackunit
(for-syntax racket/file racket/string))
(provide (all-defined-out))
(define-syntax (convert-input-to-wire-functions stx)
(syntax-case stx ()
[(_)
(let* ([input-strings (file->lines "day07-input.txt")]
[wire-strings (map (λ(str) (format "(wire ~a)" str)) input-strings)]
[wire-datums (map (compose1 read open-input-string) wire-strings)])
(datum->syntax stx `(begin ,@wire-datums)))]))
(define-syntax (wire stx)
(syntax-case stx (->)
[(_ arg -> wire-name)
#'(define (wire-name) (evaluate-arg arg))]
[(_ 16bit-op arg -> wire-name)
#'(define (wire-name) (16bit-op (evaluate-arg arg)))]
[(_ arg1 16bit-op arg2 -> wire-name)
#'(define (wire-name) (16bit-op (evaluate-arg arg1) (evaluate-arg arg2)))]
[(_ expr) #'(begin expr)]
[else #'(void)]))
(convert-input-to-wire-functions)
(define wire-value-cache (make-hash))
(define (evaluate-arg x)
(cond
[(procedure? x) (hash-ref! wire-value-cache x (thunk* (x)))]
[else x]))
]
We also need to implement our 16-bit math operations. As we saw above, our syntax transformers are generating code that looks like, for instance, @racket[(RSHIFT (evaluate-arg bn) (evaluate-arg 2))]. This code won't work unless we've defined an @racket[RSHIFT] function too.
These next definitions use @racket[define-syntax-rule] as a shortcut, which is another syntax transformer. (Thanks to @link["https://jeapostrophe.github.io"]{Jay McCarthy} for the 16-bit operations.)
@chunk[<day07-ops>
(define (16bitize x)
(define 16bit-max (expt 2 16))
(define r (modulo x 16bit-max))
(cond
[(negative? r) (16bitize (+ 16bit-max r))]
[else r]))
(define-syntax-rule (define-16bit id proc)
(define id (compose1 16bitize proc)))
(define-16bit AND bitwise-and)
(define-16bit OR bitwise-ior)
(define-16bit LSHIFT arithmetic-shift)
(define-16bit RSHIFT (λ(x y) (arithmetic-shift x (- y))))
(define-16bit NOT bitwise-not)]
After that, we just evaluate wire function @racket[a] to get our answer.
@chunk[<day07-q1>
(define (q1) (a))]
@section{What's the signal on wire @tt{a} if wire @tt{b} is overridden with @tt{a}'s original value?}
Having done the heavy lifting, this is easy. We'll redefine wire function @racket[b] to produce the new value, and then check the value of @racket[a] again.
Ordinarily, as a safety measure, Racket won't let you redefine functions. But we can circumvent this limitation by setting @racket[compile-enforce-module-constants] to @racket[#f]. We'll also need to reset our cache, since this change will affect the other wires too.
@chunk[<day07-q2>
(compile-enforce-module-constants #f)
(define (q2)
(define first-a-val (a))
(set! b (thunk* first-a-val))
(set! wire-value-cache (make-hash))
(a))
]
@section{Testing Day 7}
@chunk[<day07-test>
(module+ test
(check-equal? (q1) 46065)
(check-equal? (q2) 14134))]

@ -0,0 +1,300 @@
"sjdivfriyaaqa\xd2v\"k\"mpcu\"yyu\"en"
"vcqc"
"zbcwgmbpijcxu\"yins\"sfxn"
"yumngprx"
"bbdj"
"czbggabkzo\"wsnw\"voklp\"s"
"acwt"
"aqttwnsohbzian\"evtllfxwkog\"cunzw"
"ugvsgfv"
"xlnillibxg"
"kexh\"pmi"
"syvugow"
"m\"ktqnw"
"yrbajyndte\\rm"
"f\"kak\x70sn\xc4kjri"
"yxthr"
"alvumfsjni\"kohg"
"trajs\x5brom\xf1yoijaumkem\"\"tahlzs"
"\"oedr\"pwdbnnrc"
"qsmzhnx\""
"\"msoytqimx\\tbklqz"
"mjdfcgwdshrehgs"
"\"rivyxahf\""
"ciagc\x04bp"
"xkfc"
"xrgcripdu\x4c\xc4gszjhrvumvz\"mngbirb"
"gvmae\"yiiujoqvr\"mkxmgbbut\"u"
"ih"
"ncrqlejehs"
"mkno\x43pcfdukmemycp"
"uanzoqxkpsksbvdnkji\"feamp"
"axoufpnbx\\ao\x61pfj\"b"
"dz\\ztawzdjy"
"ihne\"enumvswypgf"
"\"dgazthrphbshdo\\vuqoiy\""
"dlnmptzt\\zahwpylc\\b\"gmslrqysk"
"mhxznyzcp"
"rebr\"amvxw\x5fmbnfpkkeghlntavj"
"lades\x47ncgdof\"\"jmbbk"
"dwxuis\xa5wdkx\\z\"admgnoddpgkt\\zs"
"g\\k\x27qsl\x34hwfglcdxqbeclt\xca\\"
"lhyjky\\m\"pvnm\\xmynpxnlhndmahjl"
"c\"uxabbgorrpprw\"xas\\vefkxioqpt"
"rfrvjxpevcmma\x71gtfipo"
"fgh\"kcwoqwfnjgdlzfclprg\"q"
"onxnwykrba"
"hkkg\x60f\"tjzsanpvarzgkfipl"
"\"aintes\"ofq\"juiaqlqxmvpe\\a"
"wiyczzs\"ciwk"
"mfqeu"
"v\xe1z\x7ftzalmvdmncfivrax\\rjwq"
"k\"vtg"
"exhrtdugeml\xf0"
"behnchkpld"
"mhgxy\"mfcrg\xc5gnp\"\"osqhj"
"rlvjy"
"awe"
"ctwy"
"vt"
"\x54t"
"zugfmmfomz"
"cv\"cvcvfaada\x04fsuqjinbfh\xa9cq\xd2c\"d"
"oj"
"xazanf\"wbmcrn"
"\\\\zkisyjpbzandqikqjqvee"
"dpsnbzdwnxk\\v"
"sj\"tuupr\\oyoh"
"myvkgnw\x81q\xaaokt\\emgejbsyvxcl\\\xee"
"ejeuqvunjcirdkkpt\"nlns"
"twmlvwxyvfyqqzu"
"\"xwtzdp\x98qkcis\"dm\\\"ep\"xyykq"
"vvcq\\expok"
"wgukjfanjgpdjb"
"\"mjcjajnxy\\dcpc"
"wdvgnecw\\ab\x44klceduzgsvu"
"dqtqkukr\"iacngufbqkdpxlwjjt"
"\"xj\"\x66qofsqzkoah"
"nptiwwsqdep"
"gsnlxql\x30mjl"
"yeezwokjwrhelny\""
"bjauamn\\izpmzqqasid"
"tvjdbkn\"tiziw\x82r"
"w"
"xwoakbbnjnypnaa\xa9wft\"slrmoqkl"
"vwxtnlvaaasyruykgygrvpiopzygf\"vq"
"qdancvnvmhlmpj\\isdxs"
"xzc\\elw"
"b\"wxeqvy\"qf\"g\xcaoklsucwicyw\"dovr"
"yomlvvjdbngz\"rly\"afr"
"bfb\"x\"aweuwbwmoa\x13\"t\"zhr"
"\"dmfoxb\"qvpjzzhykt\xd2\"\"ryhxi"
"psqef\"yu\\qiflie\"\x79w"
"arzewkej\"lqmh\\sayyusxxo\\"
"vuvvp"
"hc\"lg\x6bcpupsewzklai\"l"
"cjdfygc\"auorqybnuqghsh\x10"
"j"
"wqjexk\"eyq\\lbroqhk\\dqzsqk"
"dws\"ru\"dvxfiwapif\"oqwzmle"
"agcykg\\jt\\vzklqjvknoe"
"kksd\"jmslja\\z\"y\\b\xaagpyojct"
"nnpipxufvbfpoz\"jno"
"dtw"
"xlolvtahvgqkx\\dgnhj\\spsclpcxv\\"
"mxea\\mbjpi"
"lgbotkk\"zmxh\\\\qji\"jszulnjsxkqf"
"lwckmhwhx\"gmftlb\x91am"
"xxdxqyxth"
"\"lmqhwkjxmvayxy"
"tf"
"qy"
"wdqmwxdztax\"m\"\x09\x11xdxmfwxmtqgwvf"
"\xcbnazlf\"ghziknszmsrahaf"
"e\x6aupmzhxlvwympgjjpdvo\"kylfa"
"\x81vhtlillb\xactgoatva"
"dvnlgr"
"f"
"xg\xfacwizsadgeclm"
"vnnrzbtw\"\\prod\\djbyppngwayy\""
"lrt\xf4jahwvfz"
"aqpnjtom\"ymkak\\dadfybqrso\\fwv"
"gz\"aac\"mrbk\"ktommrojraqh"
"wycamwoecsftepfnlcdkm"
"nrhddblbuzlqsl\x9cben"
"vckxhyqkmqmdseazcykrbysm"
"sil\xbbtevmt\"gvrvybui\"faw\"j"
"cjex\\tp\x45pzf"
"asjobvtxszfodgf\"ibftg"
"gkyjyjdrxdcllnh\"sjcibenrdnxv"
"oswsdpjyxpbwnqbcpl\"yrdvs\\zq"
"\"\"tyowzc\\fycbp\"jbwrbvgui"
"cbpcabqkdgzmpgcwjtrchxp"
"iyrzfh\x45gw\"fdlfpiaap\x31xqq"
"evgksznidz"
"b\\w\\"
"loufizbiy\x57aim\"bgk"
"qjfyk"
"g\"anmloghvgr\x07zwqougqhdz"
"usbbmwcxd\\bdgg"
"htitqcpczml"
"eke\\cqvpexqqk\"to\"tqmljrpn\xe6lji\""
"g\xd2ifdsej"
"h\"sk\"haajajpagtcqnzrfqn\xe6btzo"
"wfkuffdxlvm\\cvlyzlbyunclhmpp"
"myaavh\"spue"
"hqvez\x68d\"eo\"eaioh"
"s\"qd\"oyxxcglcdnuhk"
"ilqvar"
"srh"
"puuifxrfmpc\"bvalwi\x2blu\\"
"yywlbutufzysbncw\\nqsfbhpz\"mngjq"
"zbl\\jfcuop"
"hjdouiragzvxsqkreup\\"
"qi"
"ckx\\funlj\xa7ahi"
"k"
"ufrcnh\"ajteit"
"cqv\"bgjozjj\x60x\xa8yhvmdvutchjotyuz"
"hkuiet\"oku\x8cfhumfpasl"
"\"\\sbe\x4d"
"vhknazqt"
"eyyizvzcahgflvmoowvs\\jhvygci"
"kki\x3ewcefkgtjap\"xtpxh\"lzepoqj"
"wvtk"
"\"ynet"
"zh\\obk\"otagx\x59txfzf"
"ocowhxlx\xe6zqg\x63wx\\tclkhq\\vmaze"
"w\"cf"
"qpniprnrzrnvykghqnalr"
"jctcqra\"\x05dhlydpqamorqjsijt\\xjdgt"
"sig"
"qhlbidbflwxe\"xljbwls\x20vht"
"irmrebfla\xefsg\"j"
"nep"
"hjuvsqlizeqobepf"
"guzbcdp\"obyh"
"\"mjagins\xf9tqykaxy\""
"knvsdnmtr\"zervsb"
"hzuy"
"zza\"k\"buapb\\elm\xfeya"
"lrqar\"dfqwkaaqifig\"uixjsz"
"\"azuo\x40rmnlhhluwsbbdb\x32pk\\yu\"pbcf"
"dplkdyty"
"rfoyciebwlwphcycmguc"
"ivnmmiemhgytmlprq\\eh"
"lhkyzaaothfdhmbpsqd\\yyw"
"tnlzifupcjcaj"
"\\qiyirsdrfpmu\\\x15xusifaag"
"\\lcomf\\s"
"uramjivcirjhqcqcg"
"kkbaklbxfxikffnuhtu\xc6t\"d"
"n\xefai"
"\"toy\"bnbpevuzoc\"muywq\"gz\"grbm"
"\"muu\\wt"
"\\srby\"ee"
"erf\"gvw\"swfppf"
"pbqcgtn\"iuianhcdazfvmidn\\nslhxdf"
"uxbp"
"up\\mgrcyaegiwmjufn"
"nulscgcewj\\dvoyvhetdegzhs\""
"masv\"k\\rzrb"
"qtx\x79d\"xdxmbxrvhj"
"fid\\otpkgjlh\"qgsvexrckqtn\xf4"
"tagzu"
"bvl\\\"noseec"
"\\xgicuuh"
"w\"a\"npemf"
"sxp"
"nsmpktic\x8awxftscdcvijjobnq\"gjd"
"uks\"\"jxvyvfezz\"aynxoev\"cuoav"
"m"
"lkvokj"
"vkfam\"yllr\"q\x92o\x4ebecnvhshhqe\\"
"efdxcjkjverw"
"lmqzadwhfdgmep\x02tzfcbgrbfekhat"
"cpbk\x9azqegbpluczssouop\x36ztpuoxsw"
"cqwoczxdd\"erdjka"
"cwvqnjgbw\\fxdlby"
"mvtm"
"lt\"bbqzpumplkg"
"ntd\xeeuwweucnuuslqfzfq"
"y\xabl\"dbebxjrlbmuoo\\\x1au"
"qjoqx\\a"
"pu\"ekdnfpmly\xbago\""
"fjhhdy"
"arl"
"xcywisim\"bwuwf\"\"raepeawwjub"
"pbe"
"dbnqfpzyaumxtqnd\xc5dcqrkwyop"
"ojv\x40vtkwgkqepm\x8bzft\\vedrry"
"wggqkfbwqumsgajqwphjec\"mstxpwz"
"zjkbem"
"icpfqxbelxazlls"
"pvpqs\\abcmtyielugfgcv\"tjxapxqxnx"
"oqddwlvmtv\"\x39lyybylfb\"jmngnpjrdw"
"gisgbve"
"\"aglg"
"y\"\"ss\xafvhxlrjv"
"qbgqjsra"
"ihshbjgqpdcljpmdwdprwloy"
"djja\\wcdn\"svkrgpqn\"uz\"hc\x43hj"
"cbjm"
"pnn"
"pqvh\"noh"
"\"\\fdktlp"
"ncea"
"pqgzphiyy"
"\xbedovhxuipaohlcvkwtxwmpz\"ckaif\"r"
"arjuzbjowqciunfwgxtph\"vlhy\"n"
"c"
"nrpdxunulgudqzlhtae"
"iefheu\"uru\""
"aqijysxuijud\"np\\opbichhudil\xbesum"
"pfpevmtstl\"lde\"bzr\"vspdxs"
"vparfbdjwvzsocpnzhp"
"g\x4ffxaarafrsjthq\\\xc1rw"
"ng\\rqx\\gwpzucbh\xafl"
"rw\"nf\\dna"
"jkkeahxurxla\\g\xb3czrlsyimmwcwthr"
"twaailoypu\"oas\"kpuuyedlaw\\\xb0vzt"
"hznex\\gdiqvtugi"
"imdibsunjeswhk"
"ta\\icileuzpxro\"cfmv\"mzp"
"coykr\x57luiysucfaflmilhlehmvzeiepo"
"u\x3dfh\xd4yt"
"piw\x1bz\"eowy\"vfk\"wqiekw"
"gan\"y"
"p\"bevidoazcznr\"hddxuuq\""
"bwzucczznutbxe"
"z\"viqgyqjisior\\iecosmjbknol"
"dmlpcglcfkfsctxydjvayhymv\x3c\\gp"
"bfvkqrintbbvgfv"
"xlzntrgdck\"cprc\xadczyarbznqmuhxyuh"
"uqdxnuwioc\"kdytxq\\ig"
"xrafmucpmfi"
"vr\"hltmfrge"
"eonf\"nt\\wtcnsocs"
"j\xb7xoslyjeyjksplkqixncgkylkw"
"njw\"pefgfbez\x9axshdmplxzquqe"
"di\x58bvptfsafirpc"
"l\x1fkco"
"x"
"mprndo\"n"
"psegit"
"svbdnkkuuqs\"sqxu\"oqcyz\"aizashk"
"cwkljukxer\\\"\\nff\"esjwiyaoy"
"ilxrkgbjjxpvhdtq\"cpiuoofdnkpp"
"hlngi\"ulxep\\qohtmqnqjb\"rkgerho"
"gxws\"bcgm\"p"
"bv\"mds\\zhfusiepgrz\\b\x32fscdzz"
"l\xfampwtme\x69qvxnx\"\"\xc4jruuymjxrpsv"
"qqmxhrn"
"xziq\\\x18ybyv\x9am\"neacoqjzytertisysza"
"aqcbvlvcrzceeyx\\j\"\"x"
"yjuhhb"
"\x5em\"squulpy"
"dpbntplgmwb"
"utsgfkm\\vbftjknlktpthoeo"
"ccxjgiocmuhf\"ycnh"
"lltj\"kbbxi"

@ -0,0 +1,57 @@
#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@aoc-title[8]
@defmodule[aoc-racket/day08]
@link["http://adventofcode.com/day/8"]{The puzzle}. Our @link-rp["day08-input.txt"]{input} consists of a list of seemingly random strings within quotation marks.
@chunk[<day08>
<day08-setup>
<day08-q1>
<day08-q2>
<day08-test>]
@section{What's the difference between the literal length of the strings, and their length in memory?}
The puzzle relies the fact that within strings, certain single characters  like the backslash @litchar{\} and double-quote mark @litchar{"} — are described with more than one character. Thus, the question asks us to compare the two lengths.
The literal length of the string is trivial  use @racket[string-length]. The memory length requires interpreting a string as a Racket value, which (as seen in @secref{Day_7}) simply means using @racket[read].
@chunk[<day08-setup>
(require racket rackunit)
(provide (all-defined-out))
]
@chunk[<day08-q1>
(define (memory-length str) (string-length (read (open-input-string str))))
(define (q1 strs)
(- (apply + (map string-length strs)) (apply + (map memory-length strs))))]
@section{What's the difference between the re-encoded length of the literal string, and the original length?}
This question simply comes down to  do you know how to use the string-formatting functions in your programming language?
In Racket, a string can be re-encoded with @racket[~v]. Not a very puzzling puzzle overall.
@chunk[<day08-q2>
(define (encoded-length str) (string-length (~v str)))
(define (q2 strs)
(- (apply + (map encoded-length strs)) (apply + (map string-length strs)))) ]
@section{Testing Day 8}
@chunk[<day08-test>
(module+ test
(define input-strs (file->lines "day08-input.txt"))
(check-equal? (q1 input-strs) 1333)
(check-equal? (q2 input-strs) 2046))]

@ -0,0 +1,28 @@
Tristram to AlphaCentauri = 34
Tristram to Snowdin = 100
Tristram to Tambi = 63
Tristram to Faerun = 108
Tristram to Norrath = 111
Tristram to Straylight = 89
Tristram to Arbre = 132
AlphaCentauri to Snowdin = 4
AlphaCentauri to Tambi = 79
AlphaCentauri to Faerun = 44
AlphaCentauri to Norrath = 147
AlphaCentauri to Straylight = 133
AlphaCentauri to Arbre = 74
Snowdin to Tambi = 105
Snowdin to Faerun = 95
Snowdin to Norrath = 48
Snowdin to Straylight = 88
Snowdin to Arbre = 7
Tambi to Faerun = 68
Tambi to Norrath = 134
Tambi to Straylight = 107
Tambi to Arbre = 40
Faerun to Norrath = 11
Faerun to Straylight = 66
Faerun to Arbre = 144
Norrath to Straylight = 115
Norrath to Arbre = 135
Straylight to Arbre = 127

@ -0,0 +1,80 @@
#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@aoc-title[9]
@defmodule[aoc-racket/day09]
@link["http://adventofcode.com/day/9"]{The puzzle}. Our @link-rp["day09-input.txt"]{input} consists of a list of distances between fictional cities, e.g., @italic{AlphaCentauri to Straylight = 133}.
@chunk[<day09>
<day09-setup>
<day09-q1>
<day09-q2>
<day09-test>]
@section{What's the shortest route that visits all the cities?}
This puzzle is a version of the famous @link["https://simple.wikipedia.org/wiki/Travelling_salesman_problem"]{traveling-salesman problem}. The problem is famous because there's no reasonable algorithm to solve it for arbitrarily large sets of cities. This version, however, has only eight cities. So it is possible (and easiest) to simply try all the options and see which is shortest.
The solution has two parts. First, we'll parse our input data and put the distances into a mutable hash table. One small wrinkle the distance between city A and city B is the same whether our path takes us from A to B or B to A. So the keys for our hash will be of the form @racket[(list city-a city-b)], with the cities always in alphabetical order.
In the second part, we'll loop through every possible path between the cities with @racket[in-permutations]. We'll split each path into pairs of cities, look up each distance between pairs, and sum them. This will give us a list of distances, and we can find the smallest with @racket[apply min].
@margin-note{The reason the traveling-saleman problem is generally difficult is that the number of permutations of @racket[_n] cities is @racket[(factorial (sub1 _n))], which gets very large, very quickly.}
@chunk[<day09-setup>
(require racket rackunit)
(provide (all-defined-out))
(define distances (make-hash))
(define (str->hash ln)
(match-define (list _ here there dist)
(regexp-match #px"^(\\w+) to (\\w+) = (\\d+)" ln))
(define key (places->key here there))
(hash-set! distances key (string->number dist)))
(define (places->key here there)
(sort (list (string-downcase here) (string-downcase there)) string<?))
(define (calculate-route-distances)
(define (pairify xs)
(map list (drop-right xs 1) (drop xs 1)))
(define (distance here there)
(hash-ref distances (places->key here there) +inf.0))
(define cities (remove-duplicates (append* (hash-keys distances))))
(for/list ([route (in-permutations cities)])
(for/sum ([pair (in-list (pairify route))])
(apply distance pair))))
]
@chunk[<day09-q1>
(define (q1 strs)
(for-each str->hash strs)
(apply min (calculate-route-distances)))]
@section{What's the longest route?}
Exactly the same, except we look for the @racket[max] value among the distances rather than the @racket[min].
@chunk[<day09-q2>
(define (q2 strs)
(apply max (calculate-route-distances))) ]
@section{Testing Day 9}
@chunk[<day09-test>
(module+ test
(define input-strs (file->lines "day09-input.txt"))
(check-equal? (q1 input-strs) 251)
(check-equal? (q2 input-strs) 898))]

@ -0,0 +1 @@
1321131112

@ -0,0 +1,66 @@
#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@aoc-title[10]
@defmodule[aoc-racket/day10]
@link["http://adventofcode.com/day/10"]{The puzzle}. Our @link-rp["day10-input.txt"]{input} is a short numeric key.
@chunk[<day10>
<day10-setup>
<day10-q1>
<day10-q2>
<day10-test>]
@section{What's the length of the sequence after 40 iterations?}
The puzzle asks us to compute the @italic{look and say} sequence invented by mathematician John Conway. Each iteration of the sequence is the description of the last step if you said it in numbers. So @racket[1] becomes ``one 1'', written @racket[11]; @racket[11] becomes ``two ones'', or @racket[21], then @racket[1211], @racket[111221], and so on.
As in @secref{Day_1}, this puzzle relies on cumulative state, so we'll loop using @racket[for/fold]. To generate the new string for each pass of the loop, we'll use @racket[regexp-match*] to find every contiguous run of digits. Each digit run is converted into a list with the number of digits and the digit itself. Then all these lists are concatenated into a new string, and the loop repeats.
The second part of the puzzle is just going to change the number of iterations. So we'll make one function that can be used for both parts.
@chunk[<day10-setup>
(require racket rackunit)
(provide (all-defined-out))
(define (look-and-say iterations input-key)
(for/fold ([start input-key])
([i (in-range iterations)])
(define digit-runs (regexp-match* #px"(\\d)\\1*" start))
(string-append*
(map ~a
(append-map (λ(digit-run)
(list (string-length digit-run)
(substring digit-run 0 1)))
digit-runs)))))
]
@chunk[<day10-q1>
(define (q1 input-key)
(string-length (look-and-say 40 input-key)))]
@section{After 50 iterations?}
We use the same @racket[look-and-say] function, but with an iteration argument of @racket[50] rather than @racket[40].
@chunk[<day10-q2>
(define (q2 input-key)
(string-length (look-and-say 50 input-key))) ]
@section{Testing Day 10}
@chunk[<day10-test>
(module+ test
(define input-key (file->string "day10-input.txt"))
(check-equal? (q1 input-key) 492982)
(check-equal? (q2 input-key) 6989950))]

@ -0,0 +1 @@
hxbxwxba

@ -0,0 +1,102 @@
#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@aoc-title[11]
@defmodule[aoc-racket/day11]
@link["http://adventofcode.com/day/11"]{The puzzle}. Our @link-rp["day11-input.txt"]{input} is a short alphabetic key that represents a password.
@chunk[<day11>
<day11-setup>
<day11-q1>
<day11-q2>
<day11-test>]
@section{What's the next password that meets the criteria?}
Though the password is alphabetic, we can increment it as we would a numerical password, by changing the rightmost letter to the next letter (for instance @litchar{x} to @litchar{y}, @litchar{y} to @litchar{z}). When we reach @litchar{z}, we roll over to @litchar{a}, and ``carry over'' the surplus by incrementing the letter to the left.
Furthermore, like @secref{Day_5}, the puzzle provides certain criteria that must be met:
@itemlist[
@item{The password must have a sequence of three consecutive letters (like @litchar{bcd}).}
@item{The password may not contain @litchar{i}, @litchar{o}, or @litchar{l}.}
@item{The password must contain two different, non-overlapping pairs of letters.}
]
As in @secref{Day_5}, we'll use @racket[regexp-match] to implement tests for these conditions. We'll also use @racket[regexp-replace*] to build the function that increments a password alphabetically. Then it's a simple matter of looking at passwords until we find one that works.
The @racket[increment-password] function works by using the observation that if the password ends in any number of @litchar{z}s, you have to roll them over to @litchar{a} and increment the letter to the left. Otherwise, you can just increment the last letter  which is actually the same rule, but with zero @litchar{z}s. This logic can all be captured in one regular expression @racket[#rx"^(.*?)(.)(z*)$"].
The @racket[three-consecutive-letters?] test works by converting the letters to numbers and creating a list of the differences betweeen adjacent values. Any three consecutive letters will differ by value of @racket[1]. So if the list of differences contains the subsequence @racket['(1 1)], then the string has three consecutive letters.
@chunk[<day11-setup>
(require racket rackunit)
(provide (all-defined-out))
(define (increment-password password)
(define (increment-letter c)
((compose1 ~a integer->char add1 char->integer car string->list) c))
(match-define (list _ prefix letter-to-increment trailing-zs)
(regexp-match #rx"^(.*?)(.)(z*)$" password))
(string-append* (list prefix (increment-letter letter-to-increment)
(regexp-replace* #rx"z" trailing-zs "a"))))
(define (three-consecutive-letters? str)
(define ints (map char->integer (string->list str)))
(let loop ([differences (map - (cdr ints) (drop-right ints 1))])
(if (empty? differences)
#f
(or (list-prefix? '(1 1) differences) (loop (cdr differences))))))
(define (no-iol? str)
(not (regexp-match #rx"[iol]" str)))
(define (two-nonoverlapping-doubles? str)
(regexp-match #px"(\\w)\\1.*?(\\w)\\2" str))
(define (valid? password)
(and (three-consecutive-letters? password)
(no-iol? password)
(two-nonoverlapping-doubles? password)))
(define (find-next-valid-password starting-password)
(define candidate-pw (increment-password starting-password))
(if (valid? candidate-pw)
candidate-pw
(find-next-valid-password candidate-pw)))
]
@chunk[<day11-q1>
(define (q1 input-key)
(find-next-valid-password input-key))]
@section{What's the next valid password after that?}
We take the answer to question 1 and use it as input to the same function.
@chunk[<day11-q2>
(define (q2 input-key)
(find-next-valid-password (q1 input-key))) ]
@section{Testing Day 11}
@chunk[<day11-test>
(module+ test
(define input-key (file->string "day11-input.txt"))
(check-equal? (q1 input-key) "hxbxxyzz")
(check-equal? (q2 input-key) "hxcaabcc"))]

File diff suppressed because one or more lines are too long

@ -0,0 +1,86 @@
#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@aoc-title[12]
@defmodule[aoc-racket/day12]
@link["http://adventofcode.com/day/12"]{The puzzle}. Our @link-rp["day12-input.txt"]{input} is, unfortunately, a @link["http://json.org/"]{JSON} file.
@chunk[<day12>
<day12-setup>
<day12-q1>
<day12-q2>
<day12-test>]
@section{What's the sum of all the numbers in the document?}
I've never liked JavaScript, and spending more time with Racket has only deepened my antipathy. So I apologize if this solution is terse.
We need to parse the JSON file, extract the numbers, and add them.
To parse the file we'll use the @racket[read-json] function from Racket's @racketmodname[json] library. This function converts the JSON into a JS-expression (see @racket[jsexpr?]), which is a recursively nested data structure. If we had a simple recursively nested list, we could just @racket[flatten] it and filter for the numbers. We'll do something similar here  recursively flatten the JS-expression and pull out the numbers.
If you're new to Racket, notice the @italic{recursive descent} pattern used in @racket[flatten-jsexpr]  it's a very common way of handling recursively structured data.
@chunk[<day12-setup>
(require racket rackunit json)
(provide (all-defined-out))
(define (string->jsexpr str)
(read-json (open-input-string str)))
]
@chunk[<day12-q1>
(define (flatten-jsexpr jsexpr)
(flatten
(let loop ([x jsexpr])
(cond
[(list? x)
(map loop x)]
[(hash? x)
(loop (flatten (hash->list x)))]
[else x]))))
(define (q1 input-str)
(define json-items (flatten-jsexpr (string->jsexpr input-str)))
(apply + (filter number? json-items)))]
@section{What's the sum of all the numbers, if hash tables with value @racket{red} are ignored?}
We'll just update our flattening function to skip over hash tables that have @racket{red} among the values.
@chunk[<day12-q2>
(define (flatten-jsexpr-2 jsexpr)
(flatten
(let loop ([x jsexpr])
(cond
[(list? x)
(map loop x)]
[(hash? x)
(if (member "red" (hash-values x))
empty
(loop (flatten (hash->list x))))]
[else x]))))
(define (q2 input-str)
(define json-items (flatten-jsexpr-2 (string->jsexpr input-str)))
(apply + (filter number? json-items))) ]
@section{Testing Day 12}
@chunk[<day12-test>
(module+ test
(define input-str (file->string "day12-input.txt"))
(check-equal? (q1 input-str) 191164)
(check-equal? (q2 input-str) 87842))]

@ -0,0 +1,56 @@
Alice would gain 54 happiness units by sitting next to Bob.
Alice would lose 81 happiness units by sitting next to Carol.
Alice would lose 42 happiness units by sitting next to David.
Alice would gain 89 happiness units by sitting next to Eric.
Alice would lose 89 happiness units by sitting next to Frank.
Alice would gain 97 happiness units by sitting next to George.
Alice would lose 94 happiness units by sitting next to Mallory.
Bob would gain 3 happiness units by sitting next to Alice.
Bob would lose 70 happiness units by sitting next to Carol.
Bob would lose 31 happiness units by sitting next to David.
Bob would gain 72 happiness units by sitting next to Eric.
Bob would lose 25 happiness units by sitting next to Frank.
Bob would lose 95 happiness units by sitting next to George.
Bob would gain 11 happiness units by sitting next to Mallory.
Carol would lose 83 happiness units by sitting next to Alice.
Carol would gain 8 happiness units by sitting next to Bob.
Carol would gain 35 happiness units by sitting next to David.
Carol would gain 10 happiness units by sitting next to Eric.
Carol would gain 61 happiness units by sitting next to Frank.
Carol would gain 10 happiness units by sitting next to George.
Carol would gain 29 happiness units by sitting next to Mallory.
David would gain 67 happiness units by sitting next to Alice.
David would gain 25 happiness units by sitting next to Bob.
David would gain 48 happiness units by sitting next to Carol.
David would lose 65 happiness units by sitting next to Eric.
David would gain 8 happiness units by sitting next to Frank.
David would gain 84 happiness units by sitting next to George.
David would gain 9 happiness units by sitting next to Mallory.
Eric would lose 51 happiness units by sitting next to Alice.
Eric would lose 39 happiness units by sitting next to Bob.
Eric would gain 84 happiness units by sitting next to Carol.
Eric would lose 98 happiness units by sitting next to David.
Eric would lose 20 happiness units by sitting next to Frank.
Eric would lose 6 happiness units by sitting next to George.
Eric would gain 60 happiness units by sitting next to Mallory.
Frank would gain 51 happiness units by sitting next to Alice.
Frank would gain 79 happiness units by sitting next to Bob.
Frank would gain 88 happiness units by sitting next to Carol.
Frank would gain 33 happiness units by sitting next to David.
Frank would gain 43 happiness units by sitting next to Eric.
Frank would gain 77 happiness units by sitting next to George.
Frank would lose 3 happiness units by sitting next to Mallory.
George would lose 14 happiness units by sitting next to Alice.
George would lose 12 happiness units by sitting next to Bob.
George would lose 52 happiness units by sitting next to Carol.
George would gain 14 happiness units by sitting next to David.
George would lose 62 happiness units by sitting next to Eric.
George would lose 18 happiness units by sitting next to Frank.
George would lose 17 happiness units by sitting next to Mallory.
Mallory would lose 36 happiness units by sitting next to Alice.
Mallory would gain 76 happiness units by sitting next to Bob.
Mallory would lose 34 happiness units by sitting next to Carol.
Mallory would gain 37 happiness units by sitting next to David.
Mallory would gain 40 happiness units by sitting next to Eric.
Mallory would gain 18 happiness units by sitting next to Frank.
Mallory would gain 7 happiness units by sitting next to George.

@ -0,0 +1,106 @@
#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@aoc-title[13]
@defmodule[aoc-racket/day13]
@link["http://adventofcode.com/day/13"]{The puzzle}. Our @link-rp["day13-input.txt"]{input} is a list of descriptions of ``happiness units'' that would be gained or lost among eight people sitting next to each other at a dinner table.
@chunk[<day13>
<day13-setup>
<day13-q1>
<day13-q2>
<day13-test>]
@section{What's the optimal happiness score for a seating arrangement of eight?}
This is a lot like @secref{Day_9}, where we had to compute the optimal path between cities. In that puzzle, the distance between city A and city B was a single number. In this case, the ``happiness score'' between person A and person B is the sum of two numbers  A's happiness being next to B, and B's happiness being next to A. (Unlike distances, happiness scores can be negative.)
Also, whereas a path between cities had a start and end, a seating arrangement is circular. So if we model a seating arrangement as a list of people, we have to compute the happiness between each duo of people, but also between the last and first, to capture the circularity of the arrangement.
Those wrinkles noted, we'll proceed as we did in @secref{Day_9}. We'll parse the input data and put the happiness scores into a hash table  the keys will be of the form @racket[(list name1 name2)] and the values will be the happiness scores for that duo, in that order. Then we'll loop through all possible seating arrangements with @racket[in-permutations] and see what the best score is.
@chunk[<day13-setup>
(require racket rackunit)
(provide (all-defined-out))
(define happiness-scores (make-hash))
(define (parse-happiness-score ln)
(define result
(regexp-match #px"^(.*?) would (gain|lose) (\\d+) happiness units by sitting next to (.*?)\\.$" (string-downcase ln)))
(when result
(match-define (list _ name1 op amount name2) result)
(hash-set! happiness-scores (list name1 name2)
((if (equal? op "gain") + -) (string->number amount)))))
(define (calculate-happiness table-arrangement)
(define table-arrangement-rotated-one-place
(append (drop table-arrangement 1) (take table-arrangement 1)))
(define clockwise-duos
(map list table-arrangement table-arrangement-rotated-one-place))
(define counterclockwise-duos (map reverse clockwise-duos))
(define all-duos (append clockwise-duos counterclockwise-duos))
(for/sum ([duo (in-list all-duos)])
(hash-ref happiness-scores duo)))
]
@subsection{Optimizing @racket[in-permutations]}
I'm in a math-jock mood, so let's make a performance optimization. It's unnecessary for this problem, but when we use @racket[in-permutations]  which grows at factorial speed  we should ask how we might prune the options.
Notice that because our seating arrangement is circular, our permutations will include a lot of ``rotationally equivalent'' arrangements e.g., @racket['(A B C ...)] is the same as @racket['(B C ... A)], @racket['(C ... A B)], etc. If we have @racket[_n] elements, each distinct arrangement will have @racket[_n] rotationally equivalent arrangements. We can save time by only checking one of each set.
How? By only looking at arrangements starting with a particular name. Doesn't matter which. This will work because every name has to appear in every arrangement. To do this, we could generate all the permutations and use a @racket[#:when] clause to select the ones we want. But it's even more efficient to only permute @racket[(sub1 _n)] names, and then @racket[cons] our first-position name onto each partial arrangement, which will produce the same set of arrangements. Thus we only have to generate and score @racket[(/ 1 _n)] of the original permutations.
@chunk[<day13-q1>
(define (q1 input-str)
(for-each parse-happiness-score (string-split input-str "\n"))
(define names
(remove-duplicates (flatten (hash-keys happiness-scores))))
(define table-arrangement-scores
(for/list ([partial-table-arrangement (in-permutations (cdr names))])
(define table-arrangement (cons (car names) partial-table-arrangement))
(calculate-happiness table-arrangement)))
(apply max table-arrangement-scores))]
@section{What's the optimal happiness score, including ourself in the seating?}
We can reuse our hash table of @racket[happiness-scores], but we have to update it with scores for ourself seated next to every other person, which in every case is @racket[0]. (The meaning of @racket[(in-list (list list (compose1 reverse list)))] is a small puzzle I leave for you.) Then we find the optimal score the same way.
@chunk[<day13-q2>
(define (q2 input-str)
(define names
(remove-duplicates (flatten (hash-keys happiness-scores))))
(for* ([name (in-list names)]
[duo-proc (in-list (list list (compose1 reverse list)))])
(hash-set! happiness-scores (duo-proc "me" name) 0))
(define table-arrangement-scores
(for/list ([partial-table-arrangement (in-permutations names)])
(define table-arrangement (cons "me" partial-table-arrangement))
(calculate-happiness table-arrangement)))
(apply max table-arrangement-scores))
]
@section{Testing Day 13}
@chunk[<day13-test>
(module+ test
(define input-str (file->string "day13-input.txt"))
(check-equal? (q1 input-str) 709)
(check-equal? (q2 input-str) 668))]

@ -0,0 +1,9 @@
Dancer can fly 27 km/s for 5 seconds, but then must rest for 132 seconds.
Cupid can fly 22 km/s for 2 seconds, but then must rest for 41 seconds.
Rudolph can fly 11 km/s for 5 seconds, but then must rest for 48 seconds.
Donner can fly 28 km/s for 5 seconds, but then must rest for 134 seconds.
Dasher can fly 4 km/s for 16 seconds, but then must rest for 55 seconds.
Blitzen can fly 14 km/s for 3 seconds, but then must rest for 38 seconds.
Prancer can fly 3 km/s for 21 seconds, but then must rest for 40 seconds.
Comet can fly 18 km/s for 6 seconds, but then must rest for 103 seconds.
Vixen can fly 18 km/s for 5 seconds, but then must rest for 84 seconds.

@ -0,0 +1,105 @@
#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@aoc-title[14]
@defmodule[aoc-racket/day14]
@link["http://adventofcode.com/day/14"]{The puzzle}. Our @link-rp["day14-input.txt"]{input} is a list of flying-reindeer descriptions  in particular, how fast they can fly, and how long they have to rest between flight sessions, e.g., @italic{Dancer can fly 27 km/s for 5 seconds, but then must rest for 132 seconds.}
@chunk[<day14>
<day14-setup>
<day14-q1>
<day14-q2>
<day14-test>]
@section{After 2503 seconds, what is the maximum distance any reindeer has flown?}
For each reindeer, we have a description that specifies a) flight speed, b) flight time, and c) rest time. Thus, if we have the total flight time as input  and we do we can use it to calculate flight distance. In other words, just as we made functions out of wire descriptions on @secref{Day_7}, now we'll make functions out of reindeer descriptions.
As in @secref{Day_7}, we'll use @racket[define-syntax] to set up the reindeer functions. Each reindeer function will take a time in seconds and then use @racket[calc-distance] to convert it into meters. After that, all we need to do is @racket[map] the time over the reindeer and @racket[apply max] to get the answer.
@chunk[<day14-setup>
(require racket rackunit (for-syntax racket/file))
(provide (all-defined-out))
(define-syntax (convert-input-to-reindeer-functions stx)
(syntax-case stx ()
[(_)
(let* ([input-strings (file->lines "day14-input.txt")]
[reindeer-strings
(map (λ(str) (format "(reindeer ~a)" (string-downcase str))) input-strings)]
[reindeer-datums
(map (compose1 read open-input-string) reindeer-strings)])
(datum->syntax stx `(begin ,@reindeer-datums)))]))
(define-syntax (reindeer stx)
(syntax-case stx (can fly seconds but then must rest for)
[(_ deer-name can fly speed km/s for fly-secs seconds, but then must rest for rest-secs seconds.)
#'(define (deer-name total-secs)
(calc-distance total-secs speed fly-secs rest-secs))]
[else #'(void)]))
(convert-input-to-reindeer-functions)
(define (calc-distance total-secs speed fly-secs rest-secs)
(let loop ([secs-remaining total-secs][distance 0])
(if (<= secs-remaining 0)
distance
(let ([secs-in-flight (min secs-remaining fly-secs)])
(loop (- secs-remaining fly-secs rest-secs)
(+ (* secs-in-flight speed) distance))))))
]
@chunk[<day14-q1>
(define (q1)
(define seconds-to-travel 2503)
(apply max (map (λ(deer-func) (deer-func seconds-to-travel))
(list dasher dancer prancer vixen comet
cupid donner blitzen rudolph))))]
@section{Under the new rule, how many points does the winning reindeer have?}
The new rule is that after each second of travel, the reindeer in the lead gets one point. Thus, the winner after 2503 seconds is not the reindeer that has traveled farthest, but that has gathered the most points  in other words, has been in the lead for the longest time.
This question is similar to the last. But instead of simulating one race, we have to simulate 2503 races, each one ending a second later than the last. After each second, we calculate the winning reindeer, and add it to our list of winners. After 2503 seconds, we find out how many times the winningest reindeer appears on the list. To do this, we'll use the helper function @racket[frequency-hash] from @racketmodname[sugar/list]. (You could also do it with @racket[for/fold], but managing nine reindeer in parallel is unwieldy. Just ask Santa.)
@chunk[<day14-q2>
(require sugar/list)
(define (q2)
(define deer-funcs (list dasher dancer prancer vixen comet
cupid donner blitzen rudolph))
(define winners
(frequency-hash
(flatten
(for/list ([sec (in-range 1 (add1 2503))])
(define deer-results
(map (λ(deer-func) (deer-func sec)) deer-funcs))
(define max-result (apply max deer-results))
(map (λ(deer-result deer-func)
(if (= deer-result max-result)
deer-func
empty))
deer-results deer-funcs)))))
(apply max (hash-values winners)))
]
@section{Testing Day 14}
@chunk[<day14-test>
(module+ test
(define input-str (file->string "day14-input.txt"))
(check-equal? (q1) 2640)
(check-equal? (q2) 1102))]

@ -0,0 +1,4 @@
Frosting: capacity 4, durability -2, flavor 0, texture 0, calories 5
Candy: capacity 0, durability 5, flavor -1, texture 0, calories 8
Butterscotch: capacity -1, durability 0, flavor 5, texture 0, calories 6
Sugar: capacity 0, durability 0, flavor -2, texture 2, calories 1

@ -0,0 +1,112 @@
#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@aoc-title[15]
@defmodule[aoc-racket/day15]
@link["http://adventofcode.com/day/15"]{The puzzle}. Our @link-rp["day15-input.txt"]{input} is a list of four cookie ingredients. Each ingredient has scores for capacity, durability, flavor, texture, and calories in each teaspoon.
@chunk[<day15>
<day15-setup>
<day15-q1>
<day15-q2>
<day15-test>]
@section{What's the best cookie we can make with 100 tsps of ingredients?}
This is similar the @secref{Day_14} puzzle. Rather than maximizing reindeer distance after 2503 seconds, we're maximizing the score of a cookie after 100 teaspoons of ingredients. But while our ``recipe'' for a reindeer race included a full measure of each reindeer, our cookie recipes can have any combination of ingredients, as long as they total 100 teaspoons. Thus, similar to combinatoric problems like @secref{Day_9} and @secref{Day_13}, we have to generate all possible cookie recipes that total 100 teaspoons, and then find the best-scoring recipe.
Let's do the receipe generator first, since that's the new element. A recipe is a list of teaspoon amounts. A recipe can have any number of teaspoons for each ingredient, as long as they total to 100. This suggests a typical Rackety recursive pattern where we consider the possible amounts for the first ingredient (0100 tsps) and then recursively generate recipes for the rest of the ingredients. For instance, suppose we have ingredients @racket['(A B C D)]. If we use 5 tsp of @racket[A], we can combine this with every @racket['(B C D)] recipe that totals 95 tsps. In turn, if we use 10 tsp of @racket[B], we can combine this with every @racket['(C D)] recipe that totals 85 tsps. And so on, generating the whole tree of possibilities.
As for the scoring. The scoring function is not per-ingredient. Rather, the quantity of each characteristic  other than calories, which appears last in each list of ingredient characteristics is summed. Negative values are rounded up to zero. Then these characteristic scores are @italic{multiplied} to get the cookie score. (In this puzzle, the reading comprehension is harder than the coding.)
Having surveyed the territory, making ingredient functions from the text descriptions seems a little overblown. It's just as convenient to represent each ingredient as a list of its characteristic values. So let's just parse the input into lists of values and store them in a hash.
@chunk[<day15-setup>
(require racket rackunit)
(provide (all-defined-out))
(define (str->ingredient-hash str)
(for/hash ([ln (in-list (string-split (string-replace str "," " ") "\n"))])
(match-define (list ingredient-name characteristic-string)
(string-split ln ":"))
(values ingredient-name
(filter number?
(map string->number
(string-split characteristic-string))))))
(define (make-recipes how-many-ingredients total-tsps)
(cond
[(= 0 how-many-ingredients) empty]
[(= 1 how-many-ingredients) (list (list total-tsps))]
[else
(append*
(for/list ([first-amount (in-range (add1 total-tsps))])
(map (curry cons first-amount)
(make-recipes (sub1 how-many-ingredients)
(- total-tsps first-amount)))))]))
]
@chunk[<day15-q1>
(define (q1 input-str)
(define ingredient-hash (str->ingredient-hash input-str))
(define ingredients (hash-keys ingredient-hash))
(define how-many-characteristics (length (car (hash-values ingredient-hash))))
(define tsps 100)
(define scores
(for/list ([recipe (in-list (make-recipes (length ingredients) tsps))])
(for/product ([char-idx (in-range (sub1 how-many-characteristics))])
(max 0 (for/sum ([tsp-quantity (in-list recipe)]
[ingredient (in-list ingredients)])
(* tsp-quantity
(list-ref (hash-ref ingredient-hash ingredient) char-idx)))))))
(apply max scores))]
@section{What's the best cookie we can make with 100 tsps that's exactly 500 calories?}
Same as the first question, but we'll add a @racket[#:when] clause to our recipe loop to only consider recipes equal to 500 calories, and a @racket[recipe->calorie] helper function. (Recall that calories appear last in the characteristics, which is why we use @racket[last] to retrieve them.) Obviously, these two answers could be combined with simple refactoring.
@chunk[<day15-q2>
(define (q2 input-str)
(define ingredient-hash (str->ingredient-hash input-str))
(define ingredients (hash-keys ingredient-hash))
(define how-many-characteristics (length (car (hash-values ingredient-hash))))
(define tsps 100)
(define (recipe->calories recipe)
(for/sum ([tsp-quantity (in-list recipe)]
[ingredient (in-list ingredients)])
(* tsp-quantity (last (hash-ref ingredient-hash ingredient)))))
(define scores
(for/list ([recipe (in-list (make-recipes (length ingredients) tsps))]
#:when (= 500 (recipe->calories recipe)))
(for/product ([char-idx (in-range (sub1 how-many-characteristics))])
(max 0 (for/sum ([tsp-quantity (in-list recipe)]
[ingredient (in-list ingredients)])
(* tsp-quantity
(list-ref (hash-ref ingredient-hash ingredient) char-idx)))))))
(apply max scores))
]
@chunk[<day15-refactored>
]
@section{Testing Day 15}
@chunk[<day15-test>
(module+ test
(define input-str (file->string "day15-input.txt"))
(check-equal? (q1 input-str) 18965440)
(check-equal? (q2 input-str) 15862900))]

@ -0,0 +1,10 @@
children: 3
cats: 7
samoyeds: 2
pomeranians: 3
akitas: 0
vizslas: 0
goldfish: 5
trees: 3
cars: 2
perfumes: 1

@ -0,0 +1,500 @@
Sue 1: goldfish: 6, trees: 9, akitas: 0
Sue 2: goldfish: 7, trees: 1, akitas: 0
Sue 3: cars: 10, akitas: 6, perfumes: 7
Sue 4: perfumes: 2, vizslas: 0, cars: 6
Sue 5: goldfish: 1, trees: 3, perfumes: 10
Sue 6: children: 9, vizslas: 7, cars: 9
Sue 7: cars: 6, vizslas: 5, cats: 3
Sue 8: akitas: 10, vizslas: 9, children: 3
Sue 9: vizslas: 8, cats: 2, trees: 1
Sue 10: perfumes: 10, trees: 6, cars: 4
Sue 11: cars: 9, children: 1, cats: 1
Sue 12: pomeranians: 4, akitas: 6, goldfish: 8
Sue 13: cats: 10, children: 5, trees: 9
Sue 14: perfumes: 8, vizslas: 3, samoyeds: 1
Sue 15: vizslas: 2, perfumes: 8, trees: 3
Sue 16: pomeranians: 10, trees: 9, samoyeds: 4
Sue 17: akitas: 7, vizslas: 0, goldfish: 6
Sue 18: trees: 5, vizslas: 9, cars: 0
Sue 19: akitas: 3, goldfish: 9, trees: 10
Sue 20: perfumes: 7, samoyeds: 3, vizslas: 10
Sue 21: perfumes: 7, pomeranians: 10, akitas: 8
Sue 22: vizslas: 6, trees: 8, akitas: 10
Sue 23: goldfish: 0, trees: 4, children: 9
Sue 24: goldfish: 7, pomeranians: 9, akitas: 4
Sue 25: cars: 7, trees: 4, pomeranians: 4
Sue 26: trees: 9, akitas: 9, pomeranians: 7
Sue 27: samoyeds: 0, perfumes: 9, goldfish: 10
Sue 28: cars: 5, trees: 7, vizslas: 1
Sue 29: perfumes: 9, trees: 1, children: 6
Sue 30: goldfish: 10, trees: 0, cars: 4
Sue 31: akitas: 2, perfumes: 5, goldfish: 5
Sue 32: goldfish: 0, akitas: 5, trees: 0
Sue 33: vizslas: 2, akitas: 2, samoyeds: 3
Sue 34: goldfish: 8, perfumes: 5, cars: 3
Sue 35: akitas: 1, cats: 4, trees: 9
Sue 36: cars: 4, vizslas: 4, goldfish: 7
Sue 37: akitas: 5, perfumes: 7, trees: 3
Sue 38: goldfish: 10, trees: 2, vizslas: 9
Sue 39: goldfish: 4, pomeranians: 5, vizslas: 5
Sue 40: perfumes: 5, samoyeds: 4, akitas: 6
Sue 41: goldfish: 9, cars: 4, perfumes: 5
Sue 42: trees: 6, pomeranians: 9, goldfish: 8
Sue 43: perfumes: 7, pomeranians: 1, akitas: 2
Sue 44: vizslas: 9, cars: 5, cats: 0
Sue 45: akitas: 1, goldfish: 6, trees: 0
Sue 46: akitas: 5, vizslas: 8, trees: 2
Sue 47: trees: 9, akitas: 2, vizslas: 9
Sue 48: goldfish: 10, trees: 5, akitas: 2
Sue 49: cars: 7, vizslas: 2, perfumes: 6
Sue 50: akitas: 5, goldfish: 6, perfumes: 0
Sue 51: cars: 9, cats: 7, trees: 5
Sue 52: akitas: 7, goldfish: 10, cars: 0
Sue 53: cars: 10, cats: 4, perfumes: 2
Sue 54: goldfish: 2, pomeranians: 5, perfumes: 10
Sue 55: vizslas: 5, akitas: 4, cars: 8
Sue 56: goldfish: 9, vizslas: 4, akitas: 5
Sue 57: perfumes: 8, samoyeds: 7, cars: 9
Sue 58: cars: 5, akitas: 7, perfumes: 8
Sue 59: samoyeds: 8, cars: 10, vizslas: 10
Sue 60: akitas: 6, samoyeds: 0, goldfish: 3
Sue 61: trees: 8, pomeranians: 0, akitas: 2
Sue 62: trees: 1, perfumes: 3, vizslas: 4
Sue 63: vizslas: 6, samoyeds: 9, goldfish: 8
Sue 64: goldfish: 7, trees: 6, vizslas: 3
Sue 65: cars: 1, vizslas: 0, akitas: 6
Sue 66: cats: 6, pomeranians: 4, cars: 9
Sue 67: trees: 10, pomeranians: 7, samoyeds: 3
Sue 68: pomeranians: 5, goldfish: 9, akitas: 1
Sue 69: akitas: 1, vizslas: 0, trees: 9
Sue 70: cats: 4, goldfish: 4, vizslas: 10
Sue 71: vizslas: 7, perfumes: 7, trees: 8
Sue 72: children: 2, vizslas: 9, cats: 3
Sue 73: cars: 8, pomeranians: 0, perfumes: 6
Sue 74: akitas: 1, pomeranians: 8, vizslas: 10
Sue 75: vizslas: 5, perfumes: 5, cars: 7
Sue 76: cars: 3, vizslas: 3, goldfish: 0
Sue 77: akitas: 9, samoyeds: 1, pomeranians: 3
Sue 78: trees: 0, vizslas: 0, akitas: 6
Sue 79: pomeranians: 9, cars: 1, perfumes: 0
Sue 80: perfumes: 10, trees: 1, cats: 0
Sue 81: goldfish: 5, akitas: 9, trees: 0
Sue 82: vizslas: 1, akitas: 6, children: 4
Sue 83: samoyeds: 7, perfumes: 8, pomeranians: 4
Sue 84: perfumes: 3, children: 3, cats: 7
Sue 85: goldfish: 9, trees: 3, cars: 9
Sue 86: cars: 0, perfumes: 9, vizslas: 0
Sue 87: children: 3, trees: 4, akitas: 3
Sue 88: trees: 1, samoyeds: 1, goldfish: 0
Sue 89: akitas: 8, cars: 3, vizslas: 9
Sue 90: pomeranians: 9, trees: 9, goldfish: 8
Sue 91: goldfish: 7, trees: 10, children: 0
Sue 92: cats: 9, cars: 7, perfumes: 7
Sue 93: vizslas: 2, goldfish: 7, cats: 9
Sue 94: akitas: 5, cars: 8, vizslas: 4
Sue 95: goldfish: 7, vizslas: 1, perfumes: 2
Sue 96: goldfish: 5, trees: 6, perfumes: 10
Sue 97: trees: 0, perfumes: 7, cars: 0
Sue 98: cars: 2, perfumes: 6, trees: 8
Sue 99: trees: 10, children: 7, cats: 9
Sue 100: samoyeds: 5, goldfish: 6, vizslas: 6
Sue 101: cars: 10, perfumes: 9, vizslas: 3
Sue 102: pomeranians: 6, trees: 1, samoyeds: 4
Sue 103: cars: 2, perfumes: 1, goldfish: 5
Sue 104: goldfish: 2, cars: 8, pomeranians: 2
Sue 105: goldfish: 6, vizslas: 0, trees: 10
Sue 106: trees: 10, akitas: 10, pomeranians: 0
Sue 107: vizslas: 2, pomeranians: 10, trees: 3
Sue 108: children: 3, vizslas: 8, akitas: 7
Sue 109: perfumes: 2, akitas: 2, samoyeds: 3
Sue 110: goldfish: 7, trees: 1, perfumes: 1
Sue 111: akitas: 2, cars: 9, perfumes: 2
Sue 112: children: 10, cars: 0, akitas: 3
Sue 113: akitas: 9, vizslas: 4, children: 3
Sue 114: pomeranians: 3, trees: 2, goldfish: 5
Sue 115: perfumes: 8, cars: 6, trees: 0
Sue 116: samoyeds: 6, children: 3, pomeranians: 1
Sue 117: goldfish: 1, trees: 2, akitas: 1
Sue 118: goldfish: 10, akitas: 10, samoyeds: 0
Sue 119: vizslas: 10, perfumes: 6, cars: 0
Sue 120: cars: 2, perfumes: 9, goldfish: 5
Sue 121: vizslas: 2, trees: 2, cars: 6
Sue 122: vizslas: 3, trees: 0, akitas: 2
Sue 123: akitas: 5, samoyeds: 7, goldfish: 1
Sue 124: goldfish: 8, samoyeds: 7, trees: 8
Sue 125: trees: 3, goldfish: 8, perfumes: 5
Sue 126: cats: 3, vizslas: 9, goldfish: 0
Sue 127: pomeranians: 9, goldfish: 3, perfumes: 6
Sue 128: vizslas: 4, cars: 8, goldfish: 5
Sue 129: vizslas: 8, children: 5, perfumes: 8
Sue 130: cars: 7, children: 7, cats: 3
Sue 131: perfumes: 1, akitas: 8, vizslas: 9
Sue 132: perfumes: 7, samoyeds: 10, pomeranians: 6
Sue 133: cars: 5, perfumes: 3, goldfish: 7
Sue 134: perfumes: 9, akitas: 2, cats: 3
Sue 135: perfumes: 1, trees: 9, vizslas: 9
Sue 136: akitas: 7, cars: 3, perfumes: 7
Sue 137: vizslas: 9, goldfish: 8, cars: 5
Sue 138: trees: 0, samoyeds: 1, cars: 3
Sue 139: cars: 0, perfumes: 6, trees: 0
Sue 140: pomeranians: 4, cars: 1, perfumes: 7
Sue 141: vizslas: 10, akitas: 8, cats: 3
Sue 142: trees: 1, cats: 6, vizslas: 5
Sue 143: pomeranians: 9, cars: 7, perfumes: 9
Sue 144: cars: 0, perfumes: 2, pomeranians: 1
Sue 145: trees: 1, goldfish: 9, perfumes: 8
Sue 146: cars: 8, children: 5, vizslas: 2
Sue 147: perfumes: 2, goldfish: 5, cars: 0
Sue 148: akitas: 2, perfumes: 7, pomeranians: 6
Sue 149: goldfish: 8, cars: 0, trees: 1
Sue 150: akitas: 6, perfumes: 5, trees: 0
Sue 151: vizslas: 6, samoyeds: 8, akitas: 10
Sue 152: trees: 7, akitas: 7, perfumes: 6
Sue 153: goldfish: 9, cats: 9, cars: 3
Sue 154: vizslas: 10, trees: 0, cars: 9
Sue 155: perfumes: 3, children: 2, goldfish: 1
Sue 156: goldfish: 7, perfumes: 5, akitas: 6
Sue 157: cats: 10, trees: 1, goldfish: 0
Sue 158: cats: 7, children: 7, vizslas: 6
Sue 159: perfumes: 9, akitas: 0, cars: 0
Sue 160: akitas: 3, goldfish: 10, pomeranians: 2
Sue 161: goldfish: 10, cars: 6, perfumes: 3
Sue 162: trees: 0, cars: 9, goldfish: 1
Sue 163: cars: 8, perfumes: 9, vizslas: 5
Sue 164: goldfish: 1, trees: 10, children: 6
Sue 165: goldfish: 0, vizslas: 6, cars: 0
Sue 166: akitas: 5, vizslas: 1, cars: 5
Sue 167: vizslas: 1, samoyeds: 1, children: 4
Sue 168: samoyeds: 7, vizslas: 7, akitas: 3
Sue 169: goldfish: 3, cats: 9, trees: 2
Sue 170: cars: 5, perfumes: 9, vizslas: 5
Sue 171: goldfish: 7, cars: 6, perfumes: 10
Sue 172: cats: 6, akitas: 1, children: 6
Sue 173: cats: 4, goldfish: 1, children: 3
Sue 174: cars: 2, pomeranians: 2, vizslas: 7
Sue 175: trees: 0, children: 4, goldfish: 7
Sue 176: children: 8, cars: 5, cats: 9
Sue 177: pomeranians: 4, vizslas: 7, trees: 3
Sue 178: vizslas: 6, perfumes: 10, akitas: 6
Sue 179: cars: 4, akitas: 4, trees: 4
Sue 180: akitas: 8, goldfish: 6, trees: 9
Sue 181: perfumes: 3, vizslas: 10, cars: 3
Sue 182: vizslas: 3, samoyeds: 3, goldfish: 7
Sue 183: goldfish: 10, perfumes: 2, cats: 1
Sue 184: goldfish: 5, trees: 1, perfumes: 1
Sue 185: vizslas: 10, trees: 9, perfumes: 2
Sue 186: goldfish: 6, perfumes: 9, trees: 1
Sue 187: cars: 0, trees: 9, goldfish: 6
Sue 188: cars: 0, trees: 1, vizslas: 9
Sue 189: akitas: 7, vizslas: 2, trees: 0
Sue 190: pomeranians: 5, perfumes: 8, akitas: 10
Sue 191: vizslas: 5, akitas: 3, cats: 0
Sue 192: children: 1, trees: 1, cars: 2
Sue 193: cars: 3, goldfish: 9, trees: 2
Sue 194: samoyeds: 3, akitas: 4, perfumes: 8
Sue 195: trees: 1, vizslas: 8, akitas: 10
Sue 196: akitas: 6, cars: 5, pomeranians: 0
Sue 197: akitas: 5, vizslas: 5, cats: 1
Sue 198: trees: 4, cars: 6, goldfish: 6
Sue 199: cats: 7, cars: 5, goldfish: 6
Sue 200: vizslas: 4, cats: 0, akitas: 9
Sue 201: pomeranians: 1, perfumes: 4, children: 2
Sue 202: cats: 1, perfumes: 4, vizslas: 3
Sue 203: vizslas: 1, akitas: 9, children: 5
Sue 204: perfumes: 8, cars: 7, trees: 4
Sue 205: perfumes: 7, pomeranians: 5, cats: 9
Sue 206: vizslas: 8, trees: 2, akitas: 2
Sue 207: akitas: 6, vizslas: 2, perfumes: 10
Sue 208: vizslas: 1, children: 7, akitas: 4
Sue 209: perfumes: 4, trees: 2, children: 1
Sue 210: goldfish: 0, vizslas: 2, samoyeds: 10
Sue 211: cars: 8, perfumes: 3, trees: 1
Sue 212: cars: 8, samoyeds: 5, pomeranians: 8
Sue 213: akitas: 2, goldfish: 8, pomeranians: 2
Sue 214: akitas: 6, pomeranians: 2, cars: 0
Sue 215: trees: 10, pomeranians: 4, vizslas: 0
Sue 216: perfumes: 0, cars: 8, trees: 0
Sue 217: samoyeds: 8, akitas: 7, children: 10
Sue 218: perfumes: 1, vizslas: 6, children: 0
Sue 219: children: 1, goldfish: 4, trees: 1
Sue 220: akitas: 10, goldfish: 10, trees: 5
Sue 221: cars: 7, pomeranians: 6, perfumes: 3
Sue 222: vizslas: 6, children: 0, akitas: 5
Sue 223: perfumes: 9, cars: 1, trees: 6
Sue 224: pomeranians: 1, trees: 0, vizslas: 0
Sue 225: goldfish: 8, akitas: 4, perfumes: 10
Sue 226: pomeranians: 7, cats: 7, children: 4
Sue 227: trees: 0, akitas: 2, perfumes: 1
Sue 228: vizslas: 6, cars: 10, perfumes: 9
Sue 229: cars: 0, perfumes: 6, trees: 4
Sue 230: pomeranians: 7, perfumes: 5, trees: 2
Sue 231: goldfish: 9, cars: 6, trees: 7
Sue 232: akitas: 1, vizslas: 5, cars: 3
Sue 233: akitas: 7, samoyeds: 2, vizslas: 5
Sue 234: akitas: 6, cats: 8, pomeranians: 0
Sue 235: pomeranians: 5, akitas: 5, vizslas: 3
Sue 236: goldfish: 5, trees: 6, akitas: 5
Sue 237: goldfish: 9, perfumes: 5, cats: 5
Sue 238: cats: 8, goldfish: 4, perfumes: 0
Sue 239: samoyeds: 8, children: 6, pomeranians: 6
Sue 240: akitas: 4, samoyeds: 10, trees: 8
Sue 241: trees: 2, goldfish: 8, cars: 1
Sue 242: perfumes: 2, cars: 0, akitas: 10
Sue 243: pomeranians: 1, cars: 7, trees: 2
Sue 244: trees: 9, vizslas: 2, akitas: 10
Sue 245: cars: 9, pomeranians: 4, trees: 0
Sue 246: cars: 9, pomeranians: 7, perfumes: 1
Sue 247: trees: 0, goldfish: 1, akitas: 8
Sue 248: vizslas: 1, cats: 4, akitas: 4
Sue 249: cats: 6, children: 4, goldfish: 9
Sue 250: vizslas: 1, cars: 10, samoyeds: 5
Sue 251: cars: 0, goldfish: 1, vizslas: 7
Sue 252: cars: 7, akitas: 9, vizslas: 10
Sue 253: akitas: 7, vizslas: 2, perfumes: 5
Sue 254: vizslas: 10, akitas: 5, samoyeds: 0
Sue 255: pomeranians: 8, goldfish: 0, cats: 6
Sue 256: cars: 10, goldfish: 8, vizslas: 9
Sue 257: goldfish: 3, perfumes: 9, cats: 3
Sue 258: trees: 6, goldfish: 6, cars: 6
Sue 259: trees: 0, goldfish: 2, perfumes: 8
Sue 260: trees: 5, akitas: 0, cars: 0
Sue 261: pomeranians: 9, goldfish: 7, perfumes: 8
Sue 262: perfumes: 8, vizslas: 6, goldfish: 2
Sue 263: vizslas: 6, trees: 5, goldfish: 9
Sue 264: vizslas: 4, perfumes: 7, cars: 9
Sue 265: goldfish: 10, trees: 3, perfumes: 1
Sue 266: trees: 10, akitas: 8, goldfish: 8
Sue 267: goldfish: 4, trees: 0, samoyeds: 9
Sue 268: vizslas: 1, trees: 0, goldfish: 8
Sue 269: cars: 2, perfumes: 10, goldfish: 5
Sue 270: perfumes: 7, cars: 2, vizslas: 1
Sue 271: cars: 6, perfumes: 10, goldfish: 6
Sue 272: samoyeds: 4, goldfish: 2, vizslas: 9
Sue 273: perfumes: 4, goldfish: 4, vizslas: 1
Sue 274: children: 4, cars: 4, perfumes: 3
Sue 275: children: 8, vizslas: 3, trees: 2
Sue 276: vizslas: 5, children: 7, perfumes: 3
Sue 277: perfumes: 3, cats: 4, vizslas: 5
Sue 278: cars: 1, samoyeds: 10, akitas: 2
Sue 279: trees: 9, perfumes: 9, cars: 10
Sue 280: vizslas: 5, trees: 0, perfumes: 6
Sue 281: vizslas: 3, akitas: 10, pomeranians: 7
Sue 282: trees: 1, children: 2, akitas: 8
Sue 283: akitas: 9, goldfish: 6, cats: 5
Sue 284: cars: 9, children: 10, pomeranians: 2
Sue 285: pomeranians: 0, perfumes: 4, cars: 7
Sue 286: perfumes: 0, vizslas: 10, akitas: 10
Sue 287: cats: 2, perfumes: 3, trees: 5
Sue 288: akitas: 9, vizslas: 8, samoyeds: 9
Sue 289: perfumes: 6, children: 2, cars: 7
Sue 290: akitas: 0, children: 5, cars: 5
Sue 291: cars: 4, perfumes: 0, trees: 1
Sue 292: cats: 0, cars: 8, perfumes: 6
Sue 293: akitas: 9, cats: 5, children: 5
Sue 294: akitas: 4, cars: 9, goldfish: 3
Sue 295: cars: 2, akitas: 3, perfumes: 7
Sue 296: perfumes: 4, cars: 7, goldfish: 10
Sue 297: trees: 5, akitas: 8, vizslas: 1
Sue 298: perfumes: 0, goldfish: 6, trees: 9
Sue 299: perfumes: 6, samoyeds: 8, cars: 1
Sue 300: goldfish: 10, perfumes: 4, akitas: 2
Sue 301: cars: 3, trees: 0, goldfish: 8
Sue 302: perfumes: 7, samoyeds: 2, vizslas: 7
Sue 303: children: 10, goldfish: 7, perfumes: 2
Sue 304: samoyeds: 8, vizslas: 2, cars: 1
Sue 305: trees: 1, cats: 0, goldfish: 10
Sue 306: trees: 4, perfumes: 2, cars: 7
Sue 307: cars: 6, vizslas: 2, children: 6
Sue 308: vizslas: 2, cars: 0, akitas: 7
Sue 309: cars: 3, vizslas: 8, perfumes: 6
Sue 310: goldfish: 7, perfumes: 7, vizslas: 3
Sue 311: pomeranians: 10, trees: 2, cars: 0
Sue 312: samoyeds: 2, vizslas: 9, akitas: 1
Sue 313: cars: 4, pomeranians: 7, goldfish: 7
Sue 314: akitas: 2, pomeranians: 9, samoyeds: 10
Sue 315: akitas: 3, vizslas: 2, trees: 0
Sue 316: cars: 0, perfumes: 4, pomeranians: 6
Sue 317: akitas: 10, goldfish: 3, pomeranians: 7
Sue 318: cars: 9, trees: 0, pomeranians: 9
Sue 319: akitas: 3, vizslas: 7, children: 10
Sue 320: vizslas: 0, akitas: 8, pomeranians: 4
Sue 321: cars: 10, akitas: 9, vizslas: 3
Sue 322: perfumes: 0, akitas: 8, vizslas: 6
Sue 323: vizslas: 10, perfumes: 5, cars: 3
Sue 324: akitas: 0, goldfish: 6, vizslas: 7
Sue 325: perfumes: 9, vizslas: 5, pomeranians: 2
Sue 326: vizslas: 6, goldfish: 10, pomeranians: 8
Sue 327: vizslas: 10, cars: 1, akitas: 7
Sue 328: trees: 1, perfumes: 10, cars: 10
Sue 329: pomeranians: 5, samoyeds: 3, cars: 10
Sue 330: akitas: 6, cars: 1, pomeranians: 4
Sue 331: cars: 5, children: 2, trees: 0
Sue 332: vizslas: 6, pomeranians: 1, perfumes: 0
Sue 333: akitas: 7, trees: 1, cats: 9
Sue 334: vizslas: 6, goldfish: 9, akitas: 7
Sue 335: akitas: 3, samoyeds: 3, cars: 3
Sue 336: samoyeds: 10, perfumes: 9, trees: 6
Sue 337: vizslas: 2, cars: 9, akitas: 0
Sue 338: akitas: 6, perfumes: 9, vizslas: 3
Sue 339: cars: 3, samoyeds: 8, trees: 2
Sue 340: cats: 7, perfumes: 8, cars: 9
Sue 341: goldfish: 9, perfumes: 5, cars: 10
Sue 342: trees: 0, akitas: 3, perfumes: 5
Sue 343: perfumes: 2, children: 0, cars: 6
Sue 344: goldfish: 8, trees: 8, perfumes: 0
Sue 345: perfumes: 6, cars: 6, goldfish: 5
Sue 346: vizslas: 8, trees: 1, cars: 6
Sue 347: cars: 0, cats: 3, perfumes: 7
Sue 348: children: 7, perfumes: 10, cars: 7
Sue 349: pomeranians: 8, akitas: 5, children: 2
Sue 350: perfumes: 9, pomeranians: 4, goldfish: 3
Sue 351: perfumes: 8, pomeranians: 7, trees: 4
Sue 352: samoyeds: 1, goldfish: 9, akitas: 8
Sue 353: akitas: 6, goldfish: 10, vizslas: 8
Sue 354: akitas: 7, cars: 2, goldfish: 6
Sue 355: cars: 3, goldfish: 6, akitas: 5
Sue 356: akitas: 2, goldfish: 9, pomeranians: 1
Sue 357: goldfish: 10, cars: 6, pomeranians: 9
Sue 358: trees: 0, children: 2, goldfish: 6
Sue 359: samoyeds: 3, cars: 2, akitas: 4
Sue 360: trees: 1, goldfish: 8, cars: 5
Sue 361: akitas: 5, vizslas: 7, perfumes: 1
Sue 362: cats: 5, vizslas: 9, children: 4
Sue 363: goldfish: 9, perfumes: 3, vizslas: 9
Sue 364: children: 7, samoyeds: 2, pomeranians: 10
Sue 365: perfumes: 9, akitas: 10, pomeranians: 4
Sue 366: cars: 10, trees: 3, cats: 4
Sue 367: vizslas: 6, akitas: 10, perfumes: 5
Sue 368: akitas: 9, vizslas: 9, children: 4
Sue 369: goldfish: 8, trees: 2, perfumes: 5
Sue 370: trees: 0, children: 4, cars: 8
Sue 371: cats: 6, perfumes: 0, vizslas: 2
Sue 372: akitas: 7, cars: 5, perfumes: 3
Sue 373: cars: 0, perfumes: 4, pomeranians: 10
Sue 374: akitas: 5, perfumes: 5, vizslas: 2
Sue 375: goldfish: 7, trees: 10, pomeranians: 7
Sue 376: cars: 8, trees: 1, pomeranians: 8
Sue 377: cars: 0, akitas: 9, vizslas: 1
Sue 378: akitas: 5, perfumes: 3, vizslas: 7
Sue 379: trees: 2, goldfish: 8, pomeranians: 8
Sue 380: akitas: 5, cars: 9, perfumes: 9
Sue 381: cars: 2, perfumes: 6, trees: 3
Sue 382: perfumes: 6, vizslas: 2, goldfish: 9
Sue 383: akitas: 8, vizslas: 7, cats: 1
Sue 384: akitas: 9, trees: 10, vizslas: 7
Sue 385: cars: 0, perfumes: 7, vizslas: 2
Sue 386: vizslas: 10, akitas: 4, perfumes: 9
Sue 387: perfumes: 6, pomeranians: 5, samoyeds: 8
Sue 388: vizslas: 10, trees: 9, goldfish: 9
Sue 389: goldfish: 8, akitas: 4, perfumes: 10
Sue 390: goldfish: 6, trees: 8, akitas: 1
Sue 391: vizslas: 4, akitas: 10, goldfish: 7
Sue 392: akitas: 1, vizslas: 6, samoyeds: 5
Sue 393: trees: 6, cars: 3, akitas: 5
Sue 394: goldfish: 9, trees: 3, cars: 5
Sue 395: akitas: 6, samoyeds: 4, goldfish: 4
Sue 396: akitas: 2, trees: 1, cats: 5
Sue 397: cars: 0, children: 9, trees: 10
Sue 398: pomeranians: 3, samoyeds: 9, goldfish: 10
Sue 399: cars: 7, akitas: 4, goldfish: 8
Sue 400: cars: 4, akitas: 5, vizslas: 4
Sue 401: pomeranians: 5, akitas: 8, vizslas: 5
Sue 402: cats: 7, cars: 6, goldfish: 6
Sue 403: samoyeds: 8, perfumes: 4, cars: 5
Sue 404: akitas: 10, goldfish: 4, trees: 2
Sue 405: trees: 8, perfumes: 1, cars: 2
Sue 406: trees: 0, perfumes: 9, pomeranians: 10
Sue 407: perfumes: 4, trees: 7, goldfish: 3
Sue 408: akitas: 1, perfumes: 3, cars: 5
Sue 409: trees: 6, samoyeds: 3, cars: 9
Sue 410: vizslas: 3, goldfish: 5, akitas: 7
Sue 411: goldfish: 10, trees: 1, vizslas: 9
Sue 412: cars: 0, akitas: 6, trees: 6
Sue 413: goldfish: 7, trees: 0, cars: 3
Sue 414: pomeranians: 10, samoyeds: 3, cars: 10
Sue 415: perfumes: 6, trees: 9, cars: 4
Sue 416: trees: 2, cars: 4, goldfish: 8
Sue 417: goldfish: 2, cars: 9, cats: 5
Sue 418: vizslas: 1, cars: 9, akitas: 0
Sue 419: perfumes: 6, cats: 3, children: 9
Sue 420: cats: 5, goldfish: 7, akitas: 9
Sue 421: trees: 1, samoyeds: 6, pomeranians: 1
Sue 422: trees: 10, goldfish: 6, children: 7
Sue 423: cars: 8, goldfish: 7, vizslas: 3
Sue 424: samoyeds: 9, akitas: 7, trees: 5
Sue 425: akitas: 5, children: 4, perfumes: 9
Sue 426: goldfish: 1, children: 9, cats: 2
Sue 427: vizslas: 9, akitas: 7, goldfish: 9
Sue 428: pomeranians: 7, akitas: 5, vizslas: 1
Sue 429: vizslas: 7, goldfish: 7, cars: 9
Sue 430: trees: 7, perfumes: 0, pomeranians: 5
Sue 431: children: 9, perfumes: 5, vizslas: 7
Sue 432: trees: 6, samoyeds: 7, cats: 1
Sue 433: goldfish: 5, trees: 5, children: 6
Sue 434: goldfish: 9, akitas: 7, cars: 3
Sue 435: samoyeds: 10, perfumes: 2, cars: 0
Sue 436: akitas: 5, pomeranians: 4, perfumes: 7
Sue 437: vizslas: 5, cats: 6, perfumes: 5
Sue 438: trees: 2, goldfish: 6, vizslas: 7
Sue 439: samoyeds: 8, pomeranians: 10, goldfish: 1
Sue 440: akitas: 6, children: 9, perfumes: 4
Sue 441: cars: 2, goldfish: 9, children: 0
Sue 442: goldfish: 7, cars: 2, vizslas: 8
Sue 443: goldfish: 6, samoyeds: 3, perfumes: 2
Sue 444: trees: 2, goldfish: 7, cars: 8
Sue 445: trees: 2, pomeranians: 0, children: 0
Sue 446: perfumes: 4, akitas: 4, goldfish: 6
Sue 447: vizslas: 7, akitas: 9, cars: 3
Sue 448: goldfish: 6, trees: 9, cars: 0
Sue 449: samoyeds: 7, perfumes: 4, vizslas: 10
Sue 450: akitas: 7, cars: 10, goldfish: 7
Sue 451: goldfish: 4, children: 7, pomeranians: 4
Sue 452: cats: 4, vizslas: 6, trees: 7
Sue 453: cars: 1, trees: 10, goldfish: 9
Sue 454: trees: 2, goldfish: 3, vizslas: 10
Sue 455: pomeranians: 9, vizslas: 3, akitas: 2
Sue 456: vizslas: 10, akitas: 2, goldfish: 1
Sue 457: trees: 5, cats: 5, children: 8
Sue 458: cars: 6, goldfish: 3, akitas: 9
Sue 459: goldfish: 7, akitas: 2, cats: 7
Sue 460: akitas: 1, cars: 5, children: 8
Sue 461: cars: 8, perfumes: 0, goldfish: 6
Sue 462: pomeranians: 6, cats: 2, perfumes: 6
Sue 463: vizslas: 7, perfumes: 3, goldfish: 3
Sue 464: akitas: 10, goldfish: 10, trees: 1
Sue 465: vizslas: 0, akitas: 2, trees: 2
Sue 466: perfumes: 6, akitas: 8, cars: 2
Sue 467: goldfish: 1, cars: 10, perfumes: 3
Sue 468: goldfish: 4, trees: 2, cars: 9
Sue 469: perfumes: 6, pomeranians: 0, vizslas: 10
Sue 470: samoyeds: 8, children: 0, akitas: 7
Sue 471: children: 3, goldfish: 9, cats: 9
Sue 472: samoyeds: 0, goldfish: 0, trees: 0
Sue 473: trees: 3, goldfish: 4, vizslas: 1
Sue 474: perfumes: 10, cars: 3, trees: 7
Sue 475: akitas: 5, vizslas: 4, goldfish: 5
Sue 476: children: 2, akitas: 7, vizslas: 3
Sue 477: vizslas: 6, pomeranians: 9, trees: 6
Sue 478: vizslas: 7, pomeranians: 6, akitas: 7
Sue 479: trees: 2, perfumes: 2, children: 2
Sue 480: cars: 8, cats: 5, vizslas: 0
Sue 481: trees: 5, goldfish: 0, akitas: 3
Sue 482: cars: 8, perfumes: 6, goldfish: 10
Sue 483: goldfish: 0, cars: 3, perfumes: 10
Sue 484: pomeranians: 1, samoyeds: 1, perfumes: 3
Sue 485: trees: 0, akitas: 2, vizslas: 4
Sue 486: cars: 3, vizslas: 8, goldfish: 1
Sue 487: pomeranians: 9, vizslas: 2, children: 10
Sue 488: akitas: 6, vizslas: 10, perfumes: 9
Sue 489: goldfish: 6, vizslas: 4, cars: 2
Sue 490: vizslas: 10, cats: 8, samoyeds: 1
Sue 491: cats: 9, cars: 1, perfumes: 10
Sue 492: goldfish: 6, cars: 9, pomeranians: 9
Sue 493: children: 10, goldfish: 10, vizslas: 0
Sue 494: pomeranians: 5, cars: 0, vizslas: 0
Sue 495: vizslas: 7, perfumes: 6, samoyeds: 3
Sue 496: trees: 1, cats: 4, cars: 10
Sue 497: cats: 1, perfumes: 0, cars: 7
Sue 498: perfumes: 7, vizslas: 6, cats: 9
Sue 499: vizslas: 8, perfumes: 1, akitas: 3
Sue 500: perfumes: 4, cars: 9, trees: 4

@ -0,0 +1,115 @@
#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@aoc-title[16]
@defmodule[aoc-racket/day16]
@link["http://adventofcode.com/day/16"]{The puzzle}. Our @link-rp["day16-input.txt"]{input} is a list of 500 people named Sue, along with three attribute pairs for each. We're also provided a set of attribute pairs that identifies one of the Sues:
@tt{children: 3
@(linebreak)
cats: 7
@(linebreak)
samoyeds: 2
@(linebreak)
pomeranians: 3
@(linebreak)
akitas: 0
@(linebreak)
vizslas: 0
@(linebreak)
goldfish: 5
@(linebreak)
trees: 3
@(linebreak)
cars: 2
@(linebreak)
perfumes: 1}
@chunk[<day16>
<day16-setup>
<day16-q1>
<day16-q2>
<day16-test>]
@section{Which Sue matches the attribute input?}
Our input has 10 attribute pairs, but each of our Sues only has three attribute pairs. The other values for each Sue are unknown. Thus, for each Sue in our list, we need to check if the three known attribute pairs are among in our 10 identifying pairs. Assumedly, this will be true for only one Sue, and that will be the answer.
We might be tempted to break down the attribute pairs into hash tables. But we don't even need to work that hard  we can just treat the attribute pairs as a list of strings, and match whole strings like @racket{children: 3}. And our Sues can be a list too, each holding a list of strings. Cheap & cheerful.
@chunk[<day16-setup>
(require racket rackunit)
(provide (all-defined-out))
(define (parse-sues str)
(for/list ([ln (in-list (string-split str "\n"))])
(define attr-str (second (regexp-match #px"^.*?: (.*)$" ln)))
(string-split attr-str ", ")))
(define master-attrs (file->lines "day16-input-master-attrs.txt"))
]
@chunk[<day16-q1>
(define (q1 input-str)
(define sues (parse-sues input-str))
(for/or ([(sue-attrs sue-number) (in-indexed sues)])
(for/and ([sue-attr (in-list sue-attrs)])
(and (member sue-attr master-attrs) (add1 sue-number)))))]
@section{Which Sue matches the attribute input, with the ``retroencabulator'' rules?}
Same question as before, with new rules for matching the master attributes:
@itemlist[
@item{The target Sue has more @tt{cats} and @tt{trees} than indicated.}
@item{The target Sue has fewer @tt{pomeranians} and @tt{goldfish} than indicated. (How many pomeranians does anyone really need?)}
]
Now that we're asked to compare attribute values in a deeper way, our avoidance of a hash table in question 1 looks like a false economy.
So let's compound our laziness with more laziness. Rather than upgrade to a hash table now, let's convert our strings to @italic{datums} (as saw in @secref{Day_7}). Because if we put a string like @racket{children: 3} inside parentheses like so @racket{(children: 3)} and then convert it to a datum (with @racket[read]) we'll end up with a list with a key and a value, e.g. @racket['(children: 3)]. In other words, just what we needed. (I know the plural of @italic{datum} is @italic{data}, but @italic{datums} better connotes ``more than one datum, in the Racket sense.'')
Plus, it's always fun to find a use for @racket[case] and the frequently overlooked @racket[assoc].
@chunk[<day16-q2>
(define (q2 input-str)
(define (attrs->datums attrs)
(map (compose1 read open-input-string
(λ(attr) (format "(~a)" attr))) attrs))
(define sues (for/list ([sue-attrs (parse-sues input-str)])
(attrs->datums sue-attrs)))
(define master-datums (attrs->datums master-attrs))
(for/or ([(sue-datums sue-number) (in-indexed sues)])
(for/and ([sue-datum (in-list sue-datums)])
(and
(let* ([sue-key (first sue-datum)]
[sue-value (second sue-datum)]
[master-value (second (assoc sue-key master-datums))]
[cmp (case sue-key
[(cats: trees:) >]
[(pomeranians: goldfish:) <]
[else =])])
(cmp sue-value master-value))
(add1 sue-number)))) )
]
@section{Testing Day 16}
@chunk[<day16-test>
(module+ test
(define input-str (file->string "day16-input.txt"))
(check-equal? (q1 input-str) 103)
(check-equal? (q2 input-str) 405))]

@ -0,0 +1,20 @@
43
3
4
10
21
44
4
6
47
41
34
17
17
44
36
31
46
9
27
38

@ -0,0 +1,70 @@
#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@aoc-title[17]
@defmodule[aoc-racket/day17]
@link["http://adventofcode.com/day/17"]{The puzzle}. Our @link-rp["day17-input.txt"]{input} is a list of containers that hold the designated number of liters.
@chunk[<day17>
<day17-setup>
<day17-q1>
<day17-q2>
<day17-test>]
@section{How many combinations of containers fit exactly 150 liters?}
This is a lot like the second part of @secref{Day_15}, where we had to find cookie recipes that totaled 500 calories. This time, rather than recipes, we need to generate combinations of the containers that add up to exactly 150 liters (though we don't have to use all the containers, and multiple containers of the same size are deemed to create unique arrangements).
We do this by creating the @italic{power set} of the containers  that is, a list of all possible subsets  and counting how many meet our criterion. As with the recipe problem, our @racket[powerset] function is a simple recursive operation.
@chunk[<day17-setup>
(require racket rackunit)
(provide (all-defined-out))
(define (powerset xs)
(if (empty? xs)
(list empty)
(append-map
(λ(s) (list (cons (car xs) s) s))
(powerset (cdr xs)))))
]
@chunk[<day17-q1>
(define (q1 input-str)
(define containers
(map string->number (string-split input-str)))
(length (filter (λ(s) (= 150 (apply + s)))
(powerset containers))))]
@section{How many combinations have the minimum number of containers?}
Same as above, except we find the minimum length among the winners, and then count how many other winners have that length.
@chunk[<day17-q2>
(define (q2 input-str)
(define containers
(map string->number (string-split input-str)))
(let* ([winners (filter (λ(s) (= 150 (apply + s)))
(powerset containers))]
[shortest (apply min (map length winners))])
(length (filter (λ(w) (= shortest (length w))) winners))))
]
@section{Testing Day 17}
@chunk[<day17-test>
(module+ test
(define input-str (file->string "day17-input.txt"))
(check-equal? (q1 input-str) 1638)
(check-equal? (q2 input-str) 17))]

@ -0,0 +1,100 @@
###.##..##.#..#.##...#..#.####..#.##.##.##..###...#....#...###..#..###..###.#.#.#..#.##..#...##.#..#
.#...##.#####..##.......#..####.###.##.#..###.###.....#.#.####.##.###..##...###....#.##.....#.#.#.##
.....#.#.....#..###..####..#.....##.#..###.####.#.######..##......#####.#.##.#########.###..#.##.#.#
...###......#.#..###..#.#.....#.##..#.##..###...#.##.#..#..#.##.#..##......##.##.##.######...#....##
.###.....#...#.#...####.#.###..#..####.#..#.##..####...##.#...#..###...###...####..##....####.##..#.
..#....#...#.......#..###.###....#.##..#.....###.#.##.#....#.#....##.##..#.##.#..###.###.##.##..##.#
##..#####.#.#....#.#...#.#.####..#....#..#....#.#..#.#####...#..##.#.....#.##..##.####......#.#.##..
.#..##..#.#.###..##..##...#....##...#..#.#..##.##..###.####.....#.####.#.....##.#.##...#..####..#...
#.#####.......#####...#...####.#.#.#....#.###.#.##.#####..#.###.#..##.##.#.##....#.##..#....####.#.#
#.##...#####....##.#.#.....##......##.##...#.##.##...##...###.###.##.#.####.####.##..#.##.#.#.####..
#.##.##....###.###.#..#..##.##.#..#.#..##..#.#...#.##........###..#...##.#.#.##.......##.....#...###
###..#.#..##.##.#.#.#...#..#...##.##.#.########.......#.#...#....########..#.#.###..#.#..#.##..#####
####.#.#...#.##.##..#.#...#....#..###..#.#.#.####.#.##.##.#..##..##..#..#####.####.##..########..##.
.#.#...#..##.#..#..###.#..####.......##.#.#.#.##.#####..#..##...#.##...#..#....#..#..###..####.#....
..#.#...#....##...#####..#..#...###.###.....#.###.#....#.#..##...#.##.##.####.#.#.#..#.##.#....#.#..
#....###.####.##..#.#.###..###.##.##..#.#...###..#.##.#####.##.#######..#.#...##.#..........####.###
#.#####.#......#.#......#.....##...##.#.#########.#......##..##..##.#..##.##..#....##...###...#.#...
#..#..##..###.#.#.#.#.....###.#.####.##.##....#.#..##....#.#..#.####..###.##...#######.#####.##.#.#.
..###.#........##.#...###..#.##..#.#....##.#......#..#.##..#.#..#.#..#.####.#####..###.##..#.##.#...
##.###....#..##...#..#.#......##..#...#..#.####..#.##...##.####.#...#..###...#.#.#....###.##..#.#...
..##.##.#.##..##.#..#.###...##..##..#....##..##...####.#..####.###...#.....#..#.##..##..###..#.#...#
#.#....#.....#...##.#...####..#..##..##.####..##..##...####...#....##.#.#######..##.#......######.#.
#.#...###.######.######..##..##....#.#......#......#.#.##.#.##.#.#.#...#...#....#.#.#.#..#.##..#...#
####.###.#.#.##..#.##.#...#.##...#.##.##...#.....#.#..#.####.##..######.#..#.#..##....#.#.#..#.#.#.#
..##......#.#...#.##.##..##..##..#..##..#########.#..###..###.##...#..##.#..#.#.#.######..#....#.#..
..##.##.#...###.#...##..######.##.#..####..#..#.#.##.####.##.##.#...##....#...###.##.####..#....#.#.
####...###..#.#.##.#.#....###..##.#.#..########..#...#.#...#.##....##.##...#.....#.#.....#.....#....
.#.###############....#.##..###..#.####.#.##.##..#..#.#...###...##..##.##.#.....##...###.###.....#..
.###..#..##.##..####.#.###.##.##..#..##....#.#......#......##.#...#.#...#..##.#.#...#...#.##..#.##..
###.#.#.########.#.#..####.#..##.#.##.##.###.##..######...#..##.##.#..#.#...#.##..#####.....#.#.#..#
.##.##..#.#...#####.#.#.###...##...####...#......#...#..####..#.##..........#..#.#..###....######.##
..#####...#.#.#.#..#.##..#...#.#..#.##...##..##.##.#.##.#..#.#...#.......##.#...###.....#...#.#.#.##
##.##.#..######.##...#.....#.###.#..##.#.#.#..####.#....##.#....####...##....#.#.##.#..###.##.##..##
.###.##.#..#.###.####..#.##..####.#.#.##..###.#######.###.###...####........##....###.#...#.#.####.#
........#..#.#..##..########..........#.##.#..##.#...#.....####....##..#..#.#####.###...#...#.##.###
.....#..##.####...##.#####..######.##.#.###.####.##.##.#..##.##.######.##......#..#.####..##....#.##
##...####....#.##.##.###....#.#...#.####..##.#.##.#.#...####.#.#.#.#...##.###...##...###...######.##
.#....#.#.####...#.##.....##...###.#.#.##...##.#####....#.######.#.#....##..##...##....##.#.##.#.#.#
.###..###.#.......#.#######..#.#.#.######....#.#####.#.....#.#########...#....##...##.####.#..#.....
##.#..##..##.....#..##...#..##.##.#..#.#####.##.##.#.##.##...##.######.####..#.##..#####.##...##..#.
#.###...##.#.#.#.##....#.#.##.##..#....#...#.#.........#..#..####..####.####..#.##.##.#....####..##.
.#..######..#####.####.##.#.....#.#.#####..##..###.#.#.#..#.#...#.#######..##....##.##...#######..#.
#...#....#.#.##..#####..#########..#.....#...##.#.#.###...#####..##...##...####.......#######.#..###
.#......#...##.###..#....#...#.#.....#.#...##.#.#..#..###.##.###.#.##..##...#.##......#.###..#.#..##
.#....####...###..#.....##..#...#.#.###.#.#.##...#.##.##.#.#.#..####..###.#.#.#.##.#.#...#..#...####
......##.##.#...#####.##..#.###..#.#####..##.#..##.###......#...#...#..#......###.######...#.#.##..#
###..#...#.##..###.#....##...#..#####.#.#..#.###...#####.#....##..####.#.##...#.#...##..#.#.#.#..#.#
...##.#.##.##..#.#.#.###.#.#...#.....###.###.##...#.###.##...##..#..###.#..##.##..###.#....###..##..
.##.#..###..###.##.##...#..#####...#.....#####.##..####...#.##.#.#..##.#.#.#....###.....#....##.....
######.#..#.#..#....#.###...####.####.#.........#..##.#..##..##.....#..#.##.##...#...#####.#.##..#.#
.##.###...####....#.####...#####..#..#...#..#.....###.#..#.###..#.###.#.......##.####..#.##.#...##..
........#.#.##.#.....#####.###......##..#.##.#..#...####.#...#..###.#.#...##..#.#...#.####...#.#.###
.#..#.##..##...######.###.##.#.#...#.#.#.#.##..##..##.#.##..#....#.##...#.##.##...##....##.###.##.#.
##...#...#...###.#.#...#...#..###......##.#.#....##..##.#..##.#.######...#..##.#.##.#.#....#.##.##..
...#..###.#....#...#.##..##.#.##.#..###.##..#.##..####.#########....#.....##.#.##.##..##.##.######.#
#.##.#..##.......###...#.###....###.#..####..##.#####.##.###....##....#.###...####..#.#.#.##.....###
.......#...#...##.#...##.#.#..#.##..##.#....###...##.#####...#.........#.......###.##.#.#.###....##.
###.#.##.##.....#.#..#.#..####.####..#..###..........####.#.##...#######.###..#####..#.....#..###..#
#...##.##..####.##.###.#.#######..###.#..#######..#.##.####...#..#.##.####..####.#.#.......####.#...
...#.##..#..#..##........#.#..#..#.#....#.###.#.###..#.......###..#.....#....#..##.#...#.###...##.#.
###.##..#.##.#.#####..#.##.####....#####..###.#.#..#...#...###.#.##..#.#.#.....#.####.#.#.#.#.#.#...
..##..##..#..##.##.#...#..#....####....#...#..####..#.....######.###.####.#....##....##.#.#.###....#
.#.#.#.##..####..#.....#.####.#....#.....#....#.##..#.#..#.#...#.#.#.#..#..#..##.#....####.......#..
..##.##..###......#...#..##...#.###.####.#...#.####..#.#.#.....#.#...####...#.########.##.#.#.#..###
#....#.##.....##.###.##.###..#.####.....####.##...#..##.###...###..###.#....####.#..#..#..#.#..##.#.
.#.#.##....#.##......#.#..###.#....###....#......#.#.##.##.#########..##..#...#.####..#...####..#..#
.#.#.......##.#.##.#...#...#.##.#..#.#.#.##....#..###.###.##.#.#...##.#..#..##....#..###.#...#.#.##.
#.##.#....####...#..##..#.#.#.#.##.#...#####.#...#..#..#.####.####.#.#....#......##..##..###...#..##
..##.###..##.####..#..#..##...###.#.#.#######.####...####......##.##..#...#.##...##....#..#..#.....#
....#..#..#.#.####.#...##..#....####.#..####...#.#...###...#..#..##...#....##...#.....#.#..#.#.#...#
...#.#.#.##..##.###..#.######....####.###...##...###.#...##.####..#.#..#.#..#.##.....#.#.#..##......
.#.##.##.....##.#..###.###.##....#...###.#......#...##.###.#.##.##...###...###...#.######..#......#.
###..#...#......#..##...#....##.#..###.##.####..##..##....####.#...#.#....##..#.#######..#.#.#####..
##...#####..####..##....#.#.###.##.#..#.#..#.....###...###.#####.....#..##.#......#...#.###.##.##...
...#.#.#..#.###..#.#.#....##.#.#..####.##.#.####.#.#.#...#....##....#.##.####..###.#.#...##.#..#..##
#.#.#..#.##..##.##.#...##.#....#...###..##..#.#######.#.###..##......##.#..###.########.#.##..#.#.##
######.###....##..#..#...####....#.#.#..#...#..######.#.#.##..##....##....##.##.##...#..#.####.#.#..
#####.###..#..###......##...##.####.#.#.#.###.......##..##.####..##.####.#..#..####..#.####.#####...
##.#.#.###..##.#.##.#.#.#.##.#...##........###.#.##..####....###.#.####.####.#.......##.##.##...##..
#.#..###...#..##.....##.#..#.#..##..######.#####...###.#.......###...#..##..#..#..##.#.#....#..#..#.
#.#..####.###..#...#...#...#.###..#.#.#.#.#.#.#..#....#.##.##.##..###..####.#..##..##.###.###....##.
#..#.##.#####........#..#.##.#..##.#...#....#..#.##..###..##..##.##..#..##.#.#...#.#.##.#.##....#.#.
.......##..#.....#..#.#.....#.##...####.###..####..#.#.#.#..#.....#....##...#..#.##..###.#.#....#...
#...###########.##.....##...###.#.##.##..####.##...#.####.#####.#####.####...###.##...##..#.#.###..#
....#.#.###.####.###...#...#.#..###.#.#.##...#..#.#.#..#.####..#..###.######.#.####.###...###.#.##.#
.....#..#..########...#.#.#.#.#.#.#.#..###.##..####...##.#.#.#...##..#####.##.#...#.####.#######.##.
.......#...#.#..#..#...#..#..##.....#.##....##.##...##..##.##...##...#.#..#.##.#.###.#.####.#.#..##.
.####...#...#.#.#....##..........##.##.###.##.#.#..#.#.#......########.#...#.####.##.###..##...####.
#.#.#...##.###..##..#..#.....####.#.....##.##.#..#.#.###.#..#######...##..#.#..#.#..############.###
.##..####.#..#.....###..#..#.#.....#.#.#...##.##.#....#..#..###.#...#....#.#...####..#.....###.####.
..#...#.###.###....##.#..#.##..####.##.#.##.##.##...###.####..#.#.#.##.#.#.#..###..##.##.##.##.#..##
#...............##.....######.#.#####.##.#....#.#..#.##...#.##....#........##.##...#.##.##.#..#.##.#
#..##..#.#.#.##.#..#.#.##.##...#...#..#.#.##..#.#...###...##...###..#####.#.#..#..#.#..#.#.##...##.#
.#######.#.....##...#.#.####.######.#..#......#....##.#.#..#..###.#...###...#....#.#..#.##.#...#.#..
#.###......##.#.##..#.###.###..####..##....#..###......##..##..#####.####....#...###.....###.#..#...
###...#....###.#..#.###.##...###.##.......##.##.#.#.#....####....###..##.###...#..##....#.#.##..##..
.##.......##.######.#.#..#..##....#####.###.#.##.....####....#......####....#.##.#.##..#.##...##.#.#
.#.###...#.#.#.##.###..###...##..#.##.##..##..#.....###.#..#.##.##.####........##.#####.#.#....#...#
##...##..#.##.#######.###.#.##.#####....##.....##.#.....#.#.##.#....#.##.#....##.#..#.###..#..#.#...
.#..#.#.#.#...#.##...###.##.#.#...###.##...#.#..###....###.#.###...##..###..#..##.##....###...###.##

@ -0,0 +1,129 @@
#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@aoc-title[18]
@defmodule[aoc-racket/day18]
@link["http://adventofcode.com/day/18"]{The puzzle}. Our @link-rp["day18-input.txt"]{input} is a 100 × 100 grid describing the initial state of a matrix of lights.
@chunk[<day18>
<day18-setup>
<day18-q1>
<day18-q2>
<day18-test>]
@section{How many lights are on after 100 iterations of the light-switching rules?}
There are two rules for incrementing the state of the lighting grid:
@itemlist[
@item{A light that's on stays on when 2 or 3 adjacent lights are also on, and otherwise turns off.}
@item{A light that's off turns on if exactly 3 adjacent lights are also on.}
]
These rules are equivalent to an implementation of Conway's @link["https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life"]{Game of Life} (recall that we implemented another Conway algorithm in @secref{Day_10}).
To model our lighting grid, we'll reuse our technique from @secref{Day_6} of using a single vector and translating between Cartesian coordinates and vector indexes. A lit bulb will be represented by @racket[1], an unlit bulb by, you guessed it, @racket[0].
The heavy lifting is in the @racket[iterate-grid] function, which steps through each bulb, looks at the eight adjacent bulbs in the previous grid, and determines whether the bulb should be on or off.
As we think about that function, we might notice that life will be easier if we don't have to make special accommodations for bulbs at the edges of the grid, which ordinarily don't have eight adjacent bulbs. So what we'll do is add a margin of bulbs around the perimeter, and leave them in the off position. That way, all the bulbs in our original grid will have an eight-bulb neighborhood. So instead of modeling our grid with a 100 × 100 = 10,000 unit vector, we'll use a 102 × 102 = 10,404 unit vector.
After that, it's just a matter of loading our input data into a grid, running @racket[iterate-grid] 100 times  a great job for @racket[for/fold] and counting the activated bulbs in the resulting grid.
@chunk[<day18-setup>
(require racket rackunit)
(provide (all-defined-out))
(define grid-side 102)
(define (rowcol->idx row col) (+ (* grid-side row) col))
(define (idx->rowcol idx) (quotient/remainder idx grid-side))
(define (count-lit grid) (apply + (vector->list grid)))
(define bulb-on 1)
(define bulb-off 0)
(define (input->grid str)
(define grid-vec (make-vector (* grid-side grid-side) bulb-off))
(for* ([(bulb-row bulb-row-idx) (in-indexed (string-split str))]
[(bulb bulb-col-idx) (in-indexed (regexp-match* #rx"." bulb-row))])
(vector-set! grid-vec (rowcol->idx (add1 bulb-row-idx) (add1 bulb-col-idx))
(if (equal? bulb "#") bulb-on bulb-off)))
grid-vec)
(define (bulb+adjacents grid grid-idx)
(define-values (row col) (idx->rowcol grid-idx))
(for*/vector ([r (in-range (sub1 row) (+ row 2))]
[c (in-range (sub1 col) (+ col 2))])
(vector-ref grid (rowcol->idx r c))))
(define (iterate-grid grid)
(for*/vector ([row (in-range grid-side)]
[col (in-range grid-side)])
(cond
[(or (= row 0) (= col 0)
(= row (sub1 grid-side))
(= col (sub1 grid-side)))
bulb-off]
[else
(define bulb-idx (rowcol->idx row col))
(define bulb (vector-ref grid bulb-idx))
(define lit-neighbors
(- (count-lit (bulb+adjacents grid bulb-idx)) bulb))
(cond
[(= bulb-on bulb) (if (<= 2 lit-neighbors 3) bulb-on bulb-off)]
[(= 3 lit-neighbors) bulb-on]
[else bulb-off])])))
]
@chunk[<day18-q1>
(define (q1 input-str)
(define initial-grid (input->grid input-str))
(define iterations 100)
(define final-grid (for/fold ([grid-so-far initial-grid])
([i (in-range iterations)])
(iterate-grid grid-so-far)))
(count-lit final-grid))]
@section{How many lights are on after 100 iterations, if the corner bulbs are always lit?}
Same as above, except we turn on the four corner bulbs after parsing the input, and after every iteration.
@chunk[<day18-q2>
(define (light-corners grid)
(vector-set*! grid (rowcol->idx 1 1) bulb-on
(rowcol->idx 1 100) bulb-on
(rowcol->idx 100 1) bulb-on
(rowcol->idx 100 100) bulb-on)
grid)
(define (q2 input-str)
(define initial-grid (light-corners (input->grid input-str)))
(define iterations 100)
(define final-grid (for/fold ([grid-so-far initial-grid])
([i (in-range iterations)])
(light-corners (iterate-grid grid-so-far))))
(count-lit final-grid))
]
@section{Testing Day 18}
@chunk[<day18-test>
(module+ test
(define input-str (file->string "day18-input.txt"))
(check-equal? (q1 input-str) 821)
(check-equal? (q2 input-str) 886))]

@ -0,0 +1,45 @@
Al => ThF
Al => ThRnFAr
B => BCa
B => TiB
B => TiRnFAr
Ca => CaCa
Ca => PB
Ca => PRnFAr
Ca => SiRnFYFAr
Ca => SiRnMgAr
Ca => SiTh
F => CaF
F => PMg
F => SiAl
H => CRnAlAr
H => CRnFYFYFAr
H => CRnFYMgAr
H => CRnMgYFAr
H => HCa
H => NRnFYFAr
H => NRnMgAr
H => NTh
H => OB
H => ORnFAr
Mg => BF
Mg => TiMg
N => CRnFAr
N => HSi
O => CRnFYFAr
O => CRnMgAr
O => HP
O => NRnFAr
O => OTi
P => CaP
P => PTi
P => SiRnFAr
Si => CaSi
Th => ThCa
Ti => BP
Ti => TiTi
e => HF
e => NAl
e => OMg
ORnPBPMgArCaCaCaSiThCaCaSiThCaCaPBSiRnFArRnFArCaCaSiThCaCaSiThCaCaCaCaCaCaSiRnFYFArSiRnMgArCaSiRnPTiTiBFYPBFArSiRnCaSiRnTiRnFArSiAlArPTiBPTiRnCaSiAlArCaPTiTiBPMgYFArPTiRnFArSiRnCaCaFArRnCaFArCaSiRnSiRnMgArFYCaSiRnMgArCaCaSiThPRnFArPBCaSiRnMgArCaCaSiThCaSiRnTiMgArFArSiThSiThCaCaSiRnMgArCaCaSiRnFArTiBPTiRnCaSiAlArCaPTiRnFArPBPBCaCaSiThCaPBSiThPRnFArSiThCaSiThCaSiThCaPTiBSiRnFYFArCaCaPRnFArPBCaCaPBSiRnTiRnFArCaPRnFArSiRnCaCaCaSiThCaRnCaFArYCaSiRnFArBCaCaCaSiThFArPBFArCaSiRnFArRnCaCaCaFArSiRnFArTiRnPMgArF

@ -0,0 +1,95 @@
#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@aoc-title[19]
@defmodule[aoc-racket/day19]
@link["http://adventofcode.com/day/19"]{The puzzle}. Our @link-rp["day19-input.txt"]{input} is list of ``molecule'' transformations that look like @tt{B => TiRnFAr}, and then a longer test molecule.
@chunk[<day19>
<day19-setup>
<day19-q1>
<day19-q2>
<day19-test>]
@section{How many distinct molecules can be created after one transformation?}
Starting with our test molecule, we are asked to try every molecule transformation at every possible position, and count up the distinct molecules that are created.
Each molecule transformation defines a string replacement. We'll parse our input into a test molecule, and a list of transformations (= each transformation is a list of a before and after string). Because we want to perform each transformation at every possible point in the test molecule, we can't use @racket[regexp-replace] (because it only replaces the first match) or @racket[regexp-replace*] (because it replaces all matches). Instead we'll use @racket[regexp-match-positions*] to generate a list of match positions, and then perform the substitutions at each location to generate our list of molecules. After doing this for every transformation, we can @racket[remove-duplicates] and see how many are left.
@chunk[<day19-setup>
(require racket rackunit)
(provide (all-defined-out))
(define (parse-input-str input-str)
(match-define (cons molecule transformation-strings)
(reverse (string-split input-str "\n")))
(define transformations
(filter-not empty?
(map (curryr string-split " => ") transformation-strings)))
(values molecule transformations))
]
@chunk[<day19-q1>
(define (transform-molecule* molecule target-atom replacement-atom)
(for/list ([pos (in-list (regexp-match-positions* (regexp target-atom) molecule))])
(match-define (cons start finish) pos)
(string-append (substring molecule 0 start)
replacement-atom
(substring molecule finish (string-length molecule)))))
(define (q1 input-str)
(define-values (molecule transformations) (parse-input-str input-str))
(length
(remove-duplicates
(append-map (λ(target replacement)
(transform-molecule* molecule target replacement))
(map first transformations) (map second transformations)))))]
@section{What's the fewest number of transformations that will generate the test module?}
Unlike some of the puzzles, this second part is a lot harder. The idea is that the test molecule was made from a series of transformations, starting with the single atom @racket{e}. So not only do we have to reverse-engineer this process, but we have to find the most efficient path.
There were three questions in Advent of Code that I couldn't figure out. This was the first. The answer to the first question seems to provide a clue: we could reverse the transformations, perform the same replacement process, and we'd have a list of precursor molecules that could've existed one step before the test molecule. Then you repeat this process with all the possible precursors, until you get back to @racket{e}. This will work in theory, but not in practice, because it requires checking too many possibilities.
So I cheated. I adapted a @link["https://github.com/ChrisPenner/Advent-Of-Code-Polyglot/blob/master/python/19/part2.py"]{Python algorithm} for Racket. I can tell you how it works, though I'm still figuring out @italic{why} it does. This function loops through the transformations and applies them everywhere they will fit. When it reaches a dead end  meaning, the molecule hasn't changed during the loop  it randomly reorders the transformations with @racket[shuffle] and tries again. It is strange to me that this process would converge to an answer at all, let alone the best answer, let alone so quickly.
@chunk[<day19-q2>
(define (q2 input-str)
(define-values (starting-molecule xforms) (parse-input-str input-str))
(let loop ([current-mol starting-molecule][transform-count 0]
[shuffles 0][xforms xforms])
(cond
[(equal? current-mol "e") transform-count]
[else
(define-values (xformed-mol last-count)
(for/fold ([mol current-mol][count-so-far transform-count])
([(from to) (in-parallel (map first xforms) (map second xforms))])
(values (string-replace mol to from)
(+ count-so-far (length (regexp-match* to mol))))))
(if (not (equal? current-mol xformed-mol))
(loop xformed-mol last-count shuffles xforms)
(loop starting-molecule 0 (add1 shuffles) (shuffle xforms)))])))
]
@section{Testing Day 19}
@chunk[<day19-test>
(module+ test
(define input-str (file->string "day19-input.txt"))
(check-equal? (q1 input-str) 576)
(check-equal? (q2 input-str) 207))]

@ -0,0 +1 @@
36000000

@ -0,0 +1,72 @@
#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@aoc-title[20]
@defmodule[aoc-racket/day20]
@link["http://adventofcode.com/day/20"]{The puzzle}. Our @link-rp["day20-input.txt"]{input} is a target number of presents, in this case @racket[36000000].
@chunk[<day20>
<day20-setup>
<day20-q1>
<day20-q2>
<day20-test>]
@section{What's the first house that gets the target number of presents?}
We're asked to imagine infinite elves delivering presents to an infinite sequence of houses. (Already @link["http://practicaltypography.com/the-infinite-pixel-screen.html"]{I like} this puzzle.) The first elf delivers a present to every house equal to 10 times his number (= 10); the second elf, 20 gifts to every second house; the @italic{n}th elf, 10@italic{n} gifts to every @italic{n}th house.
Math jocks will notice that the elf behavior roughly describes a @link["https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes"]{Sieve of Eratosthenes}. Each house is visited by elf @italic{n} only if @italic{n} is a divisor of the house number. (Houses that are primes are therefore only visited by the first elf.) Might there be a Racket function that finds the divisors of a number? Why, yes it's called @racket[divisors]. We can use it to find the numbers of the elves that visit a house, and loop through house numbers till we reach the target. (The 10-gift multiplier is arbitrary.)
@chunk[<day20-setup>
(require racket rackunit (only-in math divisors))
(provide (all-defined-out))
]
@chunk[<day20-q1>
(define (q1 input-str)
(define target-gifts (read (open-input-string input-str)))
(define gifts-per-elf 10)
(for/first ([house-number (in-naturals)]
#:when (let* ([elves (divisors house-number)]
[elf-gifts
(apply + (map (curry * gifts-per-elf) elves))])
(>= elf-gifts target-gifts)))
house-number))]
@section{What's the first house that gets the target number of presents, if each elf delivers 11 gifts to 50 houses?}
Going with the math-jock vibe, what this condition means is that the highest-numbered house the @italic{n}th elf will visit is 50@italic{n}. So the answer for this question is like the first, but we'll @racket[filter] the list of elves using this condition.
@chunk[<day20-q2>
(define (q2 input-str)
(define target-gifts (read (open-input-string input-str)))
(define gifts-per-elf 11)
(for/first ([house-number (in-naturals)]
#:when (let* ([elves (divisors house-number)]
[elves (filter
(λ(e) (<= house-number (* 50 e))) elves)]
[elf-gifts
(apply + (map (curry * gifts-per-elf) elves))])
(>= elf-gifts target-gifts)))
house-number))
]
@section{Testing Day 20}
@chunk[<day20-test>
(module+ test
(define input-str (file->string "day20-input.txt"))
(check-equal? (q1 input-str) 831600)
(check-equal? (q2 input-str) 884520))]

@ -0,0 +1,3 @@
Hit Points: 109
Damage: 8
Armor: 2

@ -0,0 +1,139 @@
#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@aoc-title[21]
@defmodule[aoc-racket/day21]
@link["http://adventofcode.com/day/21"]{The puzzle}. Our @link-rp["day21-input.txt"]{input} tells us the hit points, damage, and armor of an enemy we have to defeat.
@chunk[<day21>
<day21-items>
<day21-setup>
<day21-q1>
<day21-q2>
<day21-test>]
@section{What's the least we can spend and win?}
I hate RPGs. So at first, I prejudicially refused to do this puzzle. But after a second look, I could see how to do it. So I relented.
We have 100 hit points. Our enemy has the hit points, damage, and armor given in our input. The Fake Equipment Shop is selling the following items (the columns are cost, damage rating, and armor value):
@chunk[<day21-items>
(define no-item
'(None 0 0 0))
(define weapons
'((Dagger 8 4 0)
(Shortsword 10 5 0)
(Warhammer 25 6 0)
(Longsword 40 7 0)
(Greataxe 74 8 0)))
(define armors
'((Leather 13 0 1)
(Chainmail 31 0 2)
(Splintmail 53 0 3)
(Bandedmail 75 0 4)
(Platemail 102 0 5)))
(define rings
'((Damage+1 25 1 0)
(Damage+2 50 2 0)
(Damage+3 100 3 0)
(Defense+1 20 0 1)
(Defense+2 40 0 2)
(Defense+3 80 0 3)))]
We have to buy a weapon. We can buy armor, or not. And we can buy zero, one, or two rings. On each turn, we inflict damage equal to our attack score minus the boss's armor, though we always do at least 1 point of damage. Likewise, the boss inflicts damage equal to her attack minus our armor, with a minimum of 1. Repeat. If the boss's hit points go to zero before ours, we win. Otherwise, not. Thus the question  what's the cheapest set of equipment that wins?
Similar to puzzles we've already seen, this is another combinatoric question. If we follow that pattern, we need to generate all possible equipment sets, find out which ones win, and then pick the cheapest. But there's an easy optimization available here. The cheapness of a set of equipment is independent of whether it wins. So we'll sort our equipment sets by cost, and try them starting from the cheapest. The first one that wins will be our answer (saving us the trouble of trying them all). The total cost, damage, and armor of the equipment set is just the sum of the individual values.
After that, we'll set up a @racket[we-win?] function that will simulate the battle of a certain equipment set vs. the boss. It's a @racket[for/fold] loop that will alternate attacks until either the player or boss has negative hit points, returning @racket[#t] if we win, or @racket[#f] if the boss does.
@chunk[<day21-setup>
(require racket rackunit)
(provide (all-defined-out))
(define (cost equipment-set)
(apply + (map second equipment-set)))
(define equipment-sets-by-cost
(let ([equipment-sets
(for*/list ([weapon (in-list weapons)]
[armor (in-list (cons no-item armors))]
[lh-ring (in-list (cons no-item rings))]
[rh-ring (in-list (cons no-item (remove lh-ring rings)))])
(list weapon armor lh-ring rh-ring))])
(sort equipment-sets < #:key cost)))
(define player-hit-points 100)
(define min-damage 1)
(define (equipment-set->player equipment-set)
(let ([total-damage (apply + (map third equipment-set))]
[total-armor (apply + (map fourth equipment-set))])
(list player-hit-points total-damage total-armor)))
(define player-turn? even?)
(define hit-points first)
(define damage second)
(define armor third)
(define (attack attacker defender)
(define net-damage (max (- (damage attacker) (armor defender)) min-damage))
(list (- (hit-points defender) net-damage) (damage defender) (armor defender)))
(define (we-win? player boss)
(define-values (last-player-state last-boss-state)
(for/fold ([player-state player][boss-state boss])
([turn-number (in-naturals)]
#:break (<= (min (hit-points player-state)
(hit-points boss-state)) 0))
(if (player-turn? turn-number)
(values player-state (player-state . attack . boss-state))
(values (boss-state . attack . player-state) boss-state))))
(<= (hit-points last-boss-state) 0))
]
@chunk[<day21-q1>
(define (q1 input-str)
(define boss (filter number? (map string->number (string-split input-str))))
(for/first ([equip (in-list equipment-sets-by-cost)]
#:when (let ([player (equipment-set->player equip)])
(we-win? player boss)))
(cost equip)))]
@section{What's the most we can spend and lose?}
A simple variation of the first answer. Instead, we iterate through the equipment sets starting at the most expensive, and stop when we find the first loser.
@chunk[<day21-q2>
(define (q2 input-str)
(define boss (filter number? (map string->number (string-split input-str))))
(for/first ([equip (in-list (reverse equipment-sets-by-cost))]
#:when (let ([player (equipment-set->player equip)])
(not (we-win? player boss))))
(cost equip)))
]
@section{Testing Day 21}
@chunk[<day21-test>
(module+ test
(define input-str (file->string "day21-input.txt"))
(check-equal? (q1 input-str) 111)
(check-equal? (q2 input-str) 188))]

@ -0,0 +1,2 @@
Hit Points: 58
Damage: 9

@ -0,0 +1,18 @@
#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@aoc-title[22]
@link["http://adventofcode.com/day/22"]{The puzzle}. Our @link-rp["day22-input.txt"]{input} tells us our hit points and damage.
@chunk[<day22>
;;
]
@section{What's the least mana we can spend and win?}
Did I mention I hate RPGs? Well, this question is much more RPG-ish than @secref{Day_21}. It involves mana and spellscasting. So I'm taking a pass. If someone devises a concise solution, I'll be happy to add it to this page.
Otherwise, onward to @secref{Day_23}.

@ -0,0 +1,49 @@
jio a, +19
inc a
tpl a
inc a
tpl a
inc a
tpl a
tpl a
inc a
inc a
tpl a
tpl a
inc a
inc a
tpl a
inc a
inc a
tpl a
jmp +23
tpl a
tpl a
inc a
inc a
tpl a
inc a
inc a
tpl a
inc a
tpl a
inc a
tpl a
inc a
tpl a
inc a
inc a
tpl a
inc a
inc a
tpl a
tpl a
inc a
jio a, +8
inc b
jie a, +4
tpl a
inc a
jmp +2
hlf a
jmp -7

@ -0,0 +1,118 @@
#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@aoc-title[23]
@defmodule[aoc-racket/day23]
@link["http://adventofcode.com/day/23"]{The puzzle}. Our @link-rp["day23-input.txt"]{input} is a list of instructions representing a program for a two-register virtual machine.
@chunk[<day23>
<day23-setup>
<day23-q1>
<day23-q2>
<day23-test>]
@section{What's the value in register @tt{b} after the program runs?}
The virtual machine has two registers, @tt{a} and @tt{b}, that both start at 0. It also has six instructions:
@itemlist[
@item{@tt{hlf r} sets register r to half its current value, then continues.}
@item{@tt{tpl r} sets register r to triple its current value, then continues.}
@item{@tt{inc r} adds 1 to register r, then continues.}
@item{@tt{jmp offset} jumps the instruction that is @tt{offset} steps away. An @tt{offset} can be positive or negative.}
@item{@tt{jie r, offset} is like @tt{jmp}, but only jumps if r is even.}
@item{@tt{jio r, offset} jumps by @tt{offset} if register r = 1.}
]
Although the virtual machine has the equivalent of functions & variables, the jump instructions add a complication. We can't just evaluate the instructions top to bottom. We have to maintain a list of all the instructions, and a pointer to where we are, so if we get a jump instruction, we can move to the right place.
Because we have to repeatedly update the values of the register, it'll be more convenient to use a hash table. Overall, the solution follows the general pattern of @secref{Day_7}.
Notice also that we're encasing the lines of the VM program in @racket[thunk*]. This creates a function wrapper around each instruction so that its evaluation is delayed until we explicitly ask for it. The @racket[inst] transformer turns the lines of the program into equivalent operations on our register hash table. All these functions return a number that indicates the offset of the next instruction (if it's not a jump instruction, then this value is just 1).
@chunk[<day23-setup>
(require racket rackunit
(for-syntax racket/file racket/string sugar/debug))
(provide (all-defined-out))
(define-syntax (convert-input-to-instruction-functions stx)
(syntax-case stx ()
[(_)
(let* ([input-strings (file->lines "day23-input.txt")]
[inst-strings (map (λ(str) (format "(thunk* (inst ~a))" (string-replace str "," ""))) input-strings)]
[inst-datums (map (compose1 read open-input-string) inst-strings)])
(datum->syntax stx `(define instructions (list ,@inst-datums))))]))
(define registers (make-hash '((a . 0)(b . 0))))
(define default-offset 1)
(define-syntax-rule (define-reg-updater id thunk)
(define (id reg)
(hash-update! registers reg thunk)
default-offset))
(define-reg-updater tpl (λ(val) (* 3 val)))
(define-reg-updater inc (λ(val) (add1 val)))
(define-reg-updater hlf (λ(val) (/ val 2)))
(define (jmpf reg num pred)
(if (pred (hash-ref registers reg)) num 1))
(define-syntax (inst stx)
(syntax-case stx (jmp jio jie)
[(_ jio reg num)
#'(jmpf 'reg num (curry = 1))]
[(_ jie reg num)
#'(jmpf 'reg num even?)]
[(_ jmp num)
#'num]
[(_ op reg)
#'(op 'reg)]))
(convert-input-to-instruction-functions)
]
With the instructions set up as functions, running the program is easy. We start at index 0 and evaluate the first instruction in our list (which will possibly update one of the registers). The result is the offset for the next instruction. We continue until our index exceeds the number of instructions available.
@chunk[<day23-q1>
(define (q1)
(let eval-instruction ([idx 0])
(if (>= idx (length instructions))
(hash-ref registers 'b)
(let* ([inst (list-ref instructions idx)]
[jump-offset (inst)]
[next-idx (+ jump-offset idx)])
(eval-instruction next-idx)))))]
@section{What's the value in register @tt{b} if register @tt{a} starts as 1?}
Same as the first question, except we'll reset the registers before running the program.
@chunk[<day23-q2>
(define (q2)
(hash-set*! registers 'a 1 'b 0)
(q1))
]
@section{Testing Day 23}
@chunk[<day23-test>
(module+ test
(check-equal? (q1) 184)
(check-equal? (q2) 231))]

@ -0,0 +1,28 @@
1
3
5
11
13
17
19
23
29
31
37
41
43
47
53
59
67
71
73
79
83
89
97
101
103
107
109
113

@ -0,0 +1,116 @@
#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@aoc-title[24]
@defmodule[aoc-racket/day24]
@link["http://adventofcode.com/day/24"]{The puzzle}. Our @link-rp["day24-input.txt"]{input} is a list of weights of certain packages.
@chunk[<day24>
<day24-setup>
<day24-q1>
<day24-q2>
<day24-test>]
@section{What's the score of the optimal group of packages, when divided into three groups?}
We need to arrange our packages into three groups of equal weight. There are multiple ways to do this, of course. According to the puzzle, the optimal arrangement is the one that results in a group with the fewest number of packages. If multiple arrangements yield an equally small group, then the winner is the one with the lowest ``quantum entanglement'' score, which is defined as the product of the weights.
This puzzle brings together elements we've seen before. Each group in a valid arrangement needs to have equal weight, which is the total weight divided by 3. Rather than immediately trying to find valid three-group arrangements, we can start with single groups of the right weight, sorted by our scoring criteria, and find the first one that's part of a valid solution.
In principle, we could reuse the @racket[powerset] function we made for @secref{Day_17} to generate all possible groups, and then @racket[filter] for the groups that are the correct weight. But in truth, we got lucky on @secref{Day_17}. There were only 20 elements, so our power set contained @racket[(expt 2 20)] (about a million) options, which only takes a few seconds to search. This time, we have 28 elements, thus 256 times as many options, which would require 1015 minutes of searching.
To be more efficient, we'll use @racket[for*/first] to alternate between generating lists of possible groups, and then testing them. We'll @racket[sort] these lists by ``quantum entanglement'' so we know we're testing groups from best to worst (similar to what we did in @secref{Day_21} with equipment cost).
After that, we just need to write a function that will test whether a given group is part of a valid solution. The first group that has a solution is our winner. Our @racket[has-solution?] function works by removing the test group from the set of all packages, and seeing if there is another group within that has the same weight. (We don't have to check the third group if the first two groups are the same weight, then the third one is too.)
@chunk[<day24-setup>
(require racket rackunit)
(provide (all-defined-out))
(define (groups packages len goal-weight)
(cond
[(= len 0) empty]
[(= len 1) (map list (filter (curry = goal-weight) packages))]
[else
(append*
(for/list ([x (in-list packages)])
(define later-packages (cdr (member x packages)))
(append-map (λ(ss) (define new-group (cons x ss))
(if (= goal-weight (weight new-group))
(list new-group)
empty))
(groups later-packages (sub1 len) (- goal-weight x)))))]))
(define (weight group) (apply + group))
(define (quantum-entanglement group) (apply * group))
(define (remove-group group packages)
(filter (λ(p) (not (member p group))) packages))
(define (has-solution? group packages)
(define target-weight (weight group))
(define remaining-packages (remove-group group packages))
(for/first ([len (in-range (length remaining-packages))]
#:when (not (empty?
(groups remaining-packages len target-weight))))
#t))
]
@chunk[<day24-q1>
(define (find-three-group-solution all-packages target-weight)
(for*/first ([len (in-range (length all-packages))]
[group (in-list
(sort
(groups all-packages len target-weight)
#:key quantum-entanglement <))]
#:when (has-solution? group all-packages))
(quantum-entanglement group)))
(define (q1 input-str)
(define all-packages (map string->number (string-split input-str)))
(define target-weight (/ (weight all-packages) 3))
(find-three-group-solution all-packages target-weight))
]
@section{What's the optimal score when divided into four groups?}
The loop is similar, but instead of using @racket[has-solution?] (which in essence tests if there is a two-group solution), we ask if there is a three-group solution after our target group is removed. So we create a recursive relationship between the second question and the first.
@chunk[<day24-q2>
(define (q2 input-str)
(define all-packages (map string->number (string-split input-str)))
(define target-weight (/ (weight all-packages) 4))
(for*/first ([len (in-range (length all-packages))]
[group (in-list (sort
(groups all-packages len target-weight)
#:key quantum-entanglement <))]
#:when (find-three-group-solution
(remove-group group all-packages) target-weight))
(quantum-entanglement group)))
]
@section{Testing Day 24}
@chunk[<day24-test>
(module+ test
(define input-str (file->string "day24-input.txt"))
(check-equal? (q1 input-str) 10439961859)
(check-equal? (q2 input-str) 72050269))]

@ -0,0 +1 @@
To continue, please consult the code grid in the manual. Enter the code at row 2947, column 3029.

@ -0,0 +1,80 @@
#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@aoc-title[25]
@defmodule[aoc-racket/day25]
@link["http://adventofcode.com/day/25"]{The puzzle}. Our @link-rp["day25-input.txt"]{input} is a row and column number for a grid.
@chunk[<day25>
<day25-setup>
<day25-q1>
<day25-test>]
@section{What code do you give the machine?}
The puzzle involves a series of codes in a grid. The first code is @racket[20151125] and subsequent codes are computed by multiplying by @racket[252533] and calculating the remainder after dividing by @racket[33554393]. Then the codes are put into a grid in diagonal fashion, in the order shown:
@tt{
  |  1  2  3  4  5  6
@(linebreak)
++++++++++++++++++++++ @(linebreak)
1 |  1  3  6 10 15 21 @(linebreak)
2 |  2  5  9 14 20 @(linebreak)
3 |  4  8 13 19 @(linebreak)
4 |  7 12 18 @(linebreak)
5 | 11 17 @(linebreak)
6 | 16}
The grid is described as infinite. But this doesn't play a role in the puzzle, since our job is to find the number at the finite coordinate given in our input. So we need a function for finding the @italic{n}th code in the series, and a function for finding which @italic{n} resides at a given grid location. Short & sweet.
@chunk[<day25-setup>
(require racket rackunit)
(provide (all-defined-out))
]
@chunk[<day25-q1>
(define first-code 20151125)
(define (next-code code)
(modulo (* code 252533) 33554393))
(define (nth-code n)
(for/fold ([code-so-far first-code])
([i (in-range (sub1 n))])
(next-code code-so-far)))
(define (rc->n row col)
(define first-col-val (add1 (apply + (range row))))
(define col-offset-val (apply + (range (add1 row) (+ row col))))
(+ first-col-val col-offset-val))
(define (q1 input-str)
(match-define (list _ row col)
(map string->number
(regexp-match #px"row (\\d+), column (\\d+)" input-str)))
(nth-code (rc->n row col)))
]
@section{Testing Day 25}
@chunk[<day25-test>
(module+ test
(define input-str (file->string "day25-input.txt"))
(check-equal? (q1 input-str) 19980801))]

@ -0,0 +1,14 @@
#lang at-exp racket/base
(require scribble/manual scribble/html-properties
scribble/core)
(provide (all-defined-out))
(define (aoc-title which)
(define which-str (number->string which))
(define day-x (format "day-~a" which-str))
(define day-prefix (format "~a-" day-x))
@title[#:style manual-doc-style]{Day @which-str})
(define (link-rp path . text-args)
(element (style #f (list (link-resource path)))
text-args))

@ -0,0 +1,6 @@
#lang info
(define collection "aoc-racket")
(define scribblings '(("aoc-racket.scrbl" (multi-page))))
(define deps '("base" "scribble-lib" "sugar" "rackunit-lib" "math-lib"))
(define test-omit-paths (list #rx"rkt$"))
(define build-deps '("rackunit-lib" "racket-doc" "scribble-doc" "rackunit-doc" "at-exp-lib" "math-doc"))

@ -0,0 +1,3 @@
#lang racket/base
;; nothing at the top level