master-blaster
Matthew Butterick 8 years ago
parent f45137d923
commit 403355f623

@ -21,15 +21,15 @@ You can install this package (if you haven't already) with
@local-table-of-contents[]
@include-section[(submod "day1.rkt" doc)]
@include-section[(submod "day2.rkt" doc)]
@include-section[(submod "day3.rkt" doc)]
@include-section[(submod "day4.rkt" doc)]
@include-section[(submod "day5.rkt" doc)]
@include-section[(submod "day6.rkt" doc)]
@include-section[(submod "day7.rkt" doc)]
@include-section[(submod "day8.rkt" doc)]
@include-section[(submod "day9.rkt" doc)]
@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)]

@ -3,17 +3,17 @@
@aoc-title[1]
@defmodule[aoc-racket/day1]
@defmodule[aoc-racket/day01]
@link["http://adventofcode.com/day/1"]{The puzzle}. Our @link-rp["day1-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.
@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[<day1>
<day1-setup>
<day1-q1>
<day1-q1-alt>
<day1-q2>
<day1-q2-alt>
<day1-test>]
@chunk[<day01>
<day01-setup>
<day01-q1>
<day01-q1-alt>
<day01-q2>
<day01-q2-alt>
<day01-test>]
@section{Where does the elevator land?}
@ -22,7 +22,7 @@ The building has an indefinite number of floors in both directions. So the ultim
@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[<day1-setup>
@chunk[<day01-setup>
(require racket rackunit)
(provide (all-defined-out))
(define up-char #\()
@ -36,7 +36,7 @@ The building has an indefinite number of floors in both directions. So the ultim
@chunk[<day1-q1>
@chunk[<day01-q1>
(define (q1 str)
(get-destination str))]
@ -44,7 +44,7 @@ The building has an indefinite number of floors in both directions. So the ultim
Rather than counting matches with @racket[regexp-match*], we could also convert the string of parentheses directly into a list of numbers.
@chunk[<day1-q1-alt>
@chunk[<day01-q1-alt>
(define (elevator-string->ints str)
(for/list ([c (in-string str)])
(if (equal? c up-char)
@ -63,7 +63,7 @@ We could characterize this as a problem of tracking @italic{cumulative values} o
@margin-note{Nothing wrong with @racket[foldl] and @racket[foldr], but @racket[for/fold] is more flexible, and makes more readable code.}
@chunk[<day1-q2>
@chunk[<day01-q2>
(define (in-basement? movements)
(negative? (apply + movements)))
@ -82,7 +82,7 @@ When you need to stop a loop the first time a condition occurs, you can also con
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[<day1-q2-alt>
@chunk[<day01-q2-alt>
(define (q2-for/first str)
(define basement-position
(let ([ints (elevator-string->ints str)])
@ -101,9 +101,9 @@ The two are similar. The choice comes down to readability and efficiency — me
@section{Testing Day 1}
@chunk[<day1-test>
@chunk[<day01-test>
(module+ test
(define input-str (file->string "day1-input.txt"))
(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)

@ -3,14 +3,14 @@
@aoc-title[2]
@defmodule[aoc-racket/day2]
@defmodule[aoc-racket/day02]
@link["http://adventofcode.com/day/2"]{The puzzle}. Our @link-rp["day2-input.txt"]{input} is a list of strings that represent dimensions of rectangular boxes.
@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[<day2>
<day2-setup>
<day2-q1>
<day2-test>]
@chunk[<day02>
<day02-setup>
<day02-q1>
<day02-test>]
@section{How much paper is needed to wrap the boxes?}
@ -22,14 +22,14 @@ First we need to parse our input file into a list of box dimensions. We'll model
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[<day2-setup>
@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[<day2-q1>
@chunk[<day02-q1>
(define (box->paper box)
(match-define (list x y z) box)
(define sides (list (* x y) (* y z) (* x z)))
@ -45,7 +45,7 @@ According to the problem, the ribbon needed is the perimeter of the smallest sid
We take the same approach, with a new @racket[box->ribbon] function.
@chunk[<day2-q1>
@chunk[<day02-q1>
(define (box->ribbon box)
(match-define (list x y z) box)
(define (perimeter dim1 dim2) (* 2 (+ dim1 dim2)))
@ -61,8 +61,8 @@ We take the same approach, with a new @racket[box->ribbon] function.
@section{Testing Day 2}
@chunk[<day2-test>
@chunk[<day02-test>
(module+ test
(define input-str (file->string "day2-input.txt"))
(define input-str (file->string "day02-input.txt"))
(check-equal? (q1 input-str) 1586300)
(check-equal? (q2 input-str) 3737498))]

@ -3,19 +3,19 @@
@aoc-title[3]
@defmodule[aoc-racket/day3]
@defmodule[aoc-racket/day03]
@link["http://adventofcode.com/day/3"]{The puzzle}. Our @link-rp["day3-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.
@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[<day3>
<day3-setup>
<day3-q1>
<day3-q1-complex>
<day3-q2>
<day3-test>]
@chunk[<day03>
<day03-setup>
<day03-q1>
<day03-q1-complex>
<day03-q2>
<day03-test>]
@section{How many grid cells are visited?}
@ -25,12 +25,12 @@ For dual-valued data, whether to use @seclink["pairs" #:doc '(lib "scribblings/g
Once the whole cell path is computed, the answer is found by removing duplicate cells and counting how many remain.
@chunk[<day3-setup>
@chunk[<day03-setup>
(require racket rackunit)
(provide (all-defined-out))
]
@chunk[<day3-q1>
@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)))
@ -55,7 +55,7 @@ Rather than use Cartesian coordinates, we could rely on Racket's built-in suppor
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[ <day3-q1-complex>
@chunk[ <day03-q1-complex>
(define (string->complex-cells str)
(define start 0)
(define east 1)
@ -81,7 +81,7 @@ By ``split'', the puzzle envisions two people starting at the origin, with one f
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[<day3-q2>
@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])
@ -100,9 +100,9 @@ The solution works the same as before — the only new task is to split the inpu
@section{Testing Day 3}
@chunk[<day3-test>
@chunk[<day03-test>
(module+ test
(define input-str (file->string "day3-input.txt"))
(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))]

@ -4,15 +4,15 @@
@aoc-title[4]
@defmodule[aoc-racket/day4]
@defmodule[aoc-racket/day04]
@link["http://adventofcode.com/day/4"]{The puzzle}. Our @link-rp["day4-input.txt"]{input} is a string of eight characters that represents part of a key for making an MD5 hash.
@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[<day4>
<day4-setup>
<day4-q1>
<day4-q2>
<day4-test>]
@chunk[<day04>
<day04-setup>
<day04-q1>
<day04-q2>
<day04-test>]
@section{What is the lowest-numbered MD5 hash starting with five zeroes?}
@ -21,12 +21,12 @@ We're asked to create an MD5 hash from an input key that consists of our eight-c
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[<day4-setup>
@chunk[<day04-setup>
(require racket rackunit openssl/md5)
(provide (all-defined-out))
]
@chunk[<day4-q1>
@chunk[<day04-q1>
(define (q1 str)
(for/or ([i (in-naturals)])
(define md5-key (string-append str (~a i)))
@ -38,7 +38,7 @@ Whether or not you already know what an MD5 hash is, you can search the Racket d
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[<day4-q2>
@chunk[<day04-q2>
(define (q2 str)
(for/or ([i (in-naturals)])
(define md5-key (string-append str (~a i)))
@ -47,8 +47,8 @@ Exactly the same, except we test for a string of six zeroes. It is likely, howev
@section{Testing Day 4}
@chunk[<day4-test>
@chunk[<day04-test>
(module+ test
(define input-str (file->string "day4-input.txt"))
(define input-str (file->string "day04-input.txt"))
(check-equal? (q1 input-str) 346386)
(check-equal? (q2 input-str) 9958218))]

@ -3,15 +3,15 @@
@aoc-title[5]
@defmodule[aoc-racket/day5]
@defmodule[aoc-racket/day05]
@link["http://adventofcode.com/day/5"]{The puzzle}. Our @link-rp["day5-input.txt"]{input} is a list of random-looking but not really random text strings.
@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[<day5>
<day5-setup>
<day5-q1>
<day5-q2>
<day5-test>]
@chunk[<day05>
<day05-setup>
<day05-q1>
<day05-q2>
<day05-test>]
@section{How many strings are ``nice''?}
@ -26,12 +26,12 @@ A string is ``nice'' if it meets certain criteria:
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[<day5-setup>
@chunk[<day05-setup>
(require racket rackunit)
(provide (all-defined-out))
]
@chunk[<day5-q1>
@chunk[<day05-q1>
(define (nice? str)
(define (three-vowels? str)
(>= (length (regexp-match* #rx"[aeiou]" str)) 3))
@ -59,7 +59,7 @@ This time a string is ``nice`` if it:
Again, a test of your regexp-writing skills.
@chunk[<day5-q2>
@chunk[<day05-q2>
(define (nicer? str)
(define (nonoverlapping-pair? str)
(regexp-match #px"(..).*\\1" str))
@ -73,8 +73,8 @@ Again, a test of your regexp-writing skills.
@section{Testing Day 5}
@chunk[<day5-test>
@chunk[<day05-test>
(module+ test
(define input-str (file->lines "day5-input.txt"))
(define input-str (file->lines "day05-input.txt"))
(check-equal? (q1 input-str) 238)
(check-equal? (q2 input-str) 69))]

@ -3,16 +3,16 @@
@aoc-title[6]
@defmodule[aoc-racket/day6]
@defmodule[aoc-racket/day06]
@link["http://adventofcode.com/day/6"]{The puzzle}. Our @link-rp["day6-input.txt"]{input} is a list of instructions for turning on (or off) the bulbs in a @racket[(* 1000 1000)] grid of lights.
@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[<day6>
<day6-setup>
<day6-q1>
<day6-q2>
<day6-refactored>
<day6-test>]
@chunk[<day06>
<day06-setup>
<day06-q1>
<day06-q2>
<day06-refactored>
<day06-test>]
@section{How many lights are lit after following the instructions?}
@ -22,7 +22,7 @@ When you need random access to a fixed-size set of items, you should think @secr
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[<day6-q1>
@chunk[<day06-q1>
(define (str->instruction str)
(match-define (list* _ action coordinates)
@ -47,7 +47,7 @@ Each instruction consists of two pieces. First, an operation: either @italic{tu
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[<day6-setup>
@chunk[<day06-setup>
(require racket rackunit)
(provide (all-defined-out))
@ -78,7 +78,7 @@ The second part redefines the meaning of the three instructions, and introduces
This part is the same as the last, except we change the definitions of our bulb functions to match the new rules.
@chunk[<day6-q2>
@chunk[<day06-q2>
(define (str->instruction-2 str)
(match-define (list* _ action coordinates)
(regexp-match #px"^(.*?)(\\d+),(\\d+) through (\\d+),(\\d+)$" str))
@ -102,8 +102,8 @@ This part is the same as the last, except we change the definitions of our bulb
Since the only part that changes between the solutions is the bulb functions, we could refactor the solutions to avoid repetition.
@chunk[<day6-refactored>
(define (day-6-solve strs bulb-func-converter)
@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))
@ -131,12 +131,12 @@ Since the only part that changes between the solutions is the bulb functions, we
@section{Testing Day 6}
@chunk[<day6-test>
@chunk[<day06-test>
(module+ test
(define input-strs (file->lines "day6-input.txt"))
(define input-strs (file->lines "day06-input.txt"))
(check-equal? (q1 input-strs) 400410)
(check-equal? (q2 input-strs) 15343601)
(check-equal? (day-6-solve input-strs q1-bulb-func-converter) 400410)
(check-equal? (day-6-solve input-strs q2-bulb-func-converter) 15343601))]
(check-equal? (day06-solve input-strs q1-bulb-func-converter) 400410)
(check-equal? (day06-solve input-strs q2-bulb-func-converter) 15343601))]

@ -3,16 +3,16 @@
@aoc-title[7]
@defmodule[aoc-racket/day7]
@defmodule[aoc-racket/day07]
@link["http://adventofcode.com/day/7"]{The puzzle}. Our @link-rp["day7-input.txt"]{input} describes an electrical circuit, with each line of the file describing the signal provided to a particular wire.
@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[<day7>
<day7-setup>
<day7-ops>
<day7-q1>
<day7-q2>
<day7-test>]
@chunk[<day07>
<day07-setup>
<day07-ops>
<day07-q1>
<day07-q2>
<day07-test>]
@section{What's the signal on wire @tt{a}?}
@ -44,7 +44,7 @@ becomes:
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[<day7-setup>
@chunk[<day07-setup>
(require racket rackunit
(for-syntax racket/file racket/string))
(provide (all-defined-out))
@ -52,7 +52,7 @@ One gotcha when using syntax transformers is that identifiers introduced by a tr
(define-syntax (convert-input-to-wire-functions stx)
(syntax-case stx ()
[(_)
(let* ([input-strings (file->lines "day7-input.txt")]
(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)))]))
@ -84,7 +84,7 @@ We also need to implement our 16-bit math operations. As we saw above, our synta
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[<day7-ops>
@chunk[<day07-ops>
(define (16bitize x)
(define 16bit-max (expt 2 16))
(define r (modulo x 16bit-max))
@ -103,7 +103,7 @@ These next definitions use @racket[define-syntax-rule] as a shortcut, which is a
After that, we just evaluate wire function @racket[a] to get our answer.
@chunk[<day7-q1>
@chunk[<day07-q1>
(define (q1) (a))]
@ -116,7 +116,7 @@ Ordinarily, as a safety measure, Racket won't let you redefine functions. But we
@chunk[<day7-q2>
@chunk[<day07-q2>
(compile-enforce-module-constants #f)
(define (q2)
@ -129,7 +129,7 @@ Ordinarily, as a safety measure, Racket won't let you redefine functions. But we
@section{Testing Day 7}
@chunk[<day7-test>
@chunk[<day07-test>
(module+ test
(check-equal? (q1) 46065)
(check-equal? (q2) 14134))]

@ -3,15 +3,15 @@
@aoc-title[8]
@defmodule[aoc-racket/day8]
@defmodule[aoc-racket/day08]
@link["http://adventofcode.com/day/8"]{The puzzle}. Our @link-rp["day8-input.txt"]{input} consists of a list of seemingly random strings within quotation marks.
@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[<day8>
<day8-setup>
<day8-q1>
<day8-q2>
<day8-test>]
@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?}
@ -19,12 +19,12 @@ The puzzle relies the fact that within strings, certain single characters — l
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[<day8-setup>
@chunk[<day08-setup>
(require racket rackunit)
(provide (all-defined-out))
]
@chunk[<day8-q1>
@chunk[<day08-q1>
(define (memory-length str) (string-length (read (open-input-string str))))
(define (q1 strs)
@ -39,7 +39,7 @@ This question simply comes down to — do you know how to use the string-format
In Racket, a string can be re-encoded with @racket[~v]. Not a very puzzling puzzle overall.
@chunk[<day8-q2>
@chunk[<day08-q2>
(define (encoded-length str) (string-length (~v str)))
(define (q2 strs)
@ -48,9 +48,9 @@ In Racket, a string can be re-encoded with @racket[~v]. Not a very puzzling puzz
@section{Testing Day 8}
@chunk[<day8-test>
@chunk[<day08-test>
(module+ test
(define input-strs (file->lines "day8-input.txt"))
(define input-strs (file->lines "day08-input.txt"))
(check-equal? (q1 input-strs) 1333)
(check-equal? (q2 input-strs) 2046))]

@ -3,15 +3,15 @@
@aoc-title[9]
@defmodule[aoc-racket/day9]
@defmodule[aoc-racket/day09]
@link["http://adventofcode.com/day/9"]{The puzzle}. Our @link-rp["day9-input.txt"]{input} consists of a list of distances between fictional cities, e.g., @italic{AlphaCentauri to Straylight = 133}.
@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[<day9>
<day9-setup>
<day9-q1>
<day9-q2>
<day9-test>]
@chunk[<day09>
<day09-setup>
<day09-q1>
<day09-q2>
<day09-test>]
@section{What's the shortest route that visits all the cities?}
@ -23,7 +23,7 @@ In the second part, we'll loop through every possible path between the cities wi
@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[<day9-setup>
@chunk[<day09-setup>
(require racket rackunit)
(provide (all-defined-out))
@ -50,7 +50,7 @@ In the second part, we'll loop through every possible path between the cities wi
(apply distance pair))))
]
@chunk[<day9-q1>
@chunk[<day09-q1>
(define (q1 strs)
@ -63,7 +63,7 @@ In the second part, we'll loop through every possible path between the cities wi
Exactly the same, except we look for the @racket[max] value among the distances rather than the @racket[min].
@chunk[<day9-q2>
@chunk[<day09-q2>
(define (q2 strs)
(apply max (calculate-route-distances))) ]
@ -71,9 +71,9 @@ Exactly the same, except we look for the @racket[max] value among the distances
@section{Testing Day 9}
@chunk[<day9-test>
@chunk[<day09-test>
(module+ test
(define input-strs (file->lines "day9-input.txt"))
(define input-strs (file->lines "day09-input.txt"))
(check-equal? (q1 input-strs) 251)
(check-equal? (q2 input-strs) 898))]