You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.
aoc-racket/day11.rkt

103 lines
4.2 KiB
Racket

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

#lang scribble/lp2
@(require scribble/manual aoc-racket/helper)
@aoc-title[11]
@defmodule[aoc-racket/day11]
@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>
<day11-setup>
<day11-q1>
<day11-q2>
<day11-test>]
@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.
Furthermore, like @secref{Day_5}, the puzzle provides certain criteria that must be met:
@itemlist[
@item{The password must have a sequence of three consecutive letters (like @litchar{bcd}).}
@item{The password may not contain @litchar{i}, @litchar{o}, or @litchar{l}.}
@item{The password must contain two different, non-overlapping pairs of letters.}
]
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[three-consecutive-letters?] test works by converting the letters to numbers and creating a list of the differences betweeen adjacent values. Any three consecutive letters will differ by value of @racket[1]. So if the list of differences contains the subsequence @racket['(1 1)], then the string has three consecutive letters.
@chunk[<day11-setup>
(require racket rackunit)
(provide (all-defined-out))
(define (increment-password password)
(define (increment-letter c)
((compose1 ~a integer->char add1 char->integer car string->list) c))
(match-define (list _ prefix letter-to-increment trailing-zs)
(regexp-match #rx"^(.*?)(.)(z*)$" password))
(string-append* (list prefix (increment-letter letter-to-increment)
(regexp-replace* #rx"z" trailing-zs "a"))))
(define (three-consecutive-letters? str)
(define ints (map char->integer (string->list str)))
(let loop ([differences (map - (cdr ints) (drop-right ints 1))])
(if (empty? differences)
#f
(or (list-prefix? '(1 1) differences) (loop (cdr differences))))))
(define (no-iol? str)
(not (regexp-match #rx"[iol]" str)))
(define (two-nonoverlapping-doubles? str)
(regexp-match #px"(\\w)\\1.*?(\\w)\\2" str))
(define (valid? password)
(and (three-consecutive-letters? password)
(no-iol? password)
(two-nonoverlapping-doubles? password)))
(define (find-next-valid-password starting-password)
(define candidate-pw (increment-password starting-password))
(if (valid? candidate-pw)
candidate-pw
(find-next-valid-password candidate-pw)))
]
@chunk[<day11-q1>
(define (q1 input-key)
(find-next-valid-password input-key))]
@section{What's the next valid password after that?}
We take the answer to question 1 and use it as input to the same function.
@chunk[<day11-q2>
(define (q2 input-key)
(find-next-valid-password (q1 input-key))) ]
@section{Testing Day 11}
@chunk[<day11-test>
(module+ test
(define input-key (file->string "day11-input.txt"))
(check-equal? (q1 input-key) "hxbxxyzz")
(check-equal? (q2 input-key) "hxcaabcc"))]