days 1-25

Matthew Butterick 9 years ago
parent cbc781337e
commit 737054da61

.gitignore vendored

@ -0,0 +1,21 @@
# for Racket
# for Mac OS X
# Thumbnails
# Files that might appear on external disk
# generated documentation

@ -1,2 +1,9 @@
# aoc-racket
Racket solutions & explanations for the Advent of Code puzzles
Racket solutions & explanations for the [Advent of Code]( puzzles. Written in Racket's literate-programming dialect, `scribble/lp2`.
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.](

@ -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" "")]
@italic{Dedicated to curious characters everywhere, especially those learning Racket.}
@link[""]{Advent of Code} is a series of programming puzzles designed by @link[""]{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[""]{pay him for it}.
You can install this package (if you haven't already) with
@tt{raco pkg install aoc-racket}
@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)
@link[""]{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.
@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.
(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)))]
(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.
(define (elevator-string->ints str)
(for/list ([c (in-string str)])
(if (equal? c up-char)
(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.}
(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].
(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))))
(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))))
@section{Testing Day 1}
(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)
@link[""]{The puzzle}. Our @link-rp["day02-input.txt"]{input} is a list of strings that represent dimensions of rectangular boxes.
@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.
(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"))))]
(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.
(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}
(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)
@link[""]{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}.
@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.
(require racket rackunit)
(provide (all-defined-out))
(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.
(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}
(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 @@

@ -0,0 +1,54 @@
#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@(require (for-label openssl/md5))
@link[""]{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.
@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}.)
(require racket rackunit openssl/md5)
(provide (all-defined-out))
(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.
(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}
(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)
@link[""]{The puzzle}. Our @link-rp["day05-input.txt"]{input} is a list of random-looking but not really random text strings.
@section{How many strings are ``nice''?}
A string is ``nice'' if it meets certain criteria:
@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]).
(require racket rackunit)
(provide (all-defined-out))
(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:
@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.
(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}
(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)
@link[""]{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.
@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.
(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.
(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))
@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'':
@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.
(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.
(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}
(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)
@link[""]{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.
@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)]
@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].
(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)]))
(define wire-value-cache (make-hash))
(define (evaluate-arg x)
[(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[""]{Jay McCarthy} for the 16-bit operations.)
(define (16bitize x)
(define 16bit-max (expt 2 16))
(define r (modulo x 16bit-max))
[(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.
(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.
(compile-enforce-module-constants #f)
(define (q2)
(define first-a-val (a))
(set! b (thunk* first-a-val))
(set! wire-value-cache (make-hash))
@section{Testing Day 7}
(module+ test
(check-equal? (q1) 46065)
(check-equal? (q2) 14134))]

@ -0,0 +1,300 @@