Advent-of-Code/day7-directories-cleanup.lisp

285 lines
11 KiB
Common Lisp

;; https://adventofcode.com/2022/day/7
;; so, right now if i need to calculate sum of the size of files.
;; but, there could be duplicates.
;; so, i suppose i'll need to maintain the full filename?
;; so, track the current directory? and on $ ls read lines until next $
;; and put them into hashtable, under full name. ok. ugh
;;
;; so types of commands:
;; cd / - drop current path
;; cd <name> - add <name> to current path
;; cd .. - drop head of current path
;; ls - read in filenames, and add them with current path and size into hashmap
;;
;; then what? go through the hashmap and do calculation
;; with current task, i don't really need to process
;; dir d - names of the directories after $ ls
;; but, how to do pleasant parsing of the lines?
;; and how to store the state, if I'm reading things one line at a time?
;; possibly with DO macro again
;; i guess is could be one function that takes in line and returns new state
;; so state would be
;; - list of directories
;; - hashtable of (filename -> size)
;; i could split string, and try to do case pattern match
(ql:quickload 'alexandria)
(require 'cl-ppcre)
(let* ((line "dir a")
(line-list (cl-ppcre:split " " line)))
(cond ((equal '("$" "cd" "/") line-list) 'ROOT)
((equal '("$" "cd") (subseq line-list 0 2)) 'CD)
((equal '("$" "ls") (subseq line-list 0 2)) 'LS)
((equal '("dir") (subseq line-list 0 1)) 'DIR)
((integerp (parse-integer (first line-list))) 'FILE)
(t 'OTHER)))
;; CASE or COND
(subseq '(1 2 3) 0 2)
(integerp (parse-integer (first (cl-ppcre:split " " "14848514 b.txt"))))
(integerp "1")
(parse-integer "1")
(parse-integer "r")
(defun what (n)
(format t "~S~%" n))
(defun my-parse-line (line)
(let ((line-list (cl-ppcre:split " " line)))
(cond ((equal '("$" "cd" "/") line-list) 'ROOT)
((equal '("$" "cd") (subseq line-list 0 2)) 'CD)
((equal '("$" "ls") (subseq line-list 0 2)) 'LS)
((equal '("dir") (subseq line-list 0 1)) 'DIR)
((integerp (parse-integer (first line-list))) 'FILE)
(t 'OTHER))))
;; (integerp (parse-integer (first (cl-ppcre:split " " "$ cd /"))))
(mapcar #'my-parse-line '(
"$ cd /"
"$ ls"
"dir a"
"14848514 b.txt"
"8504156 c.dat"
"dir d"
"$ cd a"
))
;; next step is to utilize parse line to change state, i guess
(ql:quickload 'fset)
(defparameter *test-dir-list* ())
(defparameter *test-dir-set* (fset:empty-set))
(defparameter *test-file-sizes* (make-hash-table))
(let ((current-path-dirs '())
(file-sizes (make-hash-table))
(dirset (fset:empty-set)))
(labels ((my-full-file-name (dirs lastName)
(let* ((all-dirs (reverse (concatenate 'list (list lastName)
dirs))))
(format nil "~{~a~^/~}" all-dirs)))
(ingest-line (line)
(let ((line-list (cl-ppcre:split " " line)))
(cond ((equal '("$" "cd" "/")
line-list)
(setf current-path-dirs '()))
((equal '("$" "cd" "..")
line-list)
(pop current-path-dirs))
((equal '("$" "cd")
(subseq line-list 0 2))
(push (third line-list) current-path-dirs))
((equal '("$" "ls")
(subseq line-list 0 2))
;; do i need to do anything if just act on the file?
)
((equal '("dir")
(subseq line-list 0 1))
(setf dirset (fset:with dirset (my-full-file-name current-path-dirs (second line-list)))))
((integerp (parse-integer (first line-list)))
(let ((file-name (my-full-file-name current-path-dirs (second line-list)))
(file-size (parse-integer (first line-list))))
(setf (gethash file-name file-sizes) file-size)))))))
(with-open-file (in "day7-input.txt")
;; with-open-file (in "day7-test-input.txt")
(loop
for line = (read-line in nil nil)
while line
do (ingest-line line))))
(setf *test-dir-list* current-path-dirs)
(setf *test-file-sizes* file-sizes)
(setf *test-dir-set* dirset))
*test-dir-list*
*test-dir-set*
*test-file-sizes*
;; now let's iterate over keys in the sizes
(print (loop
for k being each hash-key of *test-file-sizes* using (hash-value v)
do (format t "~a => ~a~%" k v)))
;; ok, popping doesn't happen i think
;; fixed
;; now i want / in the beginning
;; now let's loop over dirs in set. and loop over keys in the hashtable
;; and sum values, and collect sum if it's < 100000
;;
;; well. it's for sure! should be different value
;; hmw/tsrqvpbq/dqbnbl/mbc/nqrdmvjm
;; is also a file
;; hmw/tsrqvpbq/dqbnbl/mbc/nqrdmvjm.vtq => 137158
;; but if all such directories are above 100k then they wouldn't matter.
;; ugh. let's sort lines
(fset:do-set (dirname *test-dir-set*)
(print dirname))
;; but I guess i'll need to just map with fset:image
;; and put sum of all files there
;; let's first return all files for which dir is prefix
(fset:image (lambda (dir) `(,dir imagined)) *test-dir-set*)
(print (fset:reduce #'+
(fset:filter (lambda (sumed)
(>= 100000 sumed))
(fset:image (lambda (dir)
(loop
for filename being each hash-key of *test-file-sizes* using (hash-value filesize)
when (alexandria:starts-with-subseq (concatenate 'string dir "/") filename )
summing filesize)) *test-dir-set*))))
;; oh, shit. it's set, so duplicates of the freaking same sizes get dropped.
;; so, i need to calculate differently
(print (let ((total-sum 0))
(fset:do-set (dirname *test-dir-set*)
(let ((dir-size (loop
for filename being each hash-key of *test-file-sizes* using (hash-value filesize)
when (alexandria:starts-with-subseq (concatenate 'string dirname "/")
filename )
summing filesize)))
(if (> 100000 dir-size)
(incf total-sum dir-size))))
total-sum))
;; crap
;; with / 159935456
;; without / 160367201
;; but if i'm not filtering, then they should be same? wtf
;; or like i'm not counting top level?
;; wow. summing instead of collect
;; now filter those that less than 10000 and sum again
;; ok, i guess.
;; now with a different file?
;;
;; wrong answer 1265478
;;
;; oh, you tricky people
;; there's dir.file that matches as prefix. ugh
;; but then my value should be more that required? ugh twice
(fset:image (lambda (dir)
(loop
for filename being each hash-key of *test-file-sizes* using (hash-value filesize)
when (alexandria:starts-with-subseq (concatenate 'string dir "/")
filename )
collect (list filename filesize)))
*test-dir-set*)
(string-prefix-p "a" "aab")
(alexandria:starts-with "a" "aab")
(alexandria:starts-with-subseq "a/" "a/ab")
(alexandria:starts-with-subseq "" "aab")
;; ok. hello
;; error - tried to use FLET* for allowing recursion, again went to stackoverflow.
;; oh. i need to sum over dirs, ugh.
;; now that's more complicated now.
;; so. then maybe i'd want to register DIRs in another hashtable?
;; and then for each dir collect all files that start with that prefix and sum?
;; would be O(n^2) but ok, i guess
(defparameter *test-set* (fset:empty-set))
(fset:with *test-set* "hello") ; already uses #'EQUAL , cool
(concatenate 'string "hello" "another" "yay")
;; yay!
;; thank you https://stackoverflow.com/questions/5457346/lisp-function-to-concatenate-a-list-of-strings
(format nil "~{~a~^/~}" '("hello" "this" "one"))
(concatenate 'list '("name") '("efim" "home"))
(defun my-full-file-name (dirs lastName)
(let* ((all-dirs (reverse (concatenate 'list (list lastName) dirs))))
(format nil "/~{~a~^/~}" all-dirs)))
(my-full-file-name '("eifm" "home") "Documents")
;; well. ok. now what? maybe i don't need to count root anyway?
;; ugh. can i build a tree then?
;; have pointer? ugh/
;;; so, just start anew? ugh
;;; TO THE PART 2
;; need to find /smallest/ directory to delete, so that free space would be 30000000 our of 70000000
;; so I need "total sum of all"
;; copying over code to calculate sum
;; oh, shit. it's set, so duplicates of the freaking same sizes get dropped.
;; so, i need to calculate differently
(print (let ((total-sum 0))
(fset:do-set (dirname *test-dir-set*)
(let ((dir-size (loop
for filename being each hash-key of *test-file-sizes* using (hash-value filesize)
when (alexandria:starts-with-subseq (concatenate 'string dirname "/")
filename )
summing filesize)))
(incf total-sum dir-size)))
total-sum))
;; 166378694 ; oh that's sum with the duplicates. ugh
;; (- 70000000 166378694)
;; i need direct sum, just over the lines.
;; luckily this should be easier? or no? well, sum over the file-hashtable, there are all unique
(print (loop
for filename being each hash-key of *test-file-sizes* using (hash-value filesize)
summing filesize))
(print (- 70000000 44795677))
;; wait, no. I need 30000000
;; so, that's my free memory right now: 25204323
;; to free is
(print (- 30000000 25204323))
;; to free 4795677
;; now i need for all dir sizes find one that is more than than, but the smallest
(fset:filter (lambda (item)
(< 4795677 (first item))) (fset:image (lambda (dir)
(list (loop
for filename being each hash-key of *test-file-sizes* using (hash-value filesize)
when (alexandria:starts-with-subseq (concatenate 'string dir "/")
filename )
summing filesize) dir))
*test-dir-set*))
;; and it shows sorted, and the first one - is the dir to be deleted.
;; cooooool. it was very hard.
;;
;; what are lessons:
;; image (mapping) on set discards duplicates. lot's of time spent debugging this
;; also - i need to learn threading.
;; maybe that's the way to make code simpler.
;; but then i won't be able to call it iteratevly? i really still should.
;;
;; so, go to Alexandria for threading, and for string things.
;; and maybe read about functions for hashmaps and such. ugh.