1
0
Fork 0

Try again

This commit is contained in:
Case Duckworth 2020-12-08 08:37:10 -06:00
parent 1c929c9589
commit 2526c506f9
1 changed files with 27 additions and 567 deletions

594
aoc7.org
View File

@ -1277,581 +1277,41 @@ How many bag colors can eventually contain at least one shiny gold bag? (The lis
** Solution
It looks like Im going to need to figure out what bags can go in what other bags, and build some kind of like, graph or something.
*** Step 1: Parse the input
First thing I have to do is parse the input lines into like, an alist?
#+begin_src emacs-lisp
(defun parse-rule (line)
"Parse a line of text into a rule for bags."
(let* ((container-list (split-string line "contain" t
"[ .]"))
(container_ (car container-list))
(container (substring container_ 0 (1- (length container_))))
(contained-list (split-string (cadr container-list) "," t
"[ s]"))
(contained))
;; turn contained-list from a list of strings to a plist
(require 'subr-x)
(dolist (bag contained-list contained)
(let* ((bag-list (split-string bag))
(bag-number (string-to-number (car bag-list)))
(bag-type (string-join (cdr bag-list) " ")))
(if (= bag-number 0)
(setq contained nil)
;;(push bag-number contained)
(push bag-type contained))))
(cons container contained)))
#+end_src
#+RESULTS:
: parse-rule
*** Step 2: test
Lets test it on our test input.
#+NAME: test-plist
#+begin_src emacs-lisp :results verbatim
(setq test-plist
(aoc
test-input nil
(let ((result))
(dolist (ln lines result)
(let ((bag (parse-rule ln)))
(setq result (plist-put result (cadr bag) (car bag)))))
(reverse result))))
#+end_src
#+RESULTS: test-plist
: (dotted\ black\ bag (nil) faded\ blue\ bag (nil) vibrant\ plum\ bag (dotted\ black\ bag faded\ blue\ bag) dark\ olive\ bag (dotted\ black\ bag faded\ blue\ bag) shiny\ gold\ bag (vibrant\ plum\ bag dark\ olive\ bag) muted\ yellow\ bag (faded\ blue\ bag shiny\ gold\ bag) bright\ white\ bag (shiny\ gold\ bag) dark\ orange\ bag (muted\ yellow\ bag bright\ white\ bag) light\ red\ bag (muted\ yellow\ bag bright\ white\ bag))
*** Step 3: Expand
Okay, Ive built the plist. Whats next? I need to count how many bags can contain somehow a shiny gold bag. That means I need to recursively expand the rules until I cant anymore.
#+NAME: test-expand
#+begin_src emacs-lisp :results verbatim
(defun expand-bag (bag baglist)
"Recursively expand a bag's definition until you can't anymore."
(let ((contained (plist-get baglist bag))
(result))
(unless (equal contained '(nil))
(dolist (b contained result)
(push (expand-bag b baglist) result)))))
#+end_src
#+RESULTS: test-expand
: expand-bag
Hmmmmm. I think I need a different tack.
** Attempt 2
I was trying plists before; lets try an alist now.
*** 1. Parse
We can actually use the same =parse-rule= from before, I think.
#+NAME: test-alist
#+begin_src emacs-lisp :results verbatim
(setq test-alist
(aoc
test-input nil
(let ((result))
(dolist (ln lines result)
(let ((bag (parse-rule ln)))
(push bag result))))))
#+end_src
#+RESULTS: test-alist
: (("dotted black bag") ("faded blue bag") ("vibrant plum bag" "dotted black bag" "faded blue bag") ("dark olive bag" "dotted black bag" "faded blue bag") ("shiny gold bag" "vibrant plum bag" "dark olive bag") ("muted yellow bag" "faded blue bag" "shiny gold bag") ("bright white bag" "shiny gold bag") ("dark orange bag" "muted yellow bag" "bright white bag") ("light red bag" "muted yellow bag" "bright white bag"))
*** 2. Expand
sigh.
#+begin_src emacs-lisp :results verbatim
(defun expand-bag (bag baglist)
(let ((result)
(contents (cadr bag)))
(dolist (b contents result)
(message "%S %S %S" b contents result)
(unless (equal b '(nil))
(push (assoc b baglist #'string=) result)))))
#+end_src
(defun count-bags (desc)
"Take a description, of the form <number> <bag-type>,[...]
and parse it."
(let* ((result)
(empty-n "no")
(desc-list (split-string desc "," t "[ s]")))
(dolist (bag desc-list result)
(let* ((bag-desc (split-string bag))
(bag-num (car bag-desc))
(bag-type (cdr bag-desc)))
(if (string= bag-num "no")
(push nil result)
(push (cons bag-num (string-join bag-type " ")) result))))))
(defun parse-rule (rule)
"Take a rule, of the form <type>s contain <list>
and parse it."
(let* ((contain-list (split-string rule "contain" t "[ .]"))
(main-bag (substring (car contain-list)
0 (1- (length (car contain-list)))))
(sub-bags (count-bags (cadr contain-list))))
(cons main-bag sub-bags)))
#+RESULTS:
: expand-bag
#+NAME: test-expand-alist
#+begin_src emacs-lisp :results verbatim
(let ((result))
(dolist (bag test-alist result)
(push (expand-bag bag test-alist) result)))
#+end_src
#+RESULTS: test-expand-alist
: ((("bright white bag" ("shiny gold bag")) ("muted yellow bag" ("faded blue bag" "shiny gold bag"))) (("bright white bag" ("shiny gold bag")) ("muted yellow bag" ("faded blue bag" "shiny gold bag"))) (("shiny gold bag" ("vibrant plum bag" "dark olive bag"))) (("shiny gold bag" ("vibrant plum bag" "dark olive bag")) ("faded blue bag" (nil))) (("dark olive bag" ("dotted black bag" "faded blue bag")) ("vibrant plum bag" ("dotted black bag" "faded blue bag"))) (("faded blue bag" (nil)) ("dotted black bag" (nil))) (("faded blue bag" (nil)) ("dotted black bag" (nil))) (nil) (nil))
** Attempt 3: /truly/ a different tack
Attempt 2 was getting nowhere. I think Im going to try another, /another/ tack.
Lets think about this mathematically. Bags that dont contain any other bags are like prime numbers, because theyre indivisible. All other bags contain some number of different prime-numbered bags, which means that we can figure out a /bag number/ for every bag by assigning successive primes to non-bag-containing bags, then multiplying them all together from there.
*** First: mark the primes
**** First-first: a primes library
from [[https://lara.epfl.ch/w/cc09/primes.el][EPFL]].
#+begin_src emacs-lisp :results verbatim
;;; primes.el --- prime number support for Emacs Lisp library code
;; Copyright (C) 1999
;; Free Software Foundation, Inc.
;; This file is part of GNU Emacs.
;; GNU Emacs is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.
;; Author: Nelson H. F. Beebe <beebe@math.utah.edu>
;; Maintainer: Nelson H. F. Beebe <beebe@math.utah.edu>
;; Created: 27 February 1999
;; Version: 1.00
;; Keywords: prime numbers, primality testing
;; This file is part of GNU Emacs.
;;; Commentary:
;; A prime number is any integer greater than one which has no exact
;; integer divisors other than one and itself.
;;
;; Prime numbers have increasingly important practical applications
;; in cryptography, and are also useful in hashing, besides being of
;; fundamental importance in number theory.
;;
;; This is a small collection of functions for:
;;
;; * testing integers for primality,
;; * generating nearby primes,
;; * finding the n-th prime,
;; * generating lists of primes in a given range,
;; * factoring a number into a product of primes,
;; * finding the greatest common divisor of two numbers, and
;; * finding the least common multiple of two numbers.
;;
;; The functions provided are:
;;
;; (gcd n m) [cost: O((12(ln 2)/pi^2)ln n)]
;; (lcm n m) [cost: O((12(ln 2)/pi^2)ln n)]
;; (next-prime n) [cost: O(sqrt(N))]
;; (nth-prime n) [cost: O(N*sqrt(N))]
;; (prev-prime n) [cost: O(sqrt(N))]
;; (prime-factors n) [cost: O(N)]
;; (prime-p n) [cost: O(sqrt(N))]
;; (primes-between from to) [cost: O((to - from + 1)*sqrt(N))]
;; (this-or-next-prime n) [cost: O(sqrt(N))]
;; (this-or-prev-prime n) [cost: O(sqrt(N))]
;;
;; The modest collection of functions here is likely to grow, and
;; perhaps may even be improved algorithmically. The core of most of
;; these functions is the primality test, (prime-p N), whose running
;; time is O(sqrt(N)), which becomes excessive for large N. Note that
;; sqrt(N) == 2^{(lg N)/2}, where lg N, the base-2 logarithm of N, is
;; the number of bits in N. Thus O(sqrt(N)) means O(2^(bits in N)),
;; or O(10^(digits in N)). That is, the running time increases
;; EXPONENTIALLY in the number of digits of N.
;;
;; Because knowledge of the cost of these functions may be critical to
;; the caller, each function's documentation string ends with a
;; bracketed cost estimate as a final paragraph.
;;
;; Faster algorithms capable of dealing with larger integers are
;; known. For example, Maple V Release 5 (1997) implements a
;; probabilistic function, isprime(n), that is
;;
;; ``very probably'' prime - see Knuth ``The art of computer
;; programming'', Vol 2, 2nd edition, Section 4.5.4, Algorithm P
;; for a reference and H. Reisel, ``Prime numbers and computer
;; methods for factorization''. No counter example is known and
;; it has been conjectured that such a counter example must be
;; hundreds of digits long.
;;
;; Robert Sedgewick also promises a fast prime test in Part 5 of his
;; book ``Algorithms in C'', not yet published at the time of writing
;; this in March 1999.
;;
;; Three algorithms for probabilistic primality tests for large
;; numbers are discussed in Bruce Schneier, ``Applied Cryptography'',
;; (Wiley, 1994, ISBN 0-471-59756-2), pp. 213--216.
;;
;; Other key references, described in more detail in the Emacs Lisp
;; Manual chapter for this library, include
;;
;; Leonard M. Adleman, Algorithmic Number Theory --- The
;; Complexity Contribution, Proc. 35th IEEE Symposium on the
;; Foundations of Computer Science (FOCS'94), Shafi Goldwasser
;; (Ed.), IEEE Computer Society Press (Silver Spring, MD),
;; pp. 88--113, 1994, ISBN 0-8186-6582-3, ISSN 0272-5428.
;;
;; Eric Bach and Jeffrey Shallit, Algorithmic Number Theory.
;; Volume I: Efficient Algorithms, MIT Press (Cambridge, MA),
;; 1996, ISBN 0-262-02405-5.
;;
;; Ronald L. Graham, Donald E. Knuth and Oren Patashnik, Concrete
;; Mathematics, Addison-Wesley, Reading, MA, USA, 1989, ISBN
;; 0-201-14236-8.
;;
;; Donald E. Knuth, Fundamental algorithms, The Art of Computer
;; Programming, Volume 1, Third edition, Addison-Wesley (Reading,
;; MA), 1997, ISBN 0-201-89683-4.
;;
;; Donald E. Knuth, Seminumerical algorithms, The Art of Computer
;; Programming, Volume 2, Third edition, Addison-Wesley (Reading,
;; MA), 1997, ISBN 0-201-89684-2.
;;
;; Steven S. Skiena, The Algorithm Design Manual, Springer-Verlag
;; (New York, NY), 1998, ISBN 0-387-94860-0.
;;
;;; Code:
(defconst primes-version "1.00"
"Version number of primes library.")
(defconst primes-date "[27-Feb-1999]"
"Revision date of primes library.")
(defun gcd (m n)
"Return the Greatest Common Divisor of integers M and N, or nil if
they are invalid.
\[cost: O((12(ln 2)/pi^2)ln max(M,N)) == O(0.8427659... max(M,N))]"
;; For details of this 2300-year algorithm due to Euclid, see, e.g.
;; Ronald L. Graham, Donald E. Knuth and Oren Patashnik, ``Concrete
;; Mathematics'' (Addison-Wesley, Reading, MA, USA, 1989, ISBN
;; 0-201-14236-8), pp. 103--104.
;;
;; The complexity analysis is surprisingly difficult; see Donald
;; E. Knuth, ``Seminumerical algorithms, The Art of Computer
;; Programming, Volume 2'', third edition (Addison-Wesley, Reading,
;; MA, USA, 1997, ISBN 0-201-89684-2), pp. 356--373.
(if (and (integerp m) (integerp n)) ; check for integer args
(progn
(setq m (abs m)) ; argument sign does not matter for gcd, so
(setq n (abs n)) ; force positive for the algorithm below
(cond
((and (= m 0) (= n 0)) 0) ; gcd(0,0) ==> 0 by definition, for convenience
((and (= m 0) (> n 0)) n) ; gcd(0,n) ==> n
((and (> m 0) (= n 0)) m) ; gcd(m,0) ==> m
((and (> m n) (> n 0)) (gcd n m)) ; reinvoke with reversed arguments
(t (gcd (% n m) m)))) ; else reduce recursively
nil)) ; non-integer args: invalid
(defun lcm (m n)
"Return the Least Common Multiple of integers M and N, or nil if
they are invalid, or the result is not representable (e.g., the
product M*N overflows).
\[cost: O((12(ln 2)/pi^2)ln max(M,N)) == O(0.8427659... max(M,N))]"
(cond
((and (integerp m) (integerp n)) ; check for integer args
(let ((mn) (the-gcd) (the-lcm))
(if (or (= m 0) (= n 0)) ; fast special case: lcm(0,anything) == 0
0
;; else compute lcm from (m * n) / gcd(m,n)
;;
;; Problem: GNU Emacs Lisp integer multiply does not detect or
;; trap overflow, which is a real possibility here, and it lacks
;; a double-length integer type to represent the product m * n.
;; Since the lcm may still be representable, we do the
;; intermediate computation in (double-precision)
;; floating-point, which is still not quite large enough to
;; represent all products of Emacs 28-bit integers stored in
;; 32-bit words, then convert back to integer results. The
;; floor function will signal an error if the result is not
;; representable. To try to avoid that, we first check that the
;; equality gcd * lcm = m * n is satisfied, and only if it is,
;; do we invoke floor.
;;
;; TO DO: find better algorithm without these undesirable
;; properties.
(setq m (abs m)) ; argument sign does not matter for lcm, so
(setq n (abs n)) ; force positive for the algorithm below
(setq the-gcd (gcd m n))
(setq mn (* (float m) (float n)))
(setq the-lcm (/ mn the-gcd))
(if (= (* the-gcd the-lcm) mn) ; then got correct answer
(floor the-lcm)
nil)))) ; else out-of-range or invalid
(t nil))) ; non-integer args: invalid
(defun prime-factors (n)
"Return a list of prime factors of N.
If N is prime, there are no factors, except the trivial one of N itself,
so the return value is the list (N). Thus, if (length (prime-factors N))
is 1, N is prime.
Otherwise, if N is not an integer greater than 1, the return value is
nil, equivalent to an empty list.
\[cost: O(N)]"
(interactive)
(let ((result-list nil)
(n-original n))
(if (and (integerp n) (> n 1))
(let ((limit (/ n 2))
(divisor 2))
(while (<= divisor limit)
;; To correctly handle factors of multiplicity > 1, we must
;; be careful to advance the divisor only when it is not a
;; factor! When n is replaced by n/divisor, we can reset
;; limit, but only to n/2, not to n. Consider
;; (prime-factors 15): the first factor found is 3, which
;; reduces n to 5, which will be the next prime factor
;; found, but would be lost if we reset limit to 5/2 == 2.
;;
;; If this divisor is rejected, as long as it is greater
;; than 2, and thus, odd, we can step it by 2, halving the
;; number of loop iterations.
(if (= 0 (% n divisor))
(setq n (/ n divisor)
limit n
result-list (append result-list (list divisor)))
(if (= divisor 2)
(setq divisor 3)
(setq divisor (+ divisor 2)))))
;; If we end the while loop with an empty result-list, then
;; the input N was prime, so set result-list to a one-element
;; list:
(if (null result-list)
(setq result-list (list n-original)))))
result-list))
(defun prime-p (n)
"Return N if it is a prime number, else nil.
Because Emacs integers are usually more limited in size than the host
word size would suggest, e.g.,
\[-2^{27}, 2^{27} - 1] == [-134217728, 134217727]
on a 32-bit machine, avoid passing excessively large integers to this
function, otherwise you may experience this failure:
\(next-prime 134217689)
Arithmetic domain error: \"sqrt\", -134217728.0
While you may be able to use larger integers on some 64-bit machines,
the required run time for this function is then likely to be excessive.
\[cost: O(sqrt(N))]"
(interactive)
(if (integerp n)
(cond ((< n 2) nil) ;there are no primes below 2
((= n 2) 2) ;special case for the only even prime
((= 0 (% n 2)) nil) ;there are no other even primes
(t (catch 'RESULT ;else we have a positive odd candidate value
(let ((limit (floor (sqrt n)))
(divisor 2))
(while (<= divisor limit)
(if (= 0 (% n divisor))
(throw 'RESULT nil))
(setq divisor (1+ divisor)))
n))))
nil))
(defun next-prime (n)
"Return the next prime number after N, or else nil.
\[cost: O(sqrt(N))]"
(if (integerp n)
(cond
((> n 1)
(let* ((k (if (= 0 (% n 2));start k at next odd number after n
(1+ n)
(+ n 2))))
(while (not (prime-p k)) ;and loop upward over odd numbers
(setq k (+ k 2)))
k))
(t 2))
nil))
(defun nth-prime (n)
"Return the N-th prime number, or else nil.
The first prime number is 2.
\[cost: O(N*sqrt(N))]"
(if (integerp n)
(cond
((<= n 0) nil) ;non-positive args are invalid
((= n 1) 2) ;special case: only even prime
(t (let ((k 1)) ;n > 1, so test only odd values 3, 5, ...
(while (> n 1)
(setq k (+ k 2))
(if (prime-p k) ;count down each prime found
(setq n (1- n))))
k))) ;at loop exit, k is the desired nth-prime
nil))
(defun prev-prime (n)
"Return the prime number before (i.e., less than) N, or else nil.
\[cost: O(sqrt(N))]"
(if (integerp n)
(cond ((<= n 2) nil) ;invalid: there are no primes before 2
((= n 3) 2) ;special case: 2 is the only even prime
(t ;n > 3
(let* ((k (if (= 0 (% n 2)) ;start k at prev odd number before n
(1- n)
(- n 2))))
(while (not (prime-p k)) ;and loop downward over odd numbers
(setq k (- k 2)))
k)))
nil))
(defun primes-between (from to)
"Return a list of prime numbers between FROM and TO, inclusive, else nil.
\[cost: O((to - from + 1)*sqrt(N)/2)]"
(if (and (integerp from) (integerp to))
(let ((k)
(primes '()))
(if (and (<= from 2) (<= 2 to)) ;handle special case of only even prime
(setq primes '(2)))
(setq k 3)
;; k is now odd, so we can loop over odd numbers only
(while (<= k to)
(if (prime-p k)
(setq primes (append primes (list k))))
(setq k (+ k 2)))
primes) ;final successful list
nil)) ;else invalid arguments
(defsubst this-or-next-prime (n)
"Return N if it is prime, else return the next prime number after N,
else nil in N is invalid.
\[cost: O(sqrt(N))]"
(or (prime-p n)
(next-prime n)))
(defsubst this-or-prev-prime (n)
"Return N if it is prime, else return the prime number before
(i.e., less than) N.
\[cost: O(sqrt(N))]"
(or (prime-p n)
(prev-prime n)))
;;; primes.el ends here
;; Illustration
(defun show-some-primes (file) "Show some prime numbers." (interactive "p")
(princ (primes-between 1 100))
)
#+end_src
#+RESULTS:
: show-some-primes
**** okay
#+begin_src emacs-lisp :results verbatim
(setq *curprime* 1)
(defun format-name (name)
(replace-regexp-in-string " " "-" name))
(defun format-names (names)
(let ((result ""))
(dolist (n names result)
(string-join
(list
(if (string= n "no other bag")
(prog1
(number-to-string (nth-prime *curprime*))
(incf *curprime*))
(let* ((words (split-string n))
(word-num (pop words)))
(string-join
(list word-num
(format-name (string-join
words "-"))))))
result)))))
(defun turn-rule-into-setq (rule)
(let* ((container-list (split-string rule
"contain"
t
"[ .]"))
(container_ (car container-list))
(container (format-name
(substring container_ 0
(1- (length container_)))))
(contained-list (split-string (cadr container-list)
","
t
"[ s]"))
)
(format "(setq %s %s)"
container
(format-names contained-list))))
(aoc test-input nil
(let ((result))
(dolist (ln lines result)
(push (turn-rule-into-setq ln) result))))
#+end_src
#+RESULTS:
: ("(setq dotted-black-bag )" "(setq faded-blue-bag )" "(setq vibrant-plum-bag )" "(setq dark-olive-bag )" "(setq shiny-gold-bag )" "(setq muted-yellow-bag )" "(setq bright-white-bag )" "(setq dark-orange-bag )" "(setq light-red-bag )")
** Try … whatever
#+begin_src emacs-lisp :results verbatim
(aoc
test-input nil
(seq-filter (lambda (elt)
(string-match "shiny")))
(let ((result))
(dolist (ln lines result)
(push (parse-rule ln) result))))
#+end_src
#+RESULTS:
: ("dotted black bags contain no other bags." "faded blue bags contain no other bags." "vibrant plum bags contain 5 faded blue bags, 6 dotted black bags." "dark olive bags contain 3 faded blue bags, 4 dotted black bags." "shiny gold bags contain 1 dark olive bag, 2 vibrant plum bags." "muted yellow bags contain 2 shiny gold bags, 9 faded blue bags." "bright white bags contain 1 shiny gold bag." "dark orange bags contain 3 bright white bags, 4 muted yellow bags." "light red bags contain 1 bright white bag, 2 muted yellow bags.")
: (("dotted black bag" nil) ("faded blue bag" nil) ("vibrant plum bag" ("6" . "dotted black bag") ("5" . "faded blue bag")) ("dark olive bag" ("4" . "dotted black bag") ("3" . "faded blue bag")) ("shiny gold bag" ("2" . "vibrant plum bag") ("1" . "dark olive bag")) ("muted yellow bag" ("9" . "faded blue bag") ("2" . "shiny gold bag")) ("bright white bag" ("1" . "shiny gold bag")) ("dark orange bag" ("4" . "muted yellow bag") ("3" . "bright white bag")) ("light red bag" ("2" . "muted yellow bag") ("1" . "bright white bag")))
* Part 2