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

published: 2022-12-09
last modified: 2023-01-21

https://vit.baisa.cz/notes/code/advent-of-code-2022/09/