UP | HOME
Land of Lisp

Zhao Wei

How can man die better than facing fearful odds, for the ashes of his fathers and the temples of his Gods? -- By Horatius.

Common-lisp Cheatsheet

1. Introduction

This document is my note for using Common-Lisp(CL) to solve leetcode problems. It covers very basic usage of CL to solve algorithm tasks.

2. Basic data structure

2.1. Array

;; Createa one-dimentional array. 
(setf myarray (make-array '(5))) ; createed a one dimensional array with length 5
(setf myarray (make-array '5 :initial-element 10)) ; created a one dimensional array with length 5 and inital value is 10. When the dimension is one, we could just use one integer to specify instead of a list of one element.
(setf myarray (make-array '(3) :initial-contents '(1 2 3))) ; created a one dimensional array with length 3 and filled it with 1, 2, and 3.

;; Create array with multiple dimensions
(setf myarray (make-array '(4 3) :initial-element -1)) ; created a two dimensional array with row = 4, and column is 3

2.2. Vector as one-dimensional array (its size is FIXED)

To convoniently create one-dimensional array, we use vector:

;; A simple vector is a simple array of one dimension that can contain elements of any type.
(vector 1 "zw" 'b)
(setf myvec (vector 1 2 3))
;; access vector element
(aref myvec 1)
(svref myvec 0) ; faster
  • It consistutes the type vector which is a subtype of array.
  • While it takes O(1) to access element in vector, it takes O(n) to add a new element to the front of a vector.
  • To create vector that could be extended, we must use array construct. See dynamic array.

2.3. Dynamic array

;; Create dynamic array: 
(setf myarray (make-array 0 :fill-pointer 0 :adjustable t)) ; created a one dimentional array with size 0
(vector-push-extend 100 myarray)
(vector-push-extend 101 myarray)
myarray
  • adjustable make our array’s capacity increase if it is enough.
  • The fill pointer is a non-negative integer no larger than the total number of elements in the vector (as returned by array-dimension); it is the number of ``active’’ or ``filled-in’’ elements in the vector.
  • Only vectors (one-dimensional arrays) may have fill pointers.
100 101

2.4. Dynamic 2D array

;; How to create dynamic 2D array: means each row can have different number of elements
(defun make-row-resizeable-array (rows max-columns)
  (make-array rows
              :initial-contents (loop for i from 0 below rows
                                     collect (make-array max-columns :fill-pointer 0))))

(let ((array (make-row-resizeable-array 6 5)))
  (vector-push 'x (aref array 2))
  (vector-push 'y (aref array 2))
  (vector-push 'z (aref array 2))
  (vector-push 'a (aref array 3))
  (vector-push 'b (aref array 3))
  (let ((row 3)
        (col 1))
    (aref (aref array row) col)))

2.5. List

2.6. Sequence

Sequence is include both list and one-dimensional array(vector).

(setf myarr (make-array 0 :adjustable t :fill-pointer 0))
(dotimes (i 5)
  (vector-push-extend i myarr))

(setf myvec (vector 0 1 2 3 4))
(setf mylst (list 0 1 2 3 4))

myarr
myvec
mylst

(length myarr)
(length myvec)
(length mylst)

(reverse myarr)
(reverse myvec)
(reverse mylst)

(setf myarr02 (subseq myarr 3))
myarr02
myarr ; not changed

;; setf with subset to replace subseq in sequence
(setf (subseq myvec 3 5) #(-1 -2 -3 -4 -5))
myvec ; => #(0 1 2 -1 -2) notice the additional values are cut off.

2.7. String and Characters

  • Strings are vectors of characters.
  • Because strings are vectors, both sequence functions and array functions work on them.
  • A single char ’x’ is denoted as #\x.
  • Examples

    ;; build string
    ;; 1) the most general way is to use format
    (setf str (format nil "~A or ~A" "true" "false"))
    ;; 2) join several strings together
    (setf str01 (concatenate 'string str " is not a question"))
    
    ;; a function which get the second word from a sentence
    (defun second-word (str)
      (let ((p1 (+ (position #\  str) 1)))
        (if (char= #\ (char str p1)) ; char is faster than aref for string 
            (second-word (subseq str (1+ p1)))
            (subseq str p1 (position #\  str :start p1)))))
    (second-word "one   two  tree.")
    
    • char
    • char=
    • position

2.8. Hashtable(Map)

  1. create hashtable, create key and values

    ;; How to create an empty map?
    (let ((ht (make-hash-table)))
      (multiple-value-bind (val ok) (gethash "zw" ht)
        (if (not ok)
            (format t "no record~%")
            (format t "val: ~A~%" val))))
    
    ;; How to add element into map?
    (let ((ht (make-hash-table)))
      (setf (gethash 'zw ht) "pdbh")
      (multiple-value-bind (val ok) (gethash 'zw ht)
        (if (not ok)
            (format t "no record~%")
            (format t "val: ~A~%" val))))
    
    ;; use :test to specify equality test function, by defaut it is "eql"
    (let ((ht (make-hash-table :test #'equal)))
      (setf (gethash "zw" ht) "pdbh")
      (multiple-value-bind (val ok) (gethash "zw" ht)
        (if (not ok)
            (format t "no record~%")
            (format t "val: ~A~%" val))))
    
    ;; How to decide if a key is valid in map?
    
  2. Remove member from hashtable

    (let ((ht (make-hash-table)))
      (setf (gethash 'zw ht) "pdbh")
      (format t "key: ~A, value: ~A~%" 'zw (gethash 'zw ht))
      (remhash 'zw ht)
      (format t "key: ~A, value: ~A~%" 'zw (gethash 'zw ht)))
    
  3. Loop through hashtable

    (let ((ht (make-hash-table)))
      (setf (gethash 1 ht) "pdbh")
      (setf (gethash 2 ht) "wei")
      (setf (gethash 3 ht) "fox")
    
      (maphash #'(lambda (k v)
                   (format t "~A = ~A~%" k v))
               ht)
    
      (terpri)
      (let ((keys ()))
        (maphash #'(lambda (k v)
                     (format t "~A = ~A~%" k v)
                     ;; (setf keys (cons k keys))
                     (push k keys))
                 ht)
        (format t "~A~%" keys)
        (setf keys (sort keys #'>))
        (format t "~A~%" keys)
        (terpri)
    
        (mapcar #'(lambda (x)
                    (format t "~A = ~A~%" x (gethash x ht)))
                keys)))
    
    ;; sort hashtable
    ;; 1) collect keys
    (sort '(2 1 3) #'<)
    ;; 2) loop through keys and retrieve value 
    

2.9. Struct

  1. A basic example

    (defstruct point
      x
      y) ; type order:  point -> structure -> atom -> t
    
    (setf p (make-point :x 0 :y 0)) ; make-point 
    (point-x p) ;access function
    
    (point-p p) ; using point-p to test whether something is a point
    (typep p 'point) ; using general-purpose function to test its type
    
    • By default, the default value is NIL.
  2. specify default values for structure field

    (defstruct polemic
      (type (progn
              (format t "what kind of polemic was it?")
              (read)))
      (effect nil))
    
  3. control how a structure is displayed and the prefix used in the name of the access function it creates

    (defstruct (point (:conc-name p)
                      (:print-function print-point))
      (x 0)
      (y 0))
    
    ;; for print-function, the third argument could be ignored for most of time
    (defun print-point (p stream depth)
      (format stream "#<~A, ~A>" (px p) (py p)))
    (setf p (make-point))
    
    • We specify the default value.
    • We specify the access-prefix.
    • We specify the print-function which must takes 3 argument.
  4. Related questions
    • How to create custom struct with zero value as simple as possible?
      • The default value is NIL.
      • We could specify the inital value when defstruct.
    • How to access struct’s attributes
      • defstruct create accessor functions automatically.
      • By default, it is the xxx-yy format, where xxx is the struct name, and yy is the attribute name.

2.10. Class

(defclass rectangle ()
  (height width))

(defclass circle ()
  (radius))

(defmethod area ((x rectangle))
  (* (slot-value x 'height)
     (slot-value x 'width)))

(defmethod area ((x circle))
  (* pi
     (expt (slot-value x 'radius) 2)))

(let ((r (make-instance 'rectangle)))
  (setf (slot-value r 'height) 20)
  (setf (slot-value r 'width) 30)
  (area r))

3. HOWTOs

3.1. How to execute a function from .lisp file on terminal?

  • Suppose we have a file called playground.lisp.

    (defun main ()
      (print 'hello-world))
    
  • Execute main function as

    sbcl --noinform --load playground --eval '(progn (main) (sb-ext:quit))'
    

3.2. How to check equality?

  • eq return true only if two object are implementationnally identical.
  • eql is the same as eq, except that if the arguments are characters or numbers or numbers of the same type then their values are compared. It is the default comparision predicate when need equality check in common-lisp code (such :test).
  • equal, The equal predicate is true if its arguments are structurally similar (isomorphic) objects. A rough rule of thumb is that two objects are equal if and only if their printed representations are the same.
  • equalp return true if its argument would print the same with. case-insensitive
  • Some cases
    1. numbers

      (eql 5.0 5)
      
      (equal 5 5.0)
      
      (equalp 5 5.0)
      
      (= 5 5.0)
      
    2. List, String and Vector

      ;; list 
      (let ((l1 (list 1 2 3))
            (l2 (list 1 2 3)))
        (format t "l1 is l2: ~A~%" (eql l1 l2)) ; nil 
        (format t "l1 has same content as l2: ~A~%" (equal l1 l2)) ; t
        (format t "l1 has same content as l2: ~A~%" (equalp l1 l2))) ; t
      
      ;; string 
      (let ((s1 "abc")
            (s2 "ABC"))
        (format t "s1 is s2: ~A~%" (eql s1 s2)) ; nil 
        (format t "s1 has same content as s2: ~A~%" (equal s1 s2)) ; nil 
        (format t "s1 has same content as s2: ~A~%" (equalp s1 s2))) ; t
      
      ;; we have to use equalp to compare contents for list and array
      (let ((arr1 (make-array 3 :initial-contents (list 1 2 3)))
            (arr2 (make-array 3 :initial-contents (list 1 2 3))))
        (format t "arr1 is arr2: ~A~%" (eql arr1 arr2)) ; nil 
        (format t "arr1 has same content as arr2: ~A~%" (equal arr1 arr2)) ; nil 
        (format t "arr1 has same content as arr2: ~A~%" (equalp arr1 arr2))) ; t
      
      (let ((v1 (vector 1 2 3))
            (v2 (vector 1 2 3)))
        (format t "v1 is v2: ~A~%" (eql v1 v2)) ; nil
        (format t "v1 has same content as v2: ~A~%" (equal v1 v2)) ; nil 
        (format t "v1 has same content as v2: ~A~%" (equalp v1 v2))) ; t
      

3.3. How to return multiple values?

(defun return-values ()
  (values "zhao" 86 t)) ; use values to return multiple values 

(defun use-multiple-values ()
  (multiple-value-bind (x y z) (return-values) ; use multiple-value-bind to receive
    (list x y z)))


3.4. How to loop over a given list?

(loop for i in '(1 2 3 4 5) collect (* i i))

3.5. How to loop over a given vector?

(loop for i in (coerce (vector 1 2 3 4 5) 'list) collect (+ i i))

3.6. How to loop over array?

(let ((myarr (make-array 5 :initial-contents '(1 2 3 4 5))))
  (loop for i from 0 to (1- (length myarr))
        do (progn
             (format t "myarr[~A]=~A~%" i (aref myarr i)))))

(loop for i in (coerce (make-array 5 :initial-contents '(1 2 3 4 5))
                       'list)
      collect (* 3 i))

3.7. How to process string?

  1. process string one character a time (loop over string)

    (loop for c across "abcd"
          collect c)
    (loop for c in (coerce "abcd" 'list)
          collect c)
    

3.8. How to sort array?

(setf myarr (make-array 5 :initial-contents '(5 4 3 1 2)))

(defun swap-arr (arr i j)
  (let ((tmp (aref arr i)))
    (setf (aref arr i) (aref arr j))
    (setf (aref arr j) tmp)))

(defun qsort (arr fnp)
  (when (> (length arr) 0)
    (labels ((qsort-partition (arr p r)
               (let* ((i (1- p))
                      (x (aref arr r)))
                 (loop for j from p to r
                       do (when (apply fnp (list (aref arr j) x))
                            (setf i (1+ i))
                            (swap-arr arr i j)))
                 (swap-arr arr (1+ i) r)
                 (1+ i)))
             (qsort-aux (arr p r)
               (when (< p r)
                 (let ((i (qsort-partition arr p r)))
                   (qsort-aux arr p (1- i))
                   (qsort-aux arr (1+ i) r)))))
      (qsort-aux arr 0 (1- (length arr))))))

(let ((myarr (make-array 5 :initial-contents '(3 1 2 5 4))))
  (terpri)
  (format t "~A~%" myarr)
  (qsort myarr #'<)
  (format t "~A~%" myarr)
  myarr)

3.9. How to sort map by its key?

3.10. How to add one element to the head of list?

(setf lst '(1 2 3))
(cons 0 lst)
  • Use cons to add new element to the head of a list.

3.11. How to append elements to the end of a list?

(setf lst '(1 2 3))
(setf lst1 (append lst '(100 101)))
(setf lst2 (append '(100 101) lst))

(setf (nth 2 lst2) 1000)
lst2
lst1

(append '(1 2 3) '(4 5 6) '(7 8 9))
  • append creates copy of sequence.

3.12. How to append elements to vector?

(setf vec #(1 2 3))

(concatenate 'vector vec (vector 100 101))
(concatenate 'vector (vector 100 101) vec)
  • We can think of concatenate as a general append for sequence.

3.13. How to convert list to vector and vector to list?

(coerce '(1 2 3) 'vector)
(concatenate 'list  #(1 2 3))

(coerce #(1 2 3) 'list)
(concatenate 'vector '(1 2 3))

3.14. How to concatenate sequence?

(concatenate 'list '(1 2) (vector 3 4 5))

(concatenate 'vector '(a b) #(c d e))
(concatenate 'vector '(a b) (vector 'c 'd 'e) #(f g))

(concatenate 'string "hello" " " "world")
  • The type must be one of these three types: list, vector and string (not including array).

3.15. How to slice a sequence then concatenate the result with other sequence?

  1. subseq creates copy of sequence

    (setf myvec #(a b c d e f))
    (concatenate 'vector (subseq myvec 3) (vector 1 2 3))
    
    ;; Here, we create a slice from myvec set it to myseq
    (setf myseq (subseq myvec 4))
    
    (setf (aref myseq 1) "100")
    myseq
    myvec
    
    • Remember sequence includes vector(one-dimensional array) and list.
    • subseq creates copy. We modify the myseq, and the its original vector doesn’t change
  2. In case we want to achieve the same passager for different buses effect:

    (ql:quickload :rutils)
    
    (setf myvec #(a b c d e f))
    (setf mypart (rtl:slice myvec 4))
    (setf (aref mypart 1) "100")
    mypart
    myvec
    
    • rtl:slice (from rutils package) create “same passager in different bus”.

3.16. How to create a copy of sequence?

(defun copy-array (arr)
  (setf result-array (subseq arr 0)))
(let ((vec (vector 1 2 3 4 5)))
  (setf cp-vec (copy-array vec))
  cp-vec)
  • Use subseq.

3.17. How to map over vector?

;;;; Dynamic Vectors
(defun map-vec (fn vec)
  (let ((result-vec (make-array (length vec))))
    (dotimes (i (length vec))
      (setf (aref result-vec i) (funcall fn (aref vec i))))
    result-vec))

(defun map-vec (fn vec)
  (let ((result-vec (make-array (length vec))))
    (dotimes (i (length vec))
      (setf (aref result-vec i) (apply fn (list (aref vec i)))))
    result-vec))

;; use build-in map
(defun map-vec (fn vec)
  (let ((result-vec (map 'vector fn vec)))
    result-vec))
  • Notice that apply vs funcall: apply needs the arguments to be a list.

3.18. How to do tail-recusive Fib?

(defun fib (n)
  (labels ((fib-aux (n a b)
             (if (= n 0)
                 a
                 (fib-aux (1- n) b (+ a b)))))
    (fib-aux n 0 1)))

(time (fib 4200))

3.19. How to implement queue using list?

;; our queue will have the following list structure:
;; ((a b c) c)
(defun make-queue ()
  (cons nil nil))

(defun enqueue (obj q)
  (if (null (car q))
      (setf (cdr q) (setf (car q) (list obj)))
      (setf (cdr (cdr q)) (list obj)
            (cdr q) (cdr (cdr q))))
  (car q))

(defun dequeue (q)
  (pop (car q)))

3.20. How to generate a sequence from x to y with step z?

(defun range (max &key (min 0) (step 1))
  (loop for i from min below max by step collect i))

(range 10)

3.21. How to read file line by line

(defun get-file (filename)
  (with-open-file (stream filename)
    (loop for line = (read-line stream nil)
          while line
          collect line)))

(setf speed-records-list (get-file "~/code/go-projects/algorithm-in-go/interview_problems/optiver/maximum_average_speed/speed.txt"))

3.22. How to replace a part of sequence other sequence

(setf myvec (vector 0 1 2 3 4))
(setf (subseq myvec 3 5) #(-1 -2 -3 -4 -5))
myvec ;=> #(0 1 2 -1 -2)


(replace "abcdefghij" "0123456789" :start1 4 :end1 7 :start2 4)

(setf myvec (vector 0 1 2 3 4 5))
(replace myvec (vector 'a 'b 'c) :start1 2 :end1 4 :start2 1) ;=> #(0 1 B C 4 5)

4. Be careful

  1. sort is destructive!.

    (let* ((keys '(3 4 1 2 5))
           (backup-keys (copy-list keys)))
      (setf new-keys  (sort keys #'<))
      (format t "~A~%" keys)
      (format t "~A~%" backup-keys)
      (format t "~A~%" new-keys))
    
    
    (let* ((keys (vector 1 2 3 4 5))
           (backup-keys (copy-list keys)))
      (setf new-keys  (sort keys #'<))
      (format t "~A~%" keys)
      (format t "~A~%" backup-keys)
      (format t "~A~%" new-keys))
    
    • The original sequence will be modified.
    • If we want to keep original one, use copy-list.
  2. map vs mapcar

    (mapcar #'1+ (list 1 2 3 4)) ; => (2 3 4 5)
    
    (map 'vector #'1+ (list 1 2 3 4)) ;=>#(2 3 4 5)
    (map 'list #'1+ (list 1 2 3 4)) ; => (2 3 4 5)
    (map 'string #'(lambda (x)
                     (if (oddp x)
                         #\A
                         #\B))
         (list 1 2 3 4)) ; => "ABAB"
    
    • map works on any sequence while mapcar works on only list
  3. while will return nil if no case matched!

5. Practise using leetcode problems