Add day 07
[adventofcode2020.git] / src / day07.lisp
diff --git a/src/day07.lisp b/src/day07.lisp
new file mode 100644 (file)
index 0000000..db50ca8
--- /dev/null
@@ -0,0 +1,142 @@
+(asdf:load-system :adventofcode2020)
+(in-package #:adventofcode2020)
+(named-readtables:in-readtable fn-reader)
+
+(defun parse-bag-rule (rule)
+  (cl-ppcre:all-matches-as-strings 
+    "(^[a-z]+ [a-z]+|[0-9] [a-z]+ [a-z]+)" rule))
+
+(defun add-reverse-rule (table rule)
+  (let ((outer-bag (first rule))
+        (inner-bags (rest rule)))
+    (loop for num-bag in inner-bags
+          do (let ((bag (string-left-trim " 0123456789" num-bag)))
+               (setf (gethash bag table)
+                     (adjoin outer-bag (gethash bag table) :test #'string=)))
+          finally (return table))))
+
+(defun add-forward-rule (table rule)
+  (let ((outer-bag (first rule))
+        (bag-with-count (lambda (bag)
+                          (cl-ppcre:register-groups-bind 
+                            ((#'parse-integer count) bag-name)
+                            ("([0-9]) ([a-z]+ [a-z]+)" bag)
+                            (list bag-name count)))))
+    (setf (gethash outer-bag table) (mapcar bag-with-count (rest rule)))
+    table))
+
+(defun make-rules-table (rules rule-type)
+  (let ((table (make-hash-table :test #'equal)))
+    (reduce rule-type rules :initial-value table)))
+
+(defun walk-rev-rules-table (table bag)
+  (let ((next (gethash bag table)))
+    (reduce λ(union _0 _1 :test #'string=)
+            (mapcar (curry #'walk-rev-rules-table table) next)
+            :initial-value next)))
+
+(defun walk-fwd-rules-table (table bag)
+  (let ((next (gethash bag table))
+        (get-bag-count (lambda (bag-pair)
+                         (if bag-pair
+                             (destructuring-bind (bag-name count) bag-pair
+                               (* count (walk-fwd-rules-table table bag-name)))
+                             0))))
+    (reduce #'+ (mapcar get-bag-count next)
+            :initial-value 1)))
+
+(day 07 input
+  (let ((rules (mapcar #'parse-bag-rule (list-from input))))
+    (part1 (-> rules
+             (make-rules-table #'add-reverse-rule)
+             (walk-rev-rules-table "shiny gold")
+             length))
+    (part2 (-> rules
+             (make-rules-table #'add-forward-rule)
+             (walk-fwd-rules-table "shiny gold")
+             (1-)))))
+
+(def-suite day07)
+(in-suite day07)
+
+(defvar *simple-rules* 
+  '("light red bags contain 1 bright white bag, 2 muted yellow bags."
+    "dark orange bags contain 3 bright white bags, 4 muted yellow bags."
+    "bright white bags contain 1 shiny gold bag."
+    "muted yellow bags contain 2 shiny gold bags, 9 faded blue bags."
+    "shiny gold bags contain 1 dark olive bag, 2 vibrant plum bags."
+    "dark olive bags contain 3 faded blue bags, 4 dotted black bags."
+    "vibrant plum bags contain 5 faded blue bags, 6 dotted black bags."
+    "faded blue bags contain no other bags."
+    "dotted black bags contain no other bags."))
+
+(test parse-bag-rules
+  (is (equal
+        '(("light red" "1 bright white" "2 muted yellow")
+          ("dark orange" "3 bright white" "4 muted yellow")
+          ("bright white" "1 shiny gold") 
+          ("muted yellow" "2 shiny gold" "9 faded blue")
+          ("shiny gold" "1 dark olive" "2 vibrant plum")
+          ("dark olive" "3 faded blue" "4 dotted black")
+          ("vibrant plum" "5 faded blue" "6 dotted black") 
+          ("faded blue")
+          ("dotted black"))
+        (mapcar #'parse-bag-rule *simple-rules*))))
+
+(test make-rev-rules-table
+  (is (equal
+        '("dotted black" ("vibrant plum" "dark olive") 
+          "vibrant plum" ("shiny gold")
+          "dark olive" ("shiny gold") 
+          "faded blue" ("vibrant plum" "dark olive" "muted yellow") 
+          "shiny gold" ("muted yellow" "bright white") 
+          "muted yellow" ("dark orange" "light red")
+          "bright white" ("dark orange" "light red"))
+        (hash-table-plist (make-rules-table (mapcar #'parse-bag-rule *simple-rules*) #'add-reverse-rule)))))
+
+(test walk-rev-rules-table
+  (is (equal
+       '("muted yellow" "bright white" "dark orange" "light red")
+        (walk-rev-rules-table (make-rules-table (mapcar #'parse-bag-rule *simple-rules*) #'add-reverse-rule)
+                          "shiny gold"))))
+
+(defvar *more-rules*
+  '("shiny gold bags contain 2 dark red bags."
+    "dark red bags contain 2 dark orange bags."
+    "dark orange bags contain 2 dark yellow bags."
+    "dark yellow bags contain 2 dark green bags."
+    "dark green bags contain 2 dark blue bags."
+    "dark blue bags contain 2 dark violet bags."
+    "dark violet bags contain no other bags."))
+
+(test make-fwd-rules-table
+  (is (equal
+        '("dotted black" nil 
+          "faded blue" nil 
+          "vibrant plum" (("faded blue" 5) ("dotted black" 6)) 
+          "dark olive" (("faded blue" 3) ("dotted black" 4)) 
+          "shiny gold" (("dark olive" 1) ("vibrant plum" 2)) 
+          "muted yellow" (("shiny gold" 2) ("faded blue" 9)) 
+          "bright white" (("shiny gold" 1)) 
+          "dark orange" (("bright white" 3) ("muted yellow" 4)) 
+          "light red" (("bright white" 1) ("muted yellow" 2)))
+        (hash-table-plist (make-rules-table (mapcar #'parse-bag-rule *simple-rules*) #'add-forward-rule))))
+  (is (equal
+        '("dark violet" nil 
+          "dark blue" (("dark violet" 2)) 
+          "dark green" (("dark blue" 2)) 
+          "dark yellow" (("dark green" 2)) 
+          "dark orange" (("dark yellow" 2)) 
+          "dark red" (("dark orange" 2)) 
+          "shiny gold" (("dark red" 2)))
+        (hash-table-plist (make-rules-table (mapcar #'parse-bag-rule *more-rules*) #'add-forward-rule)))))
+
+(test walk-fwd-rules-table
+  (is (equal
+        126
+        (1- (walk-fwd-rules-table (make-rules-table (mapcar #'parse-bag-rule *more-rules*) #'add-forward-rule)
+                               "shiny gold")))))
+
+
+(run! 'day07)
+; "([a-z]+ [a-z]+) bags contain ([0-9]+ [a-z]+ [a-z]+|no other) bag[s]?(?:, (?:([0-9]+ [a-z]+ [a-z]+) bag[s]?))*\."