Nyquist / XLISP 2.0 - Contents | Tutorials | Examples | Reference

# Lists

Lists are also Sequences.

- print-cons - print lists as conses
- dolist* - a dolist version that can iterate dotted lists
- List Accessors

- cl:list:unary-accessor
- cl:list:binary-accessor

- List Membership

- cl:member - [Function] - test for membership in lists and sub-elements
- cl:member-if - [Function] - search for the first element matching a predicate
- cl:member-if-not - [Function] - search for the first element not matching a predicate

- Non-destructive Removal

- cl:remove
- cl:remove-if
- cl:remove-if-not

- Destructive Removal = Deletion

- cl:delete
- cl:delete-if
- cl:delete-if-not

- Lists as Sets

- cl:pushnew - [Macro] -
- cl:union - [Function]
- cl:intersection - [Function]
- cl:set-difference - [Function]
- cl:set-exclusive-or - [Function]
- cl:subsetp - [Function]

## print-cons

    (defun print-cons (item)
      (labels ((cons-string (item)
                 (case (type-of item)
                   (array      (let ((end (length item))
                                     (result ""))
                                 (dotimes (index end)
                                   (let ((string (cons-string (aref item index))))
                                     (setq result
                                       (if (eql 0 index)
                                           (format nil "#(~a" string)
                                           (format nil "~a ~a" result string)))))
                                 (format nil "~a)" result)))
                   (character  (format nil "~s" item))
                   (cons       (format nil "(~a . ~a)"
                                       (cons-string (car item))
                                       (cons-string (cdr item))))
                   (string     (format nil "\"~a\"" item))
                   (t          item))))
        (format t "~a~%" (cons-string item))
        item))

Examples:

    > (print-cons '(1 2 3))
    (1 . (2 . (3 . NIL)))
    (1 2 3)

The 'print-cons' function is useful for debugging association lists, where print often fails to display the correct layout:

    > (print-cons (cons '((1 . 2) (3 . 4)) '((a . b) (c . d))))
    (((1 . 2) . ((3 . 4) . NIL)) . ((A . B) . ((C . D) . NIL)))
    (((1 . 2) (3 . 4)) (A . B) (C . D))  ; <- output of PRINT

Do not think that print is bad, it saves you from reading things like this:

    > (print-cons '(defun hello-world ()
                     (print "Hello World!")))
    (DEFUN . (HELLO-WORLD . (NIL . ((PRINT . ("Hello World!" . NIL)) . NIL))))
    (DEFUN HELLO-WORLD NIL (PRINT "Hello World!"))  ; <- output of PRINT

Test this if you don't believe:

    > (DEFUN . (HELLO-WORLD . (NIL . ((PRINT . ("Hello World!" . NIL)) . NIL))))
    HELLO-WORLD

    > (hello-world)
    "Hello World!"

Back to top

## dolist*

A dolist version that can iterate dotted lists:

    (defmacro dolist* (fargs &rest body)
      (let ((list (gensym)))
        `(let ((,list ,(second fargs)))
           (if (not (listp ,list))
               (error "not a list" ,list)
               (do ((,(first fargs) (first ,list)
                                    (if (consp ,list) (first ,list) ,list)))
                   ((null ,list))
                 (setq ,list (and (consp ,list) (rest ,list)))
                 ,@body)))))

    (dolist  (i '(1 2 3)) (print i))    ; prints 1 2 3
    (dolist* (i '(1 2 3)) (print i))    ; prints 1 2 3

    (dolist  (i '(1 2 . 3)) (print i))  ; prints 1 2
    (dolist* (i '(1 2 . 3)) (print i))  ; prints 1 2 3

Back to top

## cl:member

XLISP already has the member function to for search elements in lists:

(member expr list [{:test | :test-not} test])
  expr - the expression to find [an atom or a list]
  list - the list to search
  test - optional test function, default is eql
  returns - the remainder of the list starting with expr

The 'cl:member' function provides an additional :key argument for accessing sub-elements in the list:

(cl:member expr list [{:test | :test-not} test :key key])
  expr - the expression to find [an atom or a list]
  list - the list to search
  test - an optional test function, default is eql
  key - an optional accessor function for sub-elements in the list
  returns - the remainder of the list starting with expr

    (defun cl:member (expr list &key test test-not key)
      (and test test-not (error "both :TEST and :TEST-NOT specified"))
      (if key
          (cond (test
                 (member expr list
                   :test #'(lambda (x y)
                             (funcall test x (funcall key y)))))
                (test-not
                 (member expr list
                   :test-not #'(lambda (x y)
                                 (funcall test-not x (funcall key y)))))
                (t (member expr list
                     :test #'(lambda (x y)
                               (eql x (funcall key y))))))
          (cond (test     (member expr list :test test))
                (test-not (member expr list :test-not test-not))
                (t        (member expr list)))))

Test if the number 4 matches the first or the second element in several sublists:

    (cl:member 4 '((1 2) (3 4) (5 6)) :key #'first)   => NIL            ; no match
    (cl:member 4 '((1 2) (3 4) (5 6)) :key #'second)  => ((3 4) (5 6))  ; number found

Subtle differences between XLISP and Common Lisp:

    ;; Lisp Form              XLISP         Common Lisp
    (member 1 '(1 2 . 3))  => (1 2 . 3)  => (1 2 . 3)
    (member 2 '(1 2 . 3))  => (2 . 3)    => (2 . 3)
    (member 3 '(1 2 . 3))  => NIL        => error: not a proper list

Here is a 'cl:member' version that behaves error-conform to Common Lisp but produces an unintelligible backtrace in case of Lisp errors. I also have found no way how to macroexpand macrolets, so debugging this function is a real pain.

    (defun cl:member (expr list &key test test-not key)
      (and test test-not (error "both :TEST and :TEST-NOT specified"))
      (macrolet ((internal-loop (list)
        `(do ()
             ;; termination test
             ((or (not (consp list))
                  ,(if key
                       (cond (test     `(funcall ,test ,expr (funcall ,key (car list))))
                             (test-not `(not (funcall ,test ,expr (funcall ,key (car list)))))
                             (t        `(eql ,expr (funcall ,key (car list)))))
                       (cond (test     `(funcall ,test ,expr (car list)))
                             (test-not `(not (funcall ,test ,expr (car list))))
                             (t        `(eql ,expr (car list))))))
              ;; return value
              (if (not (listp list))
                  (error "a proper list must not end with" list)
                  list))
            ;; body
            (setq list (cdr list)))))
         (internal-loop list)))

Back to top

## cl:member-if

Here are two functions to search for elements that satisfy a given predicate:

(member-if predicate list [:key key])
  predicate - a test function with one argument
  list - the list to search
  key - optional accessor function for sub-elements in the list
  returns - the remainder of the list starting with the first matching element

    (defun cl:member-if-not (predicate list &key key)
      (member nil list :test (if key
                                 #'(lambda (x y)
                                     (funcall predicate (funcall key y)))
                                 #'(lambda (x y)
                                     (funcall predicate y)))))))

Back to top

## cl:member-if-not

(member-if-not predicate list [:key key])
  predicate - a test function with one argument
  list - the list to search
  key - optional accessor function for sub-elements in the list
  returns - the remainder of the list starting with the first non-matching element

    (defun cl:member-if-not (predicate list &key key)
      (member nil list :test-not (if key
                                     #'(lambda (x y)
                                         (funcall predicate (funcall key y)))
                                     #'(lambda (x y)
                                         (funcall predicate y)))))))

Examples:

    (cl:member-if     #'plusp  '(-2 -1 0 1 2))  => (1 2)    ; 1 = first positive number
    (cl:member-if-not #'minusp '(-2 -1 0 1 2))  => (0 1 2)  ; 0 = first non-negative number

More test functions see Predicates.

Back to top

## cl:list:accessor

The 'lists as sets' functions have common :test, :test-not and :key parameters:

    (defmacro cl:list:accessor (test test-not &optional key)
      (if (and test test-not)
          (error "both :TEST and :TEST-NOT specified"))
          (if key
              (cond (test     `(lambda (x y)
                                 (funcall ,test (funcall ,key x)
                                                (funcall ,key y))))
                    (test-not `(lambda (x y)
                                 (not (funcall ,test-not (funcall ,key x)
                                                         (funcall ,key y)))))
                    (t        `(lambda (x y)
                                 (eql (funcall ,key x) (funcall ,key y)))))
              (cond (test     `(lambda (x y)
                                 (funcall ,test x y)))
                    (test-not `(lambda (x y)
                                 (not (funcall ,test-not x y))))
                    (t        `(lambda (x y)
                                 (eql x y))))))

Back to top

## cl:pushnew

Back to top

## cl:union

(union list1 list2 [{:test | :test-not} test :key key])
  listN - a list of symbols or numbers
  returns - the union of list1 and list2

    (defun union (a b)
      (let (result)
        (dolist (element a)
          (unless (member element result)
            (push element result)))
        (dolist (element b)
          (unless (member element result)
            (push element result)))
        result))

The 'cl:union' function returns a list that contains every element that occurs in either 'list1' or 'list2'.

Back to top

## cl:intersection

(intersection list1 list2 [{:test | :test-not} test :key key])
  listN - a list of symbols or numbers
  returns - the intersection of list1 and list2

    (defun intersection (a b)
      (let (result)
        (dolist (element a)
          (when (member element b)
            (push element result)))
        result))

Back to top

## cl:set-difference

(set-difference list1 list2)
  listN - a list of symbols or numbers
  returns - the set-difference of list1 and list2

    (defun set-difference (a b)
      (remove-if #'(lambda (element)
                     (member element b))
                 a))

An element of list1 appears in the result if and only if it does not match any element of list2.

    (set-difference '(1 2 3) '(2 3 4)) => (1)

Back to top

## cl:set-exclusive-or

The result contains precisely those elements of list1 and list2 that appear in no matching pair.

    (set-exclusive-or '(1 2 3) '(2 3 4)) => (1 4)

Back to top

## cl:subsetp

(subsetp list1 list2)
  listN - a list of symbols or numbers
  returns - T if list1 is a subset of list2, NIL otherwise

    (defun subsetp (a b)
      (let ((result t))
        (dolist (element a)
          (when (not (member element b)
            (setf result nil)
            (return))))
        result))

Back to top

Nyquist / XLISP 2.0 - Contents | Tutorials | Examples | Reference