add index with per-function entries

pull/2/head
Matthew Butterick 9 years ago
parent 737054da61
commit 1e2dfbf2fe

@ -1,5 +1,5 @@
#lang scribble/manual #lang scribble/manual
@(require (for-label racket rackunit sugar/list)) @(require (for-label racket rackunit sugar/list) aoc-racket/helper)
@title{Advent of Code: solutions & explanations} @title{Advent of Code: solutions & explanations}
@ -46,3 +46,6 @@ You can install this package (if you haven't already) with
@include-section[(submod "day23.rkt" doc)] @include-section[(submod "day23.rkt" doc)]
@include-section[(submod "day24.rkt" doc)] @include-section[(submod "day24.rkt" doc)]
@include-section[(submod "day25.rkt" doc)] @include-section[(submod "day25.rkt" doc)]
@index-section[]

@ -16,11 +16,11 @@
<day01-test>] <day01-test>]
@section{Where does the elevator land?} @isection{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. 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. @iracket[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> @chunk[<day01-setup>
(require racket rackunit) (require racket rackunit)
@ -40,7 +40,7 @@ The building has an indefinite number of floors in both directions. So the ultim
(define (q1 str) (define (q1 str)
(get-destination str))] (get-destination str))]
@subsection{Alternate approach: numerical conversion} @isubsection{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.
@ -54,11 +54,11 @@ Rather than counting matches with @racket[regexp-match*], we could also convert
(define (q1-alt str) (define (q1-alt str)
(apply + (elevator-string->ints str)))] (apply + (elevator-string->ints str)))]
@section[#:tag "q2"]{At what point does the elevator enter the basement?} @isection[#: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.
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. We could characterize this as a problem of tracking @italic{cumulative values} or @italic{state}. Either way, @iracket[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.} @margin-note{Nothing wrong with @racket[foldl] and @racket[foldr], but @racket[for/fold] is more flexible, and makes more readable code.}
@ -76,9 +76,9 @@ We could characterize this as a problem of tracking @italic{cumulative values} o
(length relative-movements))] (length relative-movements))]
@subsection{Alternate approaches: @tt{for/first} or @tt{for/or}} @isubsection{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 @iracket[for/first] or @iracket[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]. 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].

@ -13,13 +13,13 @@
<day02-test>] <day02-test>]
@section{How much paper is needed to wrap the boxes?} @isection{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. 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.) 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. Then we have a traditional setup for the devastating one-two punch of @iracket[map] and @iracket[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> @chunk[<day02-setup>

@ -17,11 +17,11 @@ In essence, this a two-dimensional version of the elevator problem in @secref{Da
<day03-q2> <day03-q2>
<day03-test>] <day03-test>]
@section{How many grid cells are visited?} @isection{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. 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.) 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 @iracket[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. Once the whole cell path is computed, the answer is found by removing duplicate cells and counting how many remain.

@ -14,11 +14,11 @@
<day04-q2> <day04-q2>
<day04-test>] <day04-test>]
@section{What is the lowest-numbered MD5 hash starting with five zeroes?} @isection{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 @iracket[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> @chunk[<day04-setup>

@ -13,7 +13,7 @@
<day05-q2> <day05-q2>
<day05-test>] <day05-test>]
@section{How many strings are ``nice''?} @isection{How many strings are ``nice''?}
A string is ``nice'' if it meets certain criteria: A string is ``nice'' if it meets certain criteria:
@ -23,7 +23,7 @@ A string is ``nice'' if it meets certain criteria:
@item{Does not contain @litchar{ab}, @litchar{cd}, @litchar{pq}, or @litchar{xy}.} @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]). This is a job for @iracket[regexp-match]. There's nothing tricky here (except for remembering that certain matching functions require the @iracket[pregexp] pattern prefix rather than @racket[regexp]).
@chunk[<day05-setup> @chunk[<day05-setup>

@ -14,11 +14,11 @@
<day06-refactored> <day06-refactored>
<day06-test>] <day06-test>]
@section{How many lights are lit after following the instructions?} @isection{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. 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]. 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 @iracket[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. 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.

@ -14,11 +14,11 @@
<day07-q2> <day07-q2>
<day07-test>] <day07-test>]
@section{What's the signal on wire @tt{a}?} @isection{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]. 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. In other languages, creating functions from text strings would be a difficult trick. But this facility is built into Racket with @iracket[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: 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:
@ -42,7 +42,7 @@ becomes:
(@racket[wire-value-cache] is just a performance enhancement, so that wire values don't have to be computed multiple times.) (@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]. 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 @iracket[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 @iracket[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 @iracket[cond].
@chunk[<day07-setup> @chunk[<day07-setup>
(require racket rackunit (require racket rackunit
@ -108,11 +108,11 @@ After that, we just evaluate wire function @racket[a] to get our answer.
@section{What's the signal on wire @tt{a} if wire @tt{b} is overridden with @tt{a}'s original value?} @isection{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. 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. Ordinarily, as a safety measure, Racket won't let you redefine functions. But we can circumvent this limitation by setting @iracket[compile-enforce-module-constants] to @racket[#f]. We'll also need to reset our cache, since this change will affect the other wires too.

@ -13,11 +13,11 @@
<day08-q2> <day08-q2>
<day08-test>] <day08-test>]
@section{What's the difference between the literal length of the strings, and their length in memory?} @isection{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 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]. The literal length of the string is trivial  use @iracket[string-length]. The memory length requires interpreting a string as a Racket value, which (as seen in @secref{Day_7}) simply means using @iracket[read].
@chunk[<day08-setup> @chunk[<day08-setup>
(require racket rackunit) (require racket rackunit)
@ -32,11 +32,11 @@ The literal length of the string is trivial — use @racket[string-length]. The
@section{What's the difference between the re-encoded length of the literal string, and the original length?} @isection{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? 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. In Racket, a string can be re-encoded with @iracket[~v]. Not a very puzzling puzzle overall.
@chunk[<day08-q2> @chunk[<day08-q2>

@ -13,13 +13,13 @@
<day09-q2> <day09-q2>
<day09-test>] <day09-test>]
@section{What's the shortest route that visits all the cities?} @isection{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. 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. 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]. In the second part, we'll loop through every possible path between the cities with @iracket[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 @iracket[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.} @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.}
@ -59,9 +59,9 @@ In the second part, we'll loop through every possible path between the cities wi
@section{What's the longest route?} @isection{What's the longest route?}
Exactly the same, except we look for the @racket[max] value among the distances rather than the @racket[min]. Exactly the same, except we look for the @iracket[max] value among the distances rather than the @racket[min].
@chunk[<day09-q2> @chunk[<day09-q2>

@ -13,11 +13,11 @@
<day10-q2> <day10-q2>
<day10-test>] <day10-test>]
@section{What's the length of the sequence after 40 iterations?} @isection{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. 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. As in @secref{Day_1}, this puzzle relies on cumulative state, so we'll loop using @iracket[for/fold]. To generate the new string for each pass of the loop, we'll use @iracket[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. 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.

@ -13,7 +13,7 @@
<day11-q2> <day11-q2>
<day11-test>] <day11-test>]
@section{What's the next password that meets the criteria?} @isection{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. 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.
@ -27,7 +27,7 @@ Furthermore, like @secref{Day_5}, the puzzle provides certain criteria that must
@item{The password must contain two different, non-overlapping pairs of letters.} @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. As in @secref{Day_5}, we'll use @iracket[regexp-match] to implement tests for these conditions. We'll also use @iracket[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[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*)$"].

@ -13,13 +13,13 @@
<day12-q2> <day12-q2>
<day12-test>] <day12-test>]
@section{What's the sum of all the numbers in the document?} @isection{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. 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. 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. To parse the file we'll use the @iracket[read-json] function from Racket's @racketmodname[json] library. This function converts the JSON into a JS-expression (see @iracket[jsexpr?]), which is a recursively nested data structure. If we had a simple recursively nested list, we could just @iracket[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. 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.

@ -13,13 +13,13 @@
<day13-q2> <day13-q2>
<day13-test>] <day13-test>]
@section{What's the optimal happiness score for a seating arrangement of eight?} @isection{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.) 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. 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. 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 @iracket[in-permutations] and see what the best score is.
@ -50,9 +50,9 @@ Those wrinkles noted, we'll proceed as we did in @secref{Day_9}. We'll parse the
] ]
@subsection{Optimizing @racket[in-permutations]} @isubsection{Optimizing @tt{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. I'm in a math-jock mood, so let's make a performance optimization. It's unnecessary for this problem, but when we use @iracket[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. 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.

@ -13,11 +13,11 @@
<day14-q2> <day14-q2>
<day14-test>] <day14-test>]
@section{After 2503 seconds, what is the maximum distance any reindeer has flown?} @isection{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. 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. As in @secref{Day_7}, we'll use @iracket[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 @iracket[map] the time over the reindeer and @iracket[max] to get the answer.
@ -63,11 +63,11 @@ As in @secref{Day_7}, we'll use @racket[define-syntax] to set up the reindeer fu
@section{Under the new rule, how many points does the winning reindeer have?} @isection{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. 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.) 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 @iracket[frequency-hash] from @racketmodname[sugar/list]. (You could also do it with @iracket[for/fold], but managing nine reindeer in parallel is unwieldy. Just ask Santa.)
@chunk[<day14-q2> @chunk[<day14-q2>

@ -13,7 +13,7 @@
<day15-q2> <day15-q2>
<day15-test>] <day15-test>]
@section{What's the best cookie we can make with 100 tsps of ingredients?} @isection{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. 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.
@ -67,9 +67,9 @@ Having surveyed the territory, making ingredient functions from the text descrip
@section{What's the best cookie we can make with 100 tsps that's exactly 500 calories?} @isection{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. 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 @iracket[last] to retrieve them.) Obviously, these two answers could be combined with simple refactoring.
@chunk[<day15-q2> @chunk[<day15-q2>
@ -97,9 +97,6 @@ Same as the first question, but we'll add a @racket[#:when] clause to our recipe
@chunk[<day15-refactored>
]
@section{Testing Day 15} @section{Testing Day 15}

@ -61,7 +61,7 @@ We might be tempted to break down the attribute pairs into hash tables. But we d
@section{Which Sue matches the attribute input, with the ``retroencabulator'' rules?} @isection{Which Sue matches the attribute input, with the ``retroencabulator'' rules?}
Same question as before, with new rules for matching the master attributes: Same question as before, with new rules for matching the master attributes:
@ -74,9 +74,9 @@ Same question as before, with new rules for matching the master attributes:
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. 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.'') 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 @iracket[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]. Plus, it's always fun to find a use for @iracket[case] and the frequently overlooked @iracket[assoc].
@chunk[<day16-q2> @chunk[<day16-q2>

@ -14,7 +14,7 @@
<day18-q2> <day18-q2>
<day18-test>] <day18-test>]
@section{How many lights are on after 100 iterations of the light-switching rules?} @isection{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: There are two rules for incrementing the state of the lighting grid:
@ -31,7 +31,7 @@ The heavy lifting is in the @racket[iterate-grid] function, which steps through
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. 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. 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 @iracket[for/fold] and counting the activated bulbs in the resulting grid.
@chunk[<day18-setup> @chunk[<day18-setup>
(require racket rackunit) (require racket rackunit)

@ -14,11 +14,11 @@
<day19-q2> <day19-q2>
<day19-test>] <day19-test>]
@section{How many distinct molecules can be created after one transformation?} @isection{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. 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. 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 @iracket[regexp-replace*] (because it replaces all matches). Instead we'll use @iracket[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 @iracket[remove-duplicates] and see how many are left.
@chunk[<day19-setup> @chunk[<day19-setup>
@ -54,13 +54,13 @@ Each molecule transformation defines a string replacement. We'll parse our input
@section{What's the fewest number of transformations that will generate the test module?} @isection{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. 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. 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. 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 @iracket[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> @chunk[<day19-q2>

@ -14,11 +14,11 @@
<day20-q2> <day20-q2>
<day20-test>] <day20-test>]
@section{What's the first house that gets the target number of presents?} @isection{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. 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.) 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 @iracket[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> @chunk[<day20-setup>
(require racket rackunit (only-in math divisors)) (require racket rackunit (only-in math divisors))
@ -39,9 +39,9 @@ Math jocks will notice that the elf behavior roughly describes a @link["https://
@section{What's the first house that gets the target number of presents, if each elf delivers 11 gifts to 50 houses?} @isection{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. 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 @iracket[filter] the list of elves using this condition.
@chunk[<day20-q2> @chunk[<day20-q2>

@ -15,7 +15,7 @@
<day21-q2> <day21-q2>
<day21-test>] <day21-test>]
@section{What's the least we can spend and win?} @isection{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. 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.
@ -51,7 +51,7 @@ We have to buy a weapon. We can buy armor, or not. And we can buy zero, one, or
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. 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. 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 @iracket[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> @chunk[<day21-setup>

@ -14,7 +14,7 @@
<day23-q2> <day23-q2>
<day23-test>] <day23-test>]
@section{What's the value in register @tt{b} after the program runs?} @isection{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: The virtual machine has two registers, @tt{a} and @tt{b}, that both start at 0. It also has six instructions:
@ -32,7 +32,7 @@ Although the virtual machine has the equivalent of functions & variables, the ju
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}. 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). Notice also that we're encasing the lines of the VM program in @iracket[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> @chunk[<day23-setup>
(require racket rackunit (require racket rackunit

@ -14,7 +14,7 @@
<day24-q2> <day24-q2>
<day24-test>] <day24-test>]
@section{What's the score of the optimal group of packages, when divided into three groups?} @isection{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. 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.
@ -22,7 +22,7 @@ This puzzle brings together elements we've seen before. Each group in a valid ar
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. 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). To be more efficient, we'll use @iracket[for*/first] to alternate between generating lists of possible groups, and then testing them. We'll @iracket[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.) 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.)

@ -1,10 +1,40 @@
#lang at-exp racket/base #lang at-exp racket/base
(require scribble/manual scribble/html-properties (require scribble/manual scribble/html-properties
scribble/core) scribble/core scribble/decode)
(require (for-syntax racket/base racket/syntax))
(provide (all-defined-out)) (provide (all-defined-out))
(define current-day (make-parameter #f))
(define current-section (make-parameter #f))
(define-syntax-rule (iracket term)
(list
(part-index-decl
(list (symbol->string 'term))
(list (tt (symbol->string 'term))
(if (current-section)
(decode-content (cons (format "in Day ~a / " (current-day)) (current-section)))
"")))
(racket term)))
(define-syntax (define-isec stx)
(syntax-case stx ()
[(_ secid)
(with-syntax ([isecid (format-id stx "i~a" #'secid)])
#'(define isecid
(make-keyword-procedure
(λ (kws kwargs . args)
(begin
(current-section args)
(keyword-apply secid kws kwargs args))))))]))
(define-isec section)
(define-isec subsection)
(define (aoc-title which) (define (aoc-title which)
(define which-str (number->string which)) (define which-str (number->string which))
(current-day which-str)
(define day-x (format "day-~a" which-str)) (define day-x (format "day-~a" which-str))
(define day-prefix (format "~a-" day-x)) (define day-prefix (format "~a-" day-x))
@title[#:style manual-doc-style]{Day @which-str}) @title[#:style manual-doc-style]{Day @which-str})