FINALLY figured out the solution to puzzle 7

Easy problem once I figured out the trick to constructing an
inherently stateful structure with backwards pointer references (at
least the first way I would've made it) into a functional alist
solution.  Very cool.
This commit is contained in:
2023-10-17 19:58:34 +01:00
parent 31878dd6e8
commit f11415c365

View File

@@ -9,7 +9,7 @@
(split-completely (string-to-clist line) #\Space))) (split-completely (string-to-clist line) #\Space)))
lines)) lines))
(defun token-cmdp (token) (defun is-token-cmd? (token)
"Checks if TOKEN is a command (by the first member)" "Checks if TOKEN is a command (by the first member)"
(string= (car token) "$")) (string= (car token) "$"))
@@ -18,65 +18,239 @@
tokens (including command)." tokens (including command)."
(loop (loop
for token-set on tokens for token-set on tokens
if (token-cmdp (car token-set)) if (is-token-cmd? (car token-set))
return token-set)) return token-set))
#| What is a directory structure? You have some root directory, with ;;; Functions to manage a directory structure
some entries. Your current working directory (CWD), at the start of (progn
the program, will be at the root but later on could be any descendant #| What is a directory structure? You have some root directory, with
of the root. How do we manage moving around the directory structure some entries. Your current working directory (CWD), at the start of
and ensure updates to the root? the program, will be at the root but later on could be any descendant
of the root. How do we manage moving around the directory structure
and ensure updates to the overall structure?
We should maintain a HISTORY stack where We should maintain a HISTORY stack where
CAR(HISTORY) = (NAME of CWD . State of CWD when entering). CAR(HISTORY) = (NAME of CWD . State of CWD according to parent).
Firstly a function which updates the HISTORY on our current state. So the top of the history stack represents the current directory
UPDATE-CURRENT-DIRECTORY(CWD, HISTORY) { structure in the form it'll inhabit in its parents. We need a way to
CWD-ENTRY = PEEK(HISTORY); update the top of the history stack with the CWD.
IF NULL(CWD-ENTRY)
HISTORY = ((NIL . CWD))
ELSE
SETF((CDR CWD-ENTRY), CWD);
}
Say CWD = {... a = directory, b = file, ...} UPDATE-CURRENT-DIRECTORY(CWD, HISTORY) {
DOWN-DIRECTORY(a, CWD, HISTORY) { CWD-ENTRY = PEEK(HISTORY);
UPDATE-CURRENT-DIRECTORY(CWD, HISTORY); <-- This is so we don't lose information IF NULL(CWD-ENTRY)
HISTORY = ACONS(a, CWD@a, HISTORY); HISTORY = ((NIL . CWD))
CWD = CWD@a; ELSE
} SETF((CDR CWD-ENTRY), CWD);
}
|#
(defun update-current-directory (cwd history)
"Updates the top of HISTORY (structure of CWD) with CWD i.e. set the
'state' of the top of the the HISTORY stack to CWD."
(if (null (car history))
(values
cwd
`((nil ,@cwd)))
(progn
(setf (cdr (car history)) cwd)
(values
cwd
history))))
#|
Say CWD = {... a = directory, ...}. How do I go down to `a`?
DOWN-DIRECTORY(a, CWD, HISTORY) {
UPDATE-CURRENT-DIRECTORY(CWD, HISTORY); <-- This is so we don't lose
any current information
HISTORY = ACONS(a, CWD@a, HISTORY);
CWD = CWD@a;
}
|#
(defun down-directory (name cwd history)
(multiple-value-bind (cwd history) (update-current-directory cwd history)
(let ((cwd@a (cdr (assoc name cwd :test #'string=))))
(values
cwd@a
(acons name cwd@a history)))))
#|
How do we ascend the directory structure? We need to account for 3 cases:
1) At the root
2) Parent is the root
3) Parent is any directory
Then UP-DIRECTORY(CWD, HISTORY) { Then UP-DIRECTORY(CWD, HISTORY) {
PAIR = POP(HISTORY); <-- (CWD-NAME . PREVIOUS-CWD-STATE) PAIR = POP(HISTORY); <-- (CWD-NAME . PREVIOUS-CWD-STATE)
PARENT = PEEK(HISTORY); <-- Parent of CWD (PARENT-NAME . PARENT-STATE) if NULL(CAR(PAIR))
ENTRY = ASSOC(CAR(PAIR) CDR(PARENT)) <-- Gives us the CWD entry in the parent then error(AT ROOT)
SETF CDR(ENTRY) CWD <-- Update the parent else
CWD = CDR(PARENT) <-- Update CWD PARENT = PEEK(HISTORY); <-- Parent of CWD (PARENT-NAME . PARENT-STATE)
} ENTRY = ASSOC(CAR(PAIR) CDR(PARENT)) <-- Gives us the CWD entry in the parent
|# SETF CDR(ENTRY) CWD <-- Update the parent
CWD = CDR(PARENT) <-- Update CWD
}
|#
(defun up-directory (cwd history)
(when (null (caar history))
;; At root, so we can't go further up
(error "Directory already at root"))
;; We can't use names as in the pseudocode because we need to mutate
;; this specific pointer: Lisp copies by value so we won't be
;; mutating it if we use names.
(setf
;; CDR(ENTRY)
(cdr
;; ENTRY
(assoc
;; CAR(PAIR)
(car (car history))
;; CDR(PARENT)
(cdr (cadr history))
:test #'string=))
cwd)
(values
(cdr (cadr history))
(cdr history)))
(defun parse-ls (tokens) #|
"Converts the following TOKENS till a command (via TOKEN-CMDP) into a Finally, how do we go from some child all the way back to the root?
UP-TO-ROOT(CWD, HISTORY) {
IF (NULL (CAAR HISTORY)) <-- Means we're at the root
RETURN
ELSE
UP-DIRECTORY(CWD, HISTORY)
UP-TO-ROOT(CWD, HISTORY)
}
|#
(defun up-to-root (cwd history)
(if (null (caar history))
(values
cwd
history)
(multiple-value-bind (cwd history) (up-directory cwd history)
(up-to-root cwd history)))))
;;; Functions to parse the shell output
(progn
(defun parse-ls (tokens)
"Converts the following TOKENS till a command (via IS-TOKEN-CMD?) into a
directory structure i.e. an alist of string names by their content." directory structure i.e. an alist of string names by their content."
(loop for token in tokens (loop for token in tokens
until (token-cmdp token) until (is-token-cmd? token)
collect collect
(let ((size (car token)) (let ((size (car token))
(name (cadr token))) (name (cadr token)))
(if (string= size "dir") (if (string= size "dir")
`(,name . nil) `(,name . nil)
`(,name . ,(parse-integer size)))))) `(,name . ,(parse-integer size))))))
(defun parse-tokens (tokens) (defun parse-cd (name cwd history)
(if (token-cmdp (car tokens)) "Converts TOKEN into an action on CWD and HISTORY then returns them"
(cond (cond
((string= (cadar tokens) "ls") ((string= "/" name)
(up-to-root cwd history))
((string= ".." name)
(up-directory cwd history))
(t
(down-directory name cwd history))))
(defun parse-next-command (tokens cwd history)
"Parses the next command available in TOKENS, managing CWD and HISTORY.
Assumes CAR(TOKENS) is a command (see IS-TOKEN-CMD?)"
(cond
((null tokens)
(multiple-value-bind (cwd history) (up-to-root cwd history)
(values
tokens
cwd
history)))
((string= (cadar tokens) "ls")
(values
(till-next-cmd (cdr tokens))
(parse-ls (cdr tokens))
history))
((string= (cadar tokens) "cd")
(multiple-value-bind (cwd history) (parse-cd (caddar tokens) cwd history)
(values (values
(till-next-cmd (cdr tokens)) (till-next-cmd (cdr tokens))
(parse-ls (cdr tokens)))) cwd
((string= (cadar tokens) "cd") history)))))
;; TODO: Actually figure out what I should do here
(values (defun parse-all-tokens (tokens &optional cwd history)
(cdr tokens) "Recursively parse all TOKENS, returning (CWD . HISTORY) from the
nil))))) perspective of the root."
(if (null tokens)
(multiple-value-bind (cwd history) (up-to-root cwd history)
(values
cwd
history))
(multiple-value-bind (tokens cwd history)
(parse-next-command tokens cwd history)
(parse-all-tokens tokens cwd history)))))
(defun is-record-directory? (record)
"Checks if the record (output from parse-tokens) is a directory by
checking if the CDR is a list."
(listp (cdr record)))
(defun strip-file-names (record)
"Deletes file names, leaving the corresponding file sizes and
directory names (and the wrapping list structure)."
(if (is-record-directory? record)
(cons (car record)
(loop
for entry in (cdr record)
collect
(strip-file-names entry)))
(cdr record)))
(defun get-directory-sizes (dir &optional current)
"Computes the size of the directory dir, as well as all subdirectories.
Returns both the overall size of dir as well as an alist of sizes for
subdirectories."
(if (not (listp dir))
(values
dir
nil)
(let ((name (car dir))
(size (loop
for record in (cdr dir)
sum
(multiple-value-bind (size dir-alist) (get-directory-sizes record nil)
;; This is for the alist of subdirectory sizes
(setq current (concatenate 'list dir-alist current))
size))))
(values
size
(acons name size current)))))
(defparameter TOTAL-SIZE 70000000)
(defparameter REQUESTED-SIZE 30000000)
(multiple-value-bind (_ history)
(parse-all-tokens (tokenise-lines lines))
;; Since we're at the root, CAR(history) is the entire file structure
(declare (ignore _))
(multiple-value-bind (sum size-list)
(get-directory-sizes
(strip-file-names
;; normalize the name of root
(cons "/" (cdr (car history)))))
(format t "Round 1: ~s~%"
(reduce #'+ (loop for rec in size-list
if (< (cdr rec) 100000) ; only records that are at most 100000
collect (cdr rec))))
(let* ((root-size sum)
(free-space (- TOTAL-SIZE root-size))
(extra-space-req (- REQUESTED-SIZE free-space)))
(format t "Round 2: ~s~%"
(cdar
(sort (loop
for rec in size-list
;; only records whose size is at least the space required
if (>= (cdr rec) extra-space-req)
collect rec)
(lambda (rec1 rec2)
(< (cdr rec1) (cdr rec2)))))))))