master-blaster
Matthew Butterick 9 years ago
parent e5bae451df
commit e74fee0bb5

@ -6,12 +6,12 @@
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. 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.
@chunk[<day1> @chunk[<day1>
<setup> <day1-setup>
<q1> <day1-q1>
<q1-alt> <day1-q1-alt>
<q2> <day1-q2>
<q2-alt> <day1-q2-alt>
<test>] <day1-test>]
@section{Where does the elevator land?} @section{Where does the elevator land?}
@ -20,7 +20,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. @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[<setup> @chunk[<day1-setup>
(require racket rackunit) (require racket rackunit)
(define up-char #\() (define up-char #\()
(define down-char #\)) (define down-char #\))
@ -33,7 +33,7 @@ The building has an indefinite number of floors in both directions. So the ultim
@chunk[<q1> @chunk[<day1-q1>
(define (q1 str) (define (q1 str)
(displayln (format "ups = ~a" (get-ups str))) (displayln (format "ups = ~a" (get-ups str)))
(displayln (format "downs = ~a" (get-downs str))) (displayln (format "downs = ~a" (get-downs str)))
@ -41,11 +41,11 @@ The building has an indefinite number of floors in both directions. So the ultim
(displayln (format "destination = ~a" destination)) (displayln (format "destination = ~a" destination))
destination)] destination)]
@subsection{Alternate approach} @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. Rather than counting matches with @racket[regexp-match*], we could also convert the string of parentheses directly into a list of numbers.
@chunk[<q1-alt> @chunk[<day1-q1-alt>
(define (elevator-string->ints str) (define (elevator-string->ints str)
(for/list ([c (in-string str)]) (for/list ([c (in-string str)])
(if (equal? c up-char) (if (equal? c up-char)
@ -57,7 +57,7 @@ Rather than counting matches with @racket[regexp-match*], we could also convert
(displayln (format "destination = ~a" destination)) (displayln (format "destination = ~a" destination))
destination)] destination)]
@section{At what point does the elevator enter the basement?} @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. 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.
@ -66,7 +66,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.} @margin-note{Nothing wrong with @racket[foldl] and @racket[foldr], but @racket[for/fold] is more flexible, and makes more readable code.}
@chunk[<q2> @chunk[<day1-q2>
(define (in-basement? movements) (define (in-basement? movements)
(negative? (apply + movements))) (negative? (apply + movements)))
@ -81,13 +81,13 @@ We could characterize this as a problem of tracking @italic{cumulative values} o
(displayln (format "basement entered at position = ~a" basement-position)) (displayln (format "basement entered at position = ~a" basement-position))
basement-position)] basement-position)]
@subsection{Alternate approaches} @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]. 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 probably will want to cache intermediate values, which means you might as well use @racket[for/fold]. The two are similar. The choice comes down to readability and efficiency — meaning, if each iteration of the loop is expensive, you probably will want to cache intermediate values, which means you might as well use @racket[for/fold].
@chunk[<q2-alt> @chunk[<day1-q2-alt>
(define (q2-for/first str) (define (q2-for/first str)
(define basement-position (define basement-position
(let ([ints (elevator-string->ints str)]) (let ([ints (elevator-string->ints str)])
@ -108,7 +108,7 @@ The two are similar. The choice comes down to readability and efficiency — me
@section{Testing our input} @section{Testing our input}
@chunk[<test> @chunk[<day1-test>
(module+ test (module+ test
(define input-str (file->string "day1-input.txt")) (define input-str (file->string "day1-input.txt"))
(check-equal? (q1 input-str) 74) (check-equal? (q1 input-str) 74)

@ -6,9 +6,9 @@
Our @link-rp["day2-input.txt"]{input} is a list of strings that represent dimensions of rectangular boxes. Our @link-rp["day2-input.txt"]{input} is a list of strings that represent dimensions of rectangular boxes.
@chunk[<day2> @chunk[<day2>
<setup> <day2-setup>
<q1> <day2-q1>
<test>] <day2-test>]
@section{How much paper is needed to wrap the boxes?} @section{How much paper is needed to wrap the boxes?}
@ -20,13 +20,13 @@ 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. 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[<setup> @chunk[<day2-setup>
(require racket rackunit) (require racket rackunit)
(define (string->boxes str) (define (string->boxes str)
(for/list ([ln (in-list (string-split str "\n"))]) (for/list ([ln (in-list (string-split str "\n"))])
(map string->number (string-split ln "x"))))] (map string->number (string-split ln "x"))))]
@chunk[<q1> @chunk[<day2-q1>
(define (box->paper box) (define (box->paper box)
(match-define (list x y z) box) (match-define (list x y z) box)
(define sides (list (* x y) (* y z) (* x z))) (define sides (list (* x y) (* y z) (* x z)))
@ -42,7 +42,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. We take the same approach, with a new @racket[box->ribbon] function.
@chunk[<q1> @chunk[<day2-q1>
(define (box->ribbon box) (define (box->ribbon box)
(match-define (list x y z) box) (match-define (list x y z) box)
(define (perimeter dim1 dim2) (* 2 (+ dim1 dim2))) (define (perimeter dim1 dim2) (* 2 (+ dim1 dim2)))
@ -58,7 +58,7 @@ We take the same approach, with a new @racket[box->ribbon] function.
@section{Testing our input} @section{Testing our input}
@chunk[<test> @chunk[<day2-test>
(module+ test (module+ test
(define input-str (file->string "day2-input.txt")) (define input-str (file->string "day2-input.txt"))
(check-equal? (q1 input-str) 1586300) (check-equal? (q1 input-str) 1586300)

@ -5,14 +5,14 @@
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. 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.
In essence, this a two-dimensional version of the elevator problem in @secref["day-1"]. In essence, this a two-dimensional version of the elevator problem in Day 1.
@chunk[<day3> @chunk[<day3>
<setup> <day3-setup>
<q1> <day3-q1>
<q1-complex> <day3-q1-complex>
<q2> <day3-q2>
<test>] <day3-test>]
@section{How many grid cells are visited?} @section{How many grid cells are visited?}
@ -22,11 +22,11 @@ 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. Once the whole cell path is computed, the answer is found by removing duplicate cells and counting how many remain.
@chunk[<setup> @chunk[<day3-setup>
(require racket rackunit) (require racket rackunit)
] ]
@chunk[<q1> @chunk[<day3-q1>
(define (string->cells str) (define (string->cells str)
(define start '(0 0)) (define start '(0 0))
(match-define (list east north west south) '((1 0) (0 1) (-1 0) (0 -1))) (match-define (list east north west south) '((1 0) (0 1) (-1 0) (0 -1)))
@ -45,13 +45,13 @@ Once the whole cell path is computed, the answer is found by removing duplicate
(define (q1 str) (define (q1 str)
(length (remove-duplicates (string->cells str))))] (length (remove-duplicates (string->cells str))))]
@subsection{Alternate approach} @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 by multiplying by @racket[+i].) 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 by multiplying by @racket[+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. 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[ <q1-complex> @chunk[ <day3-q1-complex>
(define (string->complex-cells str) (define (string->complex-cells str)
(define start 0) (define start 0)
(define east 1) (define east 1)
@ -77,7 +77,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. 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[<q2> @chunk[<day3-q2>
(define (split-odds-and-evens str) (define (split-odds-and-evens str)
(define-values (odd-chars even-chars) (define-values (odd-chars even-chars)
(for/fold ([odds-so-far empty][evens-so-far empty]) (for/fold ([odds-so-far empty][evens-so-far empty])
@ -96,7 +96,7 @@ The solution works the same as before — the only new task is to split the inpu
@section{Testing our input} @section{Testing our input}
@chunk[<test> @chunk[<day3-test>
(module+ test (module+ test
(define input-str (file->string "day3-input.txt")) (define input-str (file->string "day3-input.txt"))
(check-equal? (q1 input-str) 2565) (check-equal? (q1 input-str) 2565)

@ -7,23 +7,23 @@
Our @link-rp["day4-input.txt"]{input} is a string of eight characters that represents part of a key for making an MD5 hash. Our @link-rp["day4-input.txt"]{input} is a string of eight characters that represents part of a key for making an MD5 hash.
@chunk[<day4> @chunk[<day4>
<setup> <day4-setup>
<q1> <day4-q1>
<q2> <day4-q2>
<test>] <day4-test>]
@section{What is the lowest-numbered MD5 hash starting with five zeroes?} @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. 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"].) 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 Day 1.)
@chunk[<setup> @chunk[<day4-setup>
(require racket rackunit openssl/md5) (require racket rackunit openssl/md5)
] ]
@chunk[<q1> @chunk[<day4-q1>
(define (q1 str) (define (q1 str)
(for/or ([i (in-naturals)]) (for/or ([i (in-naturals)])
(define md5-key (string-append str (~a i))) (define md5-key (string-append str (~a i)))
@ -35,7 +35,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. 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[<q2> @chunk[<day4-q2>
(define (q2 str) (define (q2 str)
(for/or ([i (in-naturals)]) (for/or ([i (in-naturals)])
(define md5-key (string-append str (~a i))) (define md5-key (string-append str (~a i)))
@ -44,7 +44,7 @@ Exactly the same, except we test for a string of six zeroes. It is likely, howev
@section{Testing our input} @section{Testing our input}
@chunk[<test> @chunk[<day4-test>
(module+ test (module+ test
(define input-str (file->string "day4-input.txt")) (define input-str (file->string "day4-input.txt"))
(check-equal? (q1 input-str) 346386) (check-equal? (q1 input-str) 346386)

File diff suppressed because it is too large Load Diff

@ -0,0 +1,77 @@
#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@aoc-title[5]
Our @link-rp["day5-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>]
@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[<day5-setup>
(require racket rackunit)
]
@chunk[<day5-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[<day5-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 our input}
@chunk[<day5-test>
(module+ test
(define input-str (file->lines "day5-input.txt"))
(check-equal? (q1 input-str) 238)
(check-equal? (q2 input-str) 69))]

@ -6,7 +6,9 @@
(define (aoc-title which) (define (aoc-title which)
(define which-str (number->string which)) (define which-str (number->string which))
@title[#:style manual-doc-style #:tag (format "day-~a" which-str)]{@link[@string-append["http://adventofcode.com/day/" @which-str]]{Day @which-str}}) (define day-x (format "day-~a" which-str))
(define day-prefix (format "~a-" day-x))
@title[#:style manual-doc-style #:tag-prefix day-prefix]{@link[@string-append["http://adventofcode.com/day/" @which-str]]{Day @which-str}})
(define-syntax (link-rp stx) (define-syntax (link-rp stx)
(syntax-case stx () (syntax-case stx ()