From 9ca361dd6168e3984675b51edc2590813d2f5baa Mon Sep 17 00:00:00 2001 From: Matthew Butterick Date: Tue, 5 Jan 2016 11:21:34 -0800 Subject: [PATCH] day19 --- aoc-racket.scrbl | 3 +- day19-input.txt | 45 +++++++++++++++++++++++ day19.rkt | 95 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 day19-input.txt create mode 100644 day19.rkt diff --git a/aoc-racket.scrbl b/aoc-racket.scrbl index 172f147..3f040d3 100644 --- a/aoc-racket.scrbl +++ b/aoc-racket.scrbl @@ -38,4 +38,5 @@ You can install this package (if you haven't already) with @include-section[(submod "day15.rkt" doc)] @include-section[(submod "day16.rkt" doc)] @include-section[(submod "day17.rkt" doc)] -@include-section[(submod "day18.rkt" doc)] \ No newline at end of file +@include-section[(submod "day18.rkt" doc)] +@include-section[(submod "day19.rkt" doc)] \ No newline at end of file diff --git a/day19-input.txt b/day19-input.txt new file mode 100644 index 0000000..2714445 --- /dev/null +++ b/day19-input.txt @@ -0,0 +1,45 @@ +Al => ThF +Al => ThRnFAr +B => BCa +B => TiB +B => TiRnFAr +Ca => CaCa +Ca => PB +Ca => PRnFAr +Ca => SiRnFYFAr +Ca => SiRnMgAr +Ca => SiTh +F => CaF +F => PMg +F => SiAl +H => CRnAlAr +H => CRnFYFYFAr +H => CRnFYMgAr +H => CRnMgYFAr +H => HCa +H => NRnFYFAr +H => NRnMgAr +H => NTh +H => OB +H => ORnFAr +Mg => BF +Mg => TiMg +N => CRnFAr +N => HSi +O => CRnFYFAr +O => CRnMgAr +O => HP +O => NRnFAr +O => OTi +P => CaP +P => PTi +P => SiRnFAr +Si => CaSi +Th => ThCa +Ti => BP +Ti => TiTi +e => HF +e => NAl +e => OMg + +ORnPBPMgArCaCaCaSiThCaCaSiThCaCaPBSiRnFArRnFArCaCaSiThCaCaSiThCaCaCaCaCaCaSiRnFYFArSiRnMgArCaSiRnPTiTiBFYPBFArSiRnCaSiRnTiRnFArSiAlArPTiBPTiRnCaSiAlArCaPTiTiBPMgYFArPTiRnFArSiRnCaCaFArRnCaFArCaSiRnSiRnMgArFYCaSiRnMgArCaCaSiThPRnFArPBCaSiRnMgArCaCaSiThCaSiRnTiMgArFArSiThSiThCaCaSiRnMgArCaCaSiRnFArTiBPTiRnCaSiAlArCaPTiRnFArPBPBCaCaSiThCaPBSiThPRnFArSiThCaSiThCaSiThCaPTiBSiRnFYFArCaCaPRnFArPBCaCaPBSiRnTiRnFArCaPRnFArSiRnCaCaCaSiThCaRnCaFArYCaSiRnFArBCaCaCaSiThFArPBFArCaSiRnFArRnCaCaCaFArSiRnFArTiRnPMgArF \ No newline at end of file diff --git a/day19.rkt b/day19.rkt new file mode 100644 index 0000000..c2c9b48 --- /dev/null +++ b/day19.rkt @@ -0,0 +1,95 @@ +#lang scribble/lp2 +@(require scribble/manual aoc-racket/helper) + +@aoc-title[19] + +@defmodule[aoc-racket/day19] + +@link["http://adventofcode.com/day/19"]{The puzzle}. Our @link-rp["day19-input.txt"]{input} is list of ``molecule'' transformations that look like @tt{B => TiRnFAr}, and then a longer test molecule. + + +@chunk[ + + + + ] + +@section{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. + +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. + + +@chunk[ + (require racket rackunit) + (provide (all-defined-out)) + + (define (parse-input-str input-str) + (match-define (cons molecule transformation-strings) + (reverse (string-split input-str "\n"))) + (define transformations + (filter-not empty? + (map (curryr string-split " => ") transformation-strings))) + (values molecule transformations)) + + + ] + +@chunk[ + (define (transform-molecule* molecule target-atom replacement-atom) + (for/list ([pos (in-list (regexp-match-positions* (regexp target-atom) molecule))]) + (match-define (cons start finish) pos) + (string-append (substring molecule 0 start) + replacement-atom + (substring molecule finish (string-length molecule))))) + + (define (q1 input-str) + (define-values (molecule transformations) (parse-input-str input-str)) + (length + (remove-duplicates + (append-map (λ(target replacement) + (transform-molecule* molecule target replacement)) + (map first transformations) (map second transformations)))))] + + + +@section{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. + +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. + +@chunk[ + + (define (q2 input-str) + (define-values (starting-molecule xforms) (parse-input-str input-str)) + (let loop ([current-mol starting-molecule][transform-count 0] + [shuffles 0][xforms xforms]) + (cond + [(equal? current-mol "e") transform-count] + [else + (define-values (xformed-mol last-count) + (for/fold ([mol current-mol][count-so-far transform-count]) + ([(from to) (in-parallel (map first xforms) (map second xforms))]) + (values (string-replace mol to from) + (+ count-so-far (length (regexp-match* to mol)))))) + (if (not (equal? current-mol xformed-mol)) + (loop xformed-mol last-count shuffles xforms) + (loop starting-molecule 0 (add1 shuffles) (shuffle xforms)))]))) + + ] + + + +@section{Testing Day 18} + +@chunk[ + (module+ test + (define input-str (file->string "day19-input.txt")) + (check-equal? (q1 input-str) 576) + (check-equal? (q2 input-str) 207))] + +