aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJuan Marín Noguera <juan@mnpi.eu>2024-12-24 19:09:20 +0100
committerJuan Marín Noguera <juan@mnpi.eu>2024-12-24 19:09:20 +0100
commit3b21289f8633a69365d1838ac9a134860fbb36c7 (patch)
tree30b51f23c6fe775dab08a338e4399e6753aac78c
parent6654ac6010cc0659e893171f1133f62d29e7df30 (diff)
Day 23
-rw-r--r--day23.scm142
1 files changed, 142 insertions, 0 deletions
diff --git a/day23.scm b/day23.scm
new file mode 100644
index 0000000..9b6d8e7
--- /dev/null
+++ b/day23.scm
@@ -0,0 +1,142 @@
+(library (day23)
+ (export part1 part2)
+ (import (ice-9 rdelim)
+ (ice-9 regex)
+ (pfds sets)
+ (guile)
+ (srfi srfi-69)
+ (srfi srfi-88)
+ (srfi srfi-89)))
+
+(define +line-regex+ (make-regexp "^([a-z][a-z])-([a-z][a-z])$"))
+
+(define (node? obj)
+ (and (string? obj)
+ (= (string-length obj) 2)
+ (char<=? #\a (string-ref obj 0) #\z)
+ (char<=? #\a (string-ref obj 1) #\z)))
+
+(define (node=? . nodes)
+ (apply string=? nodes))
+
+(define (network? obj)
+ ;; Also, the table maps from node? to set? of node? (comparator string<?) and
+ ;; the graph represented this way should be symmetric and antireflexive.
+ (hash-table? obj))
+
+(define (network-connects? nw n1 n2)
+ (and (hash-table-exists? nw n1)
+ (set-member? (hash-table-ref nw n1) n2)))
+
+(define (make-network)
+ (make-hash-table))
+
+(define (network-connect! nw n1 n2)
+ (define (add n1 n2)
+ (define set (hash-table-ref nw n1 (lambda () (make-set string<?))))
+ (hash-table-set! nw n1 (set-insert set n2)))
+ (unless (string=? n1 n2)
+ (add n1 n2)
+ (add n2 n1)))
+
+(define (set-for-each proc set)
+ (set-fold (lambda (v base) (proc v)) #f set))
+
+(define* (read-network (port (current-input-port)))
+ (define nw (make-network))
+ (let iter ()
+ (define line (read-line port))
+ (unless (or (eof-object? line) (string=? "" line))
+ (let ((mtch (regexp-exec +line-regex+ line)))
+ (unless mtch (error "Expected a connection line, like 'aa-bb'." line))
+ (network-connect! nw (match:substring mtch 1) (match:substring mtch 2)))
+ (iter)))
+ nw)
+
+(define (network-for-each proc nw)
+ (hash-table-walk
+ nw (lambda (u set)
+ (set-for-each (lambda (v) (when (string<? u v) (proc u v))) set))))
+
+(define* (print-network nw (port (current-output-port)))
+ (network-for-each (lambda (u v) (format port "~a-~a\n" u v)) nw))
+
+(define (t-prefix? node)
+ (char=? #\t (string-ref node 0)))
+
+(define (count-increment n1 n2)
+ ;; Each three-element set is counted as many times as there are permutations
+ ;; of the elements where the first one starts with t, so here we compensate
+ ;; for that.
+ (/ 1/2 (+ 1 (if (t-prefix? n1) 1 0) (if (t-prefix? n2) 1 0))))
+
+(define* (count-3-tuples nw)
+ (define count 0)
+ (define (parse-node! k set)
+ (when (t-prefix? k)
+ (set-for-each
+ (lambda (v1)
+ (set-for-each
+ (lambda (v2)
+ (when (network-connects? nw v1 v2)
+ (set! count (+ count (count-increment v1 v2)))))
+ set))
+ set)))
+ (hash-table-walk nw parse-node!)
+ count)
+
+(define* (part1 (port (current-input-port)))
+ (count-3-tuples (read-network port)))
+
+(define (generate-dotted-graph nw port)
+ (display "graph G {\n" port)
+ (display "\tnode[shape=point]\n" port)
+ (network-for-each (lambda (u v) (format port "\t~a--~a\n" u v)) nw)
+ (display "}\n" port))
+
+(define (find-k-clique nw k)
+ ;; We start with the empty clique, and all nodes in the graph as candidates
+ ;; for adding to the clique.
+ ;; Then, for each candidate node:
+ ;; - If the candidate connects with every node in the current clique, we
+ ;; search by expanding the current clique with the current candidate and
+ ;; using as candidates the nodes from the current set of candidates that
+ ;; also connect with this newly-added element. We do this by recursion.
+ ;; - If it doesn't connect to every node of the clique, or if the search above
+ ;; fails, we remove the current element from the current set of candidates,
+ ;; as an optimization.
+ (define (recur clique intersection)
+ (define (fold-elem elem acc)
+ ;; acc can be:
+ ;; the set of elements that have not been discarded yet, or
+ ;; (cons #t found-clique), if a k-clique has been found.
+ (if (pair? acc)
+ acc
+ (let* ((connected (hash-table-ref nw elem))
+ (found-clique (and (subset? clique connected)
+ (recur (set-insert clique elem)
+ (set-intersection acc connected)))))
+ (if found-clique
+ (cons #t found-clique)
+ (set-remove acc elem)))))
+ (cond ((= (set-size clique) k) clique)
+ ((< (+ (set-size clique) (set-size intersection)) k) #f)
+ (else (let ((folded (set-fold fold-elem intersection intersection)))
+ (and (pair? folded) (cdr folded))))))
+ (recur (make-set string<?)
+ (list->set (map car (hash-table->alist nw)) string<?)))
+
+(define (maximum-clique nw)
+ (let binary-search ((low 0) (high (hash-table-size nw)))
+ (if (= low high)
+ #f
+ (let* ((mid (quotient (+ low high) 2))
+ (clique (find-k-clique nw mid)))
+ (if clique
+ (or (binary-search (+ mid 1) high) clique)
+ (binary-search low mid))))))
+
+(define* (part2 (port (current-input-port)))
+ (define nw (read-network port))
+ (define clique (maximum-clique nw))
+ (string-join (sort! (set->list clique) string<?) ","))