day12 and day13

master-blaster
Matthew Butterick 9 years ago
parent 755017a06c
commit e6c0c384f1

@ -20,3 +20,5 @@
@include-section[(submod "day9.scrbl" doc)] @include-section[(submod "day9.scrbl" doc)]
@include-section[(submod "day10.scrbl" doc)] @include-section[(submod "day10.scrbl" doc)]
@include-section[(submod "day11.scrbl" doc)] @include-section[(submod "day11.scrbl" doc)]
@include-section[(submod "day12.scrbl" doc)]
@include-section[(submod "day13.scrbl" doc)]

@ -3,7 +3,7 @@
@aoc-title[11] @aoc-title[11]
@link["http://adventofcode.com/day/1q"]{The puzzle}. Our @link-rp["day11-input.txt"]{input} is a short alphabetic key that represents a password. @link["http://adventofcode.com/day/11"]{The puzzle}. Our @link-rp["day11-input.txt"]{input} is a short alphabetic key that represents a password.
@chunk[<day11> @chunk[<day11>
<day11-setup> <day11-setup>

File diff suppressed because one or more lines are too long

@ -0,0 +1,83 @@
#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@aoc-title[12]
@link["http://adventofcode.com/day/12"]{The puzzle}. Our @link-rp["day12-input.txt"]{input} is, unfortunately, a @link["http://json.org/"]{JSON} file.
@chunk[<day12>
<day12-setup>
<day12-q1>
<day12-q2>
<day12-test>]
@section{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.
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.
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.
@chunk[<day12-setup>
(require racket rackunit json)
(define (string->jsexpr str)
(read-json (open-input-string str)))
]
@chunk[<day12-q1>
(define (flatten-jsexpr jsexpr)
(flatten
(let loop ([x jsexpr])
(cond
[(list? x)
(map loop x)]
[(hash? x)
(loop (flatten (hash->list x)))]
[else x]))))
(define (q1 input-str)
(define json-items (flatten-jsexpr (string->jsexpr input-str)))
(apply + (filter number? json-items)))]
@section{What's the sum of all the numbers, if hash tables with value @racket{red} are ignored?}
We'll just update our flattening function to skip over hash tables that have @racket{red} among the values.
@chunk[<day12-q2>
(define (flatten-jsexpr-2 jsexpr)
(flatten
(let loop ([x jsexpr])
(cond
[(list? x)
(map loop x)]
[(hash? x)
(if (member "red" (hash-values x))
empty
(loop (flatten (hash->list x))))]
[else x]))))
(define (q2 input-str)
(define json-items (flatten-jsexpr-2 (string->jsexpr input-str)))
(apply + (filter number? json-items))) ]
@section{Testing Day 12}
@chunk[<day12-test>
(module+ test
(define input-str (file->string "day12-input.txt"))
(check-equal? (q1 input-str) 191164)
(check-equal? (q2 input-str) 87842))]

@ -0,0 +1,56 @@
Alice would gain 54 happiness units by sitting next to Bob.
Alice would lose 81 happiness units by sitting next to Carol.
Alice would lose 42 happiness units by sitting next to David.
Alice would gain 89 happiness units by sitting next to Eric.
Alice would lose 89 happiness units by sitting next to Frank.
Alice would gain 97 happiness units by sitting next to George.
Alice would lose 94 happiness units by sitting next to Mallory.
Bob would gain 3 happiness units by sitting next to Alice.
Bob would lose 70 happiness units by sitting next to Carol.
Bob would lose 31 happiness units by sitting next to David.
Bob would gain 72 happiness units by sitting next to Eric.
Bob would lose 25 happiness units by sitting next to Frank.
Bob would lose 95 happiness units by sitting next to George.
Bob would gain 11 happiness units by sitting next to Mallory.
Carol would lose 83 happiness units by sitting next to Alice.
Carol would gain 8 happiness units by sitting next to Bob.
Carol would gain 35 happiness units by sitting next to David.
Carol would gain 10 happiness units by sitting next to Eric.
Carol would gain 61 happiness units by sitting next to Frank.
Carol would gain 10 happiness units by sitting next to George.
Carol would gain 29 happiness units by sitting next to Mallory.
David would gain 67 happiness units by sitting next to Alice.
David would gain 25 happiness units by sitting next to Bob.
David would gain 48 happiness units by sitting next to Carol.
David would lose 65 happiness units by sitting next to Eric.
David would gain 8 happiness units by sitting next to Frank.
David would gain 84 happiness units by sitting next to George.
David would gain 9 happiness units by sitting next to Mallory.
Eric would lose 51 happiness units by sitting next to Alice.
Eric would lose 39 happiness units by sitting next to Bob.
Eric would gain 84 happiness units by sitting next to Carol.
Eric would lose 98 happiness units by sitting next to David.
Eric would lose 20 happiness units by sitting next to Frank.
Eric would lose 6 happiness units by sitting next to George.
Eric would gain 60 happiness units by sitting next to Mallory.
Frank would gain 51 happiness units by sitting next to Alice.
Frank would gain 79 happiness units by sitting next to Bob.
Frank would gain 88 happiness units by sitting next to Carol.
Frank would gain 33 happiness units by sitting next to David.
Frank would gain 43 happiness units by sitting next to Eric.
Frank would gain 77 happiness units by sitting next to George.
Frank would lose 3 happiness units by sitting next to Mallory.
George would lose 14 happiness units by sitting next to Alice.
George would lose 12 happiness units by sitting next to Bob.
George would lose 52 happiness units by sitting next to Carol.
George would gain 14 happiness units by sitting next to David.
George would lose 62 happiness units by sitting next to Eric.
George would lose 18 happiness units by sitting next to Frank.
George would lose 17 happiness units by sitting next to Mallory.
Mallory would lose 36 happiness units by sitting next to Alice.
Mallory would gain 76 happiness units by sitting next to Bob.
Mallory would lose 34 happiness units by sitting next to Carol.
Mallory would gain 37 happiness units by sitting next to David.
Mallory would gain 40 happiness units by sitting next to Eric.
Mallory would gain 18 happiness units by sitting next to Frank.
Mallory would gain 7 happiness units by sitting next to George.

@ -0,0 +1,96 @@
#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@aoc-title[13]
@link["http://adventofcode.com/day/13"]{The puzzle}. Our @link-rp["day13-input.txt"]{input} is a list of descriptions of ``happiness units'' that would be gained or lost among eight people sitting next to each other at a dinner table.
@chunk[<day13>
<day13-setup>
<day13-q1>
<day13-q2>
<day13-test>]
@section{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.)
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 pair 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. Then we'll loop through all possible seating arrangements with @racket[in-permutations] and see what the best score is.
@margin-note{Math jocks might note that because our seating arrangement is circular, our permutations will include a lot of ``rotationally equivalent'' arrangements — e.g., @racket['(A B C D)] is the same as @racket['(B C D A)] and @racket['(C D A B)]. If we had more people, or the happiness function were more expensive, we might want to prune out these equivalent arrangements as a performance optimization. But in this case, it's unnecessary.}
@chunk[<day13-setup>
(require racket rackunit)
(define happiness-scores (make-hash))
(define (parse-happiness-score ln)
(define result
(regexp-match #px"^(.*?) would (gain|lose) (\\d+) happiness units by sitting next to (.*?)\\.$" (string-downcase ln)))
(when result
(match-define (list _ name1 op amount name2) result)
(hash-set! happiness-scores (list name1 name2)
((if (equal? op "gain") + -) (string->number amount)))))
(define (calculate-happiness table-arrangement)
(define table-arrangement-rotated-one-place
(append (cdr table-arrangement) (list (car table-arrangement))))
(define clockwise-pairs
(map list table-arrangement table-arrangement-rotated-one-place))
(define counterclockwise-pairs (map reverse clockwise-pairs))
(define all-pairs (append clockwise-pairs counterclockwise-pairs))
(for/sum ([pair (in-list all-pairs)])
(hash-ref happiness-scores pair)))
]
@chunk[<day13-q1>
(define (q1 input-str)
(for-each parse-happiness-score (string-split input-str "\n"))
(define names
(remove-duplicates (flatten (hash-keys happiness-scores))))
(define table-arrangement-scores
(for/list ([table-arrangement (in-permutations names)])
(calculate-happiness table-arrangement)))
(apply max table-arrangement-scores))]
@section{What's the optimal happiness score, including ourself in the seating?}
We can reuse our hash table of @racket[happiness-scores], but we have to update it with scores for ourself seated next to every other person, which in every case is @racket[0]. Then we find the optimal score the same way.
@chunk[<day13-q2>
(define (q2 input-str)
(define names
(remove-duplicates (flatten (hash-keys happiness-scores))))
(for ([name (in-list names)])
(hash-set*! happiness-scores
(list "me" name) 0
(list name "me") 0))
(define names-with-me (cons "me" names))
(define table-arrangement-scores
(for/list ([table-arrangement (in-permutations names-with-me)])
(calculate-happiness table-arrangement)))
(apply max table-arrangement-scores))
]
@section{Testing Day 13}
@chunk[<day13-test>
(module+ test
(define input-str (file->string "day13-input.txt"))
(check-equal? (q1 input-str) 709)
(check-equal? (q2 input-str) 668))]