list functions TDDC65 Artificial intelligence and Lisp Lecture 2 in Lisp Recursion, symbols and lists (chapter 4, 5, 6 Haraldssons book) local variables and local functions recursion over sequences * patterns * recursive / iterative process * double recursion * back tracking the function cons graphical representation of lists the list as a binary tree traversing formulas Test of equality (= 5 5) - numbers (eq 'a 'a) - symbols (or pointers) (eql 5 5) - atoms (eql #\a #\a) (eql 'a 'a) (equal '(a b c) '(a b c)) - lists Test of data types (numberp 5) - number? (symbolp 'a) - symbol? (atom 5) - atom? = not a list? (atom 'a) (listp '(a b c)) - list? To create lists The primitive (cons 'a '(b c)) => (a b c) Concatenate 2 (or more) lists (append '(a b) '(x y)) => (a b x y) Create a list of a fixed number of elements (list 'a (1+ 4) (first '(b c))) => (a 5 b) Constant list '(a b c d) or (quote (a b c d)) Observe the difference. What is? (cons '(1 2) '(a b)) =>? (append '(1 2) '(a b)) =>? (list '(1 2) '(a b)) =>? Common Lisp functions for lists Some of them: Is an element on a list? (member 'x '(a b x c)) => true Remove all occurrences of an element (remove 'q '(a b q r q)) => (a b r) Reverse the elements on a list (reverse '(a b c)) => (c b a) Replace all occurrences of an element (subst 'new-q 'q '(a q (b q))) => (a new-q (b new-q)) Find the last part of a list (last '(ett två tre)) => (tre) Find the n+1 element (nth '(a b c d) 2) => c
How to add a new last element? Put five last on the list (one two three four). Example: (append '(one two three four) (list 'five)) => (one two three four five) Define a function add-to-the-end (defun add-to-the-end (e l) (append l (list e))) What happens here? (setq number-list '(one two three four)) (add-to-the-end 'five number-list) => (one two three four five) number-list =>? Local variables (let ((var expr) (var expr)...) expr) Avoid double calculations: g(x) = sin(f(x)) + cos(f(x)) (defun g (x) (let ((f-value (f x))) (+ (sin f-value) (cos f-value))) After the let-expression the binding disappear between the variable and value. Introduce a local name for a value: (let ((vocals (a e i o u å ä ö)))... (member (first l) vocals)... ) Local functions (labels ((fn (argument) body)) (fn (argument) body))...) expression) Example: f(x,y) = x!/y 2 (defun f (x y) (labels ((square (n) (* n n)) (fak (n) (if (= n 0) 1 (* n (fak (- n 1)))))) (/ (fak x) (square y)))) The function definitions are only valid inside f! Example: When we have iterative process the recursive function is defined locally. (defun fak (n) (fak-iter n 1)) (defun fak-iter (n res) (if (= n 0) res (fak-iter (- n 1) (* n res)))) (defun fak (n) (labels ((fak-iter (n res) (if (= n 0) res (fak-iter (- n 1) (* n res)))) ) (fak-iter n 1)))
Sequential processing of lists recursive process Sequential processing of lists iterative process (defun fn-sekv (l) ((endp l) init-value ) ( other condition expression ) (t ( operation (first l) (fn-sekv (rest l)) ))) (defun fn (l) (fn-iter l init-value )) (defun fn-iter (l res) ((endp l) res) ( other condition expression ) (t (fn-iter (rest l) ( operation (first l) res)) ))) recursive process: Example Replace the first occurrence of a given element (change 'karl kalle '(lisa per kalle stina)) => (lisa per karl stina) (defun change (new old l) ((endp l) ()) ((eq old (first l)) (cons new (rest l))) (t (cons (first l) (change new old (rest l))) ))) iterative process: (defun change (new old l) (change-iter new old l ())) (defun change-iter (n g l res) ((endp l) res) ((eq g (first l)) (append res (cons n (rest l)))) (t (change-iter n g (rest l) (add-to-the-end (first l) res))))) Why add-to-the-end instead of cons?
The substitution model for the two solutions: recursive process: (change 'karl kalle '(lisa per kalle stina)) -> (cons lisa (change karl kalle (per kalle stina)) -> (cons lisa (cons per (change karl kalle (kalle stina))) -> (cons lisa (cons per (cons karl (stina)))) -> (cons lisa (cons per (karl stina))) -> (cons lisa (per karl stina)) => (lisa per karl stina) iterative process: (change 'karl kalle '(lisa per kalle stina)) -> (ch-i karl kalle (lisa per kalle stina) ()) -> (ch-i karl kalle (per kalle stina) (lisa)) -> (ch-i karl kalle (kalle stina) (lisa per)) -> (ch-i karl kalle (append (lisa per) (cons karl (stina)))) => (lisa per karl stina) Double recursion Lists with lists as elements Pattern for sequences: (defun fn (l) ((endp l) init-value ) ( operation (first l) (fn (rest l)))) (t ( operation (fn (first l)) (fn (rest l)))) )) Total number of elements (defun symbols-in-seq (l) ((endp l) 0) (+ 1 (symbols-in-seq (rest l)))) (t (+ (symbols-in-seq (first l)) (symbols-in-seq (rest l))) ))) (symbols-in-seq '(a (b c (d e)) f)) => 6 Remove all occurrences of a symbol on all levels (defun my-all-remove (x l) ((endp l) ()) (if (eq x (first l)) (my-all-remove x (rest l)) (cons (first l) (my-all-remove x (rest l)))) (t (cons (my-all-remove x (first l)) (my-all-remove x (rest l))) ))) (my-all-remove 'q '(a q (b q (q)) c)) => (a (b nil) c)
Back tracking Is a given element somewhere in a list? (exist? 'q '(a (b q) c)) => t (defun exist? (x l) ((endp l) nil) (if (eq x (first l)) t (exist? x (rest l)))) (t (or (exist? x (first l)) (exist? x (rest l))) ))) Some problems are of the nature that you can not directly say if you have reached the right element or not. You must go on. Typical in search problems you must handle back tracking. Search one alternative, if it fails come back and try another alternative. Lab 2 and 3 use this concept. Here we will solve the following problem: Find the element after the last occurrence of a given element. We assume that the given element is not last in the list. (find-after x (a b x c)) => c (find-after x (a b x c x d x e)) => e (find-after x (a b c)) => element-not-on-list (defun find-after (x l) ((endp l) element-not-on-list) ((eq x (first l)) ; the element is found (let ((next-value (find-after x (rest l)))) ; check further (if (eq next-value element-not-on-list) (second l) next-value))) (t (find-after x (rest l))))) The function cons dotted pair: (cons a b) => (a. b) dotted list: (cons x (cons y z)) => (x y. z) association list: ((ett. one) (två. two) (tre. three)) Observe that all of these expressions describes the same list: (x y z) = (x y z. nil) = (x y. (z. nil)) = (x. (y. (z. nil)))
The list seen as a binary tree Pattern - binary tree: (defun fn (bt) (if (atom bt) <- leaf? processing of the leaf ( operation (fn (car bt)) (fn (cdr bt))))) The list seen as a binary tree pattern - binary tree - nil a special case: (defun fn (bt) ((eq bt nil) value ) ((atom bt) processing of the leaf ) (t ( operation (fn (car bt)) (fn (cdr bt))))) Observe: Here I use car and cdr instead of first and rest. (Better with primitives left and right) Number of elements (as leaves) Here is nil a leaf, it will be counted. (defun count-leaves (bt) (if (atom bt) 1 (+ (count-leaves (car bt)) (count-leaves (cdr bt)) ))) Here is nil not a leaf (defun count-leaves (bt) ((eq bt nil) 0) ((atom bt) 1) (t (+ (count-leaves (car bt)) (count-leaves (cdr bt)) ))) Sequence solution: Observations 3 cases - end of list - first element is an atom - first element is a list Binary tree solution 2 cases - atom (the leaf) - list (intern nod in the tree) Observe: The binary tree algorithm is more general, it includes the sequential algorithm. But, of course the problems nature gives what solution to use.
An arithmetic formula as a binary tree. + 2 4 We represent the binary tree as a list with the following structure: (left-tree operator right-tree) * Define a function to calculate its value (value 3) => 3 (value ((2 + 4) * ((8-2) / 3))) => 12-8 2 / 3 (defun value (expr) ((number? expr) expr) ((eq (operator expr) +) (+ (value (arg1 expr)) (value (arg2 expr)))) ((eq (operator expr) -) (- (value (arg1 expr)) (value (arg2 expr)))) ((eq (operator expr) *) (*......)) ((eq (operator expr) /) (/......)) (t (error... error message...)))) (defun number? (expr) (numberp expr)) (defun arg1 (expr) (first expr)) (defun arg2 (expr) (third expr) (defun operator (expr) (second expr))