09 Lisp
Lisp is the second-oldest high-level programming language (after Fortran). It is a functional programming language.
The algorithm was not trivial, but again, I spent roughly half of the time on learning the language and half on debugging my shitty code.
It is installed with
$ sudo apt install sbcl
on Ubuntu.
Part I Assignment
#!/usr/bin/sbcl --script
; tell sbcl to run in a script mode
; in script mode we need to import this manually
(require :uiop)
; http://dnaeon.github.io/generating-sequences-in-common-lisp/
(defun iota (n &key)
"iota using DO iteration macro"
(do
(
(i 0 (1+ i))
(item 1 (+ item 1))
(result nil (push item result))
)
((= i n) (nreverse result))
)
)
; global variable for storing unique positions
(defparameter *upos* ())
(defun ll (l)
"function for getting the last item of a list"
(car (cdr l))
)
(defun follow-head (hpos tpos)
"Follow head with tail; returns a new position of tail"
(if (and (= (car hpos) (car tpos)) (= (ll hpos) (ll tpos)))
tpos ; at the same position => do not move tail
(if (= (car hpos) (car tpos)) ;x coordinates are the same
(if (< 1 (abs (- (ll hpos) (ll tpos)))) ; need to move in y direction
(if (< 0 (- (ll hpos) (ll tpos)))
(list (car tpos) (+ 1 (ll tpos))) ; move y up one step
(list (car tpos) (- (ll tpos) 1)) ; move y down
)
tpos ; close enough => do not move
)
;x coordinates differ
(if (= (ll hpos) (ll tpos))
;y coordinates are the same
(if (< 1 (abs (- (car hpos) (car tpos))))
; need to move in x direction
(if (< 0 (- (car hpos) (car tpos)))
(list (+ 1 (car tpos)) (ll tpos)) ; move x right
(list (- (car tpos) 1) (ll tpos)) ; move x left
)
tpos ; close enough => do not move
)
;both x and y differs
(if
(or
(< 1 (abs (- (car hpos) (car tpos))))
(< 1 (abs (- (ll hpos) (ll tpos))))
)
(if (< 0 (- (ll hpos) (ll tpos)))
; head is below tail
(if (< 0 (- (car hpos) (car tpos)))
; head is to the right
(list (+ 1 (car tpos)) (+ 1 (ll tpos)))
; head is to the left
(list (- (car tpos) 1) (+ 1 (ll tpos)))
)
; head is above tail
(if (< 0 (- (car hpos) (car tpos)))
; head is to the right
(list (+ 1 (car tpos)) (- (ll tpos) 1))
; head is to the left
(list (- (car tpos) 1) (- (ll tpos) 1))
)
)
tpos ; close enough => do not move
)
)
)
)
)
(defun move-head (hpos move)
"Break up into singular moves"
(let
; local variables
(
; direction is the first character in the string
(dir (car (uiop:split-string move :separator " ")))
; number is the second part of the string, needs converting to integer
(num (parse-integer (car (reverse (uiop:split-string move :separator " ")))))
)
; something like switch
(cond
; we are moving up; break U X into X steps by one
((string= "R" dir)
(loop for x in (iota num) collect (list (+ x (car hpos)) (ll hpos))))
((string= "L" dir)
(loop for x in (iota num) collect (list (- (car hpos) x) (ll hpos))))
((string= "U" dir)
(loop for x in (iota num) collect (list (car hpos) (- (ll hpos) x))))
((string= "D" dir)
(loop for x in (iota num) collect (list (car hpos) (+ x (ll hpos)))))
)
)
)
(defun process-moves (hpos tpos moves)
"Reduces list of moves and updates state variables"
;hpos is head position, list (x y)
;tpos is tail position, list (x y)
;moves is a list of moves as read from the input file
(if (null moves) ; no more moves, return unique positions
; in that case return the number of unique positions
(length *upos*)
; wrapping more forms
(progn
; iterate over the individual steps (by 1)
(loop for s in (move-head hpos (car moves)) do
(progn
; update the head position
(setq hpos s)
; update the tail position by following the head
(setq tpos (follow-head hpos tpos))
; add the item if not already in the list
(pushnew tpos *upos* :test 'equal)
)
)
; run the process recursively by shortening the list of moves
(process-moves hpos tpos (cdr moves))
)
)
)
; start at positions (0 0) for head
; (0 0) for tail
; and split lines from the input file into a list of strings
; write out the resulting number
(write (process-moves (list 0 0) (list 0 0) (uiop:read-file-lines "09.input")))
; print new line
(terpri)
Part II
The rope is now made of 9 small ropes.
Fortunately, all the functions stay the same,
we just need to change the main function process-moves
and call it with different parameters.
The tail position is not a single value (position) but a list with 9 items: positions of the tails of those 9 short ropes.
We will follow only positions of the 9th rope.
(defun process-moves (hpos tposes moves)
(if (null moves)
(length *upos*)
(progn
(loop for s in (move-head hpos (car moves)) do
(progn
(setq hpos s)
; instead of updating one value (tpos)
; we update all 9 values sequentially
(loop for i in (iota 9)
; the first step is the same: follow head with the 1st rope's tail
do (if (= i 1)
; update 1st position in the list of tail positions
(setf
(nth (- i 1) tposes)
(follow-head hpos (nth (- i 1) tposes))
)
; update nth position according to the (n-1)th position
(setf
(nth (- i 1) tposes)
(follow-head (nth (- i 2) tposes) (nth (- i 1) tposes))
)
)
)
; only follow the 9th position
(pushnew (nth 8 tposes) *upos* :test 'equal)
)
)
(process-moves hpos tposes (cdr moves))
)
)
)
(write
(process-moves
(list 0 0)
; start with all positions at (0 0)
(loop for x in (iota 9) collect (list 0 0))
(uiop:read-file-lines "09.input")
)
)
What I learned
- Lisp expressions are case-insensitive.
- I would need to read some tutorial to better grasp the basic concepts (e.g. forms).
- The Common Lisp Cookbook is very handy.
- A newline is printed with
(terpri)
. Lol. - Parentheses are important and ubiquitous in Lisp.
- Jumping between opening and closing brackets in Vim with
%
is VERY handy.
published: 2022-12-09
last modified: 2023-01-21
https://vit.baisa.cz/notes/code/advent-of-code-2022/09/
last modified: 2023-01-21
https://vit.baisa.cz/notes/code/advent-of-code-2022/09/