## ЛЕКЦИЯ 9.

Массивы. Макросы. Пример программы на лиспе. Дифференцирование выражений.

Содержание

  9.1 Массивы.
  9.1.1 Определение массива.
  9.1.2 Доступ к ячейке массива.
  9.1.3 Запись данных в массив.
  9.1.4 Обработка массивов.
  9.1.5 Длина массива.
  9.2 Обратная блокировка.
  9.3.1 Макросы.
  9.3.2 Разработка макро.
  9.4 Пример программы на лиспе. Дифференцирование выражений..
  9.5.1 Модульный подход.
  9.5.2 Интерфейс программы.
  9.5.3 Загрузка программы.

## 9.1 Массивы.

Для хранения большого количества данных в лиспе используются массивы.
Массив - это переменных, ячеек, имеющих одно имя, но разные номера, обеспечивающие доступ к этим ячейкам.

## 9.1.1 Определение массива

Для определения массива заданной размерности используется функция make-array (make-array <размерность>) поддерживаются только одномерные массивы-векторы.

Пример :
(setq data (make-array 10))

# (0 0 0 0 0 0 0 0 0 0)
data - имя массива,
0 - начальное наполнение.

## 9.1.2 Доступ к ячейке массива.

Доступ производится с помощью функции aref. AREF имеет два аргумента - имя массива и индекс и возвращает значение ячейки
(aref <имя> <индекс>)
* (aref data 8)
0,

так как там записан 0.

Особенности: первый аргумент не блокируется, первая ячейка имеет номер 0.

## 9.1.3 Запись данных в массив.

Поместить данные в массив можно ,используя функцию setf

* (setf (aref data 2) 'dog)
aref -вызывает значение ячейки, функция setf помещает значение.

Рассмотрим массив testdata
(setq testdata (make-array 4))
# * (setf (aref testdata 1) 'dog)
* (setf (aref testdata 0) 18 )
* (setf (aref testdata 2) '(a b) )
* (setf (aref testdata 3) 4 )

Можно (setq testdata ( vector 18 'dog '(a b) 0)) (aref d 1)

В результате получим
testdata 0 1 2 3
18 dog (a b) 0

Можно использовать эти данные
(cons (aref testdata 1) (list (aref testdata 3)
(aref testdata 2)))

(dog 0 ( a b))
(aref testdata (aref testdata 3))
18

## 9.1.4 Обработка массивов.

Так как доступ к элементам массива производится по номерам, то удобно использовать численные итерации и рекурсии.

Рассмотрим функцию, которая берет два аргумента имя массива и его длину и возвращает все значения, помещенные в список
(defun array-list (arnam len)
(do (( i 0 ( + 1 i))
( result nil (append result (list (aref arnam i)))))
(( equal i len) result)))
(array-list testdata 4)
( 18 dog (a b) 0)

## 9.1.5 Длина массива.

(array-length <имя>) возвращает длину массива.
(array-length testdata) возвращает длину массива 4.

## 9.2 Обратная блокировка.

Обычная блокировка не позволяет вычислять выражения
* '(a b c)
(a b c)

Иногда возникает необходимость вычислить только часть выражения.
Если
(setq b '(x y z))
И мы запишем
* `( a ,b c) ,то b - будет вычислено и мы получим
(a (x y z) c)
` - обратная верхняя запятая обозначает блокировку, которая может частично сниматься запятой.
Обратная блокировка может использоваться при печати:
(setq n 'john)
(setq m 'Robert)
(print `(boys ,n and ,m))
(boys john and roberts)

Наиболее часто обратная блокировка используется в мощном средстве лиспа - макросах.

## 9.3.1 Макросы.

Это специальное средство,позволяющее формировать вычисляемое выражение в ходе выполнения программы.

Рассмотрим например функцию blancs, которая производит n переводов каретки (пропускает n линий)
(defun blancs (n)
(do ((count n ( - count 1)))
(( zerop count) nil)
(terpri)))

(blancs 4) пропустит четыре строки.

Будем писать программу, которая позволит выполнять некоторое действие n раз
Для blancs:
(do-times '(terpri) 4)
Или
(do-times '(print 'hello) 3)
Напечатает hello три раза

Можно через eval определить
(defun do-times (operation n)
(do ((count n ( - count 1)))
(( zerop count) nil)
(eval operation)))

Это можно сделать также через специальную форму : defmacro

(defmacro do-times (operation n)
` (do ((count ,n ( - count 1)))
(( zerop count) nil)
,operation))

Как видно, форма макро похожа на определение функции, но есть отличия:

1. Аргументы макро не вычисляются перед их использованием.
Тогда обращение записывается:
(do-times (print 'hello) 3)

2. Когда макро вызывается, лисп сначала вычисляет тело макро, а затем выполняет получившееся выражение.
Например,после обращения
(do-times (print 'hello) 3)
Получим
(do ((count 3 ( - count 1)))
(( zerop count) nil)
(print 'hello))
Этот список потом вычисляется.
Таким образом при вызове макро сначала вычисляется тело (этап называется расширением) и формируется выражение.
На втором этапе вычисляется полученное выражение, и полученное значение возвращается как результат.
Вызывая macro с разными аргументами получим разные результаты. Если мы вызываем:
(do-times (print count) 10)

После вычисления тела получим:
(do ((count 10 ( - count 1)))
(( zerop count) nil)
(print count))

Печатается числа от 10 до 1.
Можно определить функцию обратной печати чисел

(defun print-number (n)
(do-times (print count) n))
( print-number 5)

## 9.3.2 Разработка макро

При разработке макро необходимо выполнить три шага:

1. Написать пример функции,которую должна формировать макро,
2. Выделить общие части для нескольких функций и переменные. Переменные части обозначить переменными, выделить запятыми и вынести в аргументы. Постоянные части записать напрямую.
3. Определить макро,которое реализует этот вызов.

Пример. Надо определить макро term-search, которая будет просматривать список и выделять первый элемент удовлетворяющий заданному условию.

Шаг1. Сформулируем пример. Запишем тело для поиска чисел:

           (setq l '(s d 3 d))    (setq item 5)
                          _
             (do (( tail  l (cdr tail)))
                   ((null tail) nil)
                   ( cond ((numberp (car tail)) (return (car tail)))))
                           ~~~~~~~

Шаг2. Выделяем общие части список lis - l и предикат predicate - number.

Шаг3.Формируем макро

               (defmacro term-search ( predicate lis)
               ` (do (( tail , lis (cdr tail)))
                     ((null tail) nil)
          (cond ((,predicate (car tail)) (return (car tail))))))

## 9.4 Пример программы на лиспе. Дифференцирование выражений.

Напишем программу дифференцирования алгебраических выражений. Для наглядности ограничимся алгебраическими выражениями в следующей форме:
(+ x y) (* x y)

Сложение и умножение можно свободно комбинировать.
Например,
(*(+ a ( * a b)) c) Программируя непосредственно получаем

       (defun diff0 ( l x)
          (cond (( atom l)
                    (if (eq l x) 1  ;l=1
                          0));l=константа
                (( eq (first l) '+)
                   (list '+
                          (diff0  (second l) x)
                          (diff0  (third  l) x)))
                (( eq (first l) '*)
                   (list '+
                      (list '*
                          (diff0  (second l) x)
                          (third  l))
                      (list '*
                          (diff0  (third l) x)
                          (second  l))))
                (t l)))

Испoльзуем

     (diff0 '(+ x (* 3 x)) 'x)
       ( + 1 (+ (* 0 x) (* 1 3))) = 4
     (diff0 '(- x (* 3 x)) 'x) return
         (- x (* 3 x))  Why?
     (diff0 '(* x ( + x 1)) 'x)
       (+ (* 1 ( x 1))(*(1 0) x))

Вычисляемые выражения не упрощаются,хотя это не трудно сделать.

## 9.5.1 Модульный подход.

Эта программа неудобна,так как трудно расширять,приходится все группировать в один cond.Она не является модульной.
Мы получим более удобное решение,если для каждого действия определим свою дифференцирующую функцию и через свойство diff свяжем эту функцию с символом ,обозначающим действие.

Прoстим запись самой дифференцирующей функции:

    (defun dif1 (l x)
    	(cond (( atom l)
    		(if (eq l x) 1 0))
    	       ( t (funcall (get (first  l) 'diff) (cdr l) x))))

Функции дифференцирования становятся значениями свойства 'diff:

(setf (get '+ 'diff) 'dif+) (setf (get '* 'diff) 'dif*)
Таким образом извлекаем действие из данных. Сами функции записываются:

    (defun dif* (l x)
    	(list '+ (list '* (dif1 (first l) x)
    			  (second l))
    		 (list '* (dif1 (second l) x)
    			  (first l))))
     (defun dif+ (l x)
    	(list '+ (dif1 (first l) x) (dif1 (second l) x)))

Благодаря модульности можно дополнить для -

     (defun dif- (l x)
    	(list '- (dif1 (first l) x) (dif1 second l) x)))

Таким образом, первоначальное упрaвление вычислительным процессом, связанное со структурой программы, мы преобразовали в динамическое управление основанное на данных.
Можно использовать макросы.
Определим макрос defdif , c помощью которого определяются дифференцирующие функции для новых действий:

    (defmacro defdif (act args body)
    	`(setf (get ',act 'diff)
    	'(lambda,args,body)))

Тогда дифф. функции задаются:

     (defdif + (l x)
    	(list '+ (dif1 (first l) x) (dif1 (second l) x)))
    (defdif * (l x)
    	(list '+ (list '* (dif1 (first l) x)
    			  (second l))
    		 (list '* (dif1 (second l) x)
    			  (first l))))
    (dif1 '(+ x x) 'x)
    (defdif - (l x)
    	(list  '- (dif1 (first l) x)
    		  (dif1 (second l) x)))
      (dif1 '(+ x (* 3 x)) 'x)
     (dif1 '(- x (* 3 x)) 'x)
    (dif1 '(* x ( - x 1)) 'x)

## 9.5.2 Интерфейс программы.

Дополним программу несколькими функциями,обеспечивающими ввод информации:
Чтение дифф. списка
(defun read-list () (princ " diff-list ? ") (setq l (read)))

Пусть продолжает вычисления до тех пор пока не будет введено не d.

    (defun d ()  (princ "enter command : d -diff;q - quit") (terpri)
                 (if (eq  (read) 'd)   (progn (read-list)
                                       (print  (dif1 l 'x ))
                                       (terpri) (d))
                                       'end))

Вызов программы теперь (d)

## 9.5.3 Загрузка программы.

Можно сразу загружать программу и начать ее выполнение, для этого используют функцию load
(load <файл>)

Записываются обычным образом,но \\ надо использовать двойные.
(load "diff1") и сразу начинается выполнение.

Содержание