Skip to content
This repository has been archived by the owner on Jul 24, 2022. It is now read-only.

Commit

Permalink
add index with per-function entries
Browse files Browse the repository at this point in the history
  • Loading branch information
mbutterick committed Jan 20, 2016
1 parent 737054d commit 1e2dfbf
Show file tree
Hide file tree
Showing 24 changed files with 102 additions and 72 deletions.
7 changes: 5 additions & 2 deletions aoc-racket.scrbl
@@ -1,5 +1,5 @@
#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}

Expand Down Expand Up @@ -45,4 +45,7 @@ You can install this package (if you haven't already) with
@include-section[(submod "day22.rkt" doc)]
@include-section[(submod "day23.rkt" doc)]
@include-section[(submod "day24.rkt" doc)]
@include-section[(submod "day25.rkt" doc)]
@include-section[(submod "day25.rkt" doc)]


@index-section[]
14 changes: 7 additions & 7 deletions day01.rkt
Expand Up @@ -16,11 +16,11 @@
<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.

@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>
(require racket rackunit)
Expand All @@ -40,7 +40,7 @@ The building has an indefinite number of floors in both directions. So the ultim
(define (q1 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.

Expand All @@ -54,11 +54,11 @@ Rather than counting matches with @racket[regexp-match*], we could also convert
(define (q1-alt 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.

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.}

Expand All @@ -76,9 +76,9 @@ We could characterize this as a problem of tracking @italic{cumulative values} o

(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].

Expand Down
4 changes: 2 additions & 2 deletions day02.rkt
Expand Up @@ -13,13 +13,13 @@
<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.

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>
Expand Down
4 changes: 2 additions & 2 deletions day03.rkt
Expand Up @@ -17,11 +17,11 @@ In essence, this a two-dimensional version of the elevator problem in @secref{Da
<day03-q2>
<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.

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.

Expand Down
4 changes: 2 additions & 2 deletions day04.rkt
Expand Up @@ -14,11 +14,11 @@
<day04-q2>
<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.

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>
Expand Down
4 changes: 2 additions & 2 deletions day05.rkt
Expand Up @@ -13,7 +13,7 @@
<day05-q2>
<day05-test>]

@section{How many strings are ``nice''?}
@isection{How many strings are ``nice''?}

A string is ``nice'' if it meets certain criteria:

Expand All @@ -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}.}
]

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>
Expand Down
4 changes: 2 additions & 2 deletions day06.rkt
Expand Up @@ -14,11 +14,11 @@
<day06-refactored>
<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.

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.

Expand Down
10 changes: 5 additions & 5 deletions day07.rkt
Expand Up @@ -14,11 +14,11 @@
<day07-q2>
<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].

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:

Expand All @@ -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.)

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>
(require racket rackunit
Expand Down Expand Up @@ -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.

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.



Expand Down
8 changes: 4 additions & 4 deletions day08.rkt
Expand Up @@ -13,11 +13,11 @@
<day08-q2>
<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 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>
(require racket rackunit)
Expand All @@ -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?
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>
Expand Down
8 changes: 4 additions & 4 deletions day09.rkt
Expand Up @@ -13,13 +13,13 @@
<day09-q2>
<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.

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.}

Expand Down Expand Up @@ -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>

Expand Down

0 comments on commit 1e2dfbf

Please sign in to comment.