On this page:
<day16>
16.1 Which Sue matches the attribute input?
<day16-setup>
<day16-q1>
16.2 Which Sue matches the attribute input, with the “retroencabulator” rules?
<day16-q2>
16.3 Testing Day 16
<day16-test>
7.7

16 Day 16

 (require aoc-racket/day16) package: aoc-racket

The puzzle. Our input is a list of 500 people named Sue, along with three attribute pairs for each. We’re also provided a set of attribute pairs that identifies one of the Sues:

children: 3
cats: 7
samoyeds: 2
pomeranians: 3
akitas: 0
vizslas: 0
goldfish: 5
trees: 3
cars: 2
perfumes: 1

16.1 Which Sue matches the attribute input?

Our input has 10 attribute pairs, but each of our Sues only has three attribute pairs. The other values for each Sue are unknown. Thus, for each Sue in our list, we need to check if the three known attribute pairs are among in our 10 identifying pairs. Assumedly, this will be true for only one Sue, and that will be the answer.

We might be tempted to break down the attribute pairs into hash tables. But we don’t even need to work that hard — we can just treat the attribute pairs as a list of strings, and match whole strings like "children: 3". And our Sues can be a list too, each holding a list of strings. Cheap & cheerful.

(require racket rackunit)
(provide (all-defined-out))
 
(define (parse-sues str)
  (for/list ([ln (in-list (string-split str "\n"))])
            (define attr-str (second (regexp-match #px"^.*?: (.*)$" ln)))
            (string-split attr-str ", ")))
 
(define master-attrs (file->lines "day16-input-master-attrs.txt"))

(define (q1 input-str)
  (define sues (parse-sues input-str))
  (for/or ([(sue-attrs sue-number) (in-indexed sues)])
          (for/and ([sue-attr (in-list sue-attrs)])
                   (and (member sue-attr master-attrs) (add1 sue-number)))))

16.2 Which Sue matches the attribute input, with the “retroencabulator” rules?

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.

So let’s compound our laziness with more laziness. Rather than upgrade to a hash table now, let’s convert our strings to datums (as saw in Day 7). Because if we put a string like "children: 3" inside parentheses like so "(children: 3)" and then convert it to a datum (with read) we’ll end up with a list with a key and a value, e.g. '(children: 3). In other words, just what we needed. (I know the plural of datum is data, but datums better connotes “more than one datum, in the Racket sense.”)

Plus, it’s always fun to find a use for case and the frequently overlooked assoc.

(define (q2 input-str)
  (define (attrs->datums attrs)
    (map (compose1 read open-input-string
                   (λ (attr) (format "(~a)" attr))) attrs))
  (define sues (for/list ([sue-attrs (parse-sues input-str)])
                         (attrs->datums sue-attrs)))
  (define master-datums (attrs->datums master-attrs))
  (for/or ([(sue-datums sue-number) (in-indexed sues)])
          (for/and ([sue-datum (in-list sue-datums)])
                   (and
                    (let* ([sue-key (first sue-datum)]
                           [sue-value (second sue-datum)]
                           [master-value (second (assoc sue-key master-datums))]
                           [cmp (case sue-key
                                  [(cats: trees:) >]
                                  [(pomeranians: goldfish:) <]
                                  [else =])])
                      (cmp sue-value master-value))
                    (add1 sue-number)))))

16.3 Testing Day 16

(module+ test
  (define input-str (file->string "day16-input.txt"))
  (check-equal? (q1 input-str) 103)
  (check-equal? (q2 input-str) 405))