среда, 12 мая 2010 г.

Продолжаем упражняться в Common Lisp

На этот раз я пробую освоить переменное количество аргументов функций, именованные аргументы и значения по умолчанию.

1. Вычисление квадратного корня с необязательным указанием точности и приближённого значения корня
;;;; Приближённое вычисление квадратного корня

;; Модуль числа
(defun abs-value (num)
(if (> num 0.0)
num
(- 0.0 num)))

;; Логическая функция, возвращающая истину,
;; если числа a и b отличаются друг от друга
;; менее, чем на precision
(defun precision-ok (a b &optional (precision 0.001))
(< (abs-value (- a b)) precision))

;; Рекурсивная функция, использующая root
;; как приближённое значение корня num.
;; Функция вызывает сама себя до тех пор,
;; пока точность результата не достигнет precision
;; По умолчанию использует 1.0 в качестве приближённого
;; значения корня и 0.001 как значение точности
(defun square-root (num &optional (root 1.0))
(if (precision-ok num (* root root))
root
(square-root num (/ (+ (/ num root) root) 2))))

;; Вычисление квадратного корня 25
(square-root 25.0)

;; Вычисление квадратного корня с указанием его
;; приближённого значения
(square-root 25.0 3.0)

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

2. Вычисление квадратного корня с необязательным указанием точности и приближённого значения корня
;;;; Приближённое вычисление квадратного корня

;; Модуль числа
(defun abs-value (num)
(if (> num 0.0)
num
(- 0.0 num)))

;; Логическая функция, возвращающая истину,
;; если числа a и b отличаются друг от друга
;; менее, чем на precision
(defun precision-ok (a b precision)
(< (abs-value (- a b)) precision))

;; Рекурсивная функция, использующая root
;; как приближённое значение корня num.
;; Функция вызывает сама себя до тех пор,
;; пока точность результата не достигнет precision
;; По умолчанию использует 1.0 в качестве приближённого
;; значения корня и 0.001 как значение точности
(defun square-root (num &optional (root 1.0) (precision 0.001))
(if (precision-ok num (* root root) precision)
root
(square-root num (/ (+ (/ num root) root) 2) precision)))

;; Вычисление квадратного корня 25
(square-root 25.0)

;; Вычисление квадратного корня с указанием его
;; приближённого значения
(square-root 25.0 3.0)

;; Вычисление квадратного корня с указанием его
;; приближённого значения и необходимой точности
(square-root 25.0 3.0 0.00001)

По-моему гораздо удобнее. Однако если мы хотим указать точность, то необходимо также указать и приближённое значение корня. Исправим это при помощи именованных аргументов.

3. Вычисление квадратного корня с необязательным указанием точности или приближённого значения корня
;;;; Приближённое вычисление квадратного корня

;; Модуль числа
(defun abs-value (num)
(if (> num 0.0)
num
(- 0.0 num)))

;; Логическая функция, возвращающая истину,
;; если числа a и b отличаются друг от друга
;; менее, чем на precision
(defun precision-ok (a b precision)
(< (abs-value (- a b)) precision))

;; Рекурсивная функция, использующая root
;; как приближённое значение корня num.
;; Функция вызывает сама себя до тех пор,
;; пока точность результата не достигнет precision
;; По умолчанию использует 1.0 в качестве приближённого
;; значения корня и 0.001 как значение точности
(defun square-root (num &key (root 1.0) (precision 0.001))
(if (precision-ok num (* root root) precision)
root
(square-root num
:root (/ (+ (/ num root) root) 2)
:precision precision)))

;; Вычисление квадратного корня 25
(square-root 25.0)

;; Вычисление квадратного корня с указанием его
;; приближённого значения
(square-root 25.0 :root 3.0)

;; Вычисление квадратного корня с указанием
;; необходимой точности
(square-root 25.0 :precision 0.000001)

;; Вычисление квадратного корня с указанием его
;; приближённого значения и необходимой точности
(square-root 25.0 :precision 0.000001 :root 25.0)

Вот так-то. Теперь можно просто вычислить корень, можно вычислить с указанием необходимой точности, можно вычислить с указанием приближённого значения корня и, наконец, можно вычислить корень указав и необходимую точность и приближённое значение корня.

4. Вычисление квадратных корней списка чисел

Дополним программу из предыдущего пункта следующей функцией:
;;Вычисление квадратных корней списка чисел
(defun square-roots (&rest rest)
(if (eql rest nil)
nil
(cons (square-root (car rest))
(apply (function square-roots) (cdr rest)))))

;; Вычисление корней 25, 64 и 121
(square-roots 25.0 64.0 121.0)

Или можно записать немного короче, заменив оператор function на его сокращённую запись:

;; То же самое, но с использованием краткой записи
;; оператора function
(defun square-roots (&rest rest)
(if (eql rest nil)
nil
(cons (square-root (car rest))
(apply #'square-roots (cdr rest)))))

;; Вычисление корней 25, 64 и 121
(square-roots 25.0 64.0 121.0)

5. Вычисление чисел Фибоначчи

Именованные аргументы со значением по умолчанию можно использовать и для сокращения других программ. Например, уже рассмотренной ранее программы для вычисления чисел из ряда Фибоначчи:
;;;; Вычисление 20000-го числа в ряду Фибоначчи

;; Рекурсивная функция, вычисляющая число Фибоначчи
;; Вызывает себя num раз, используя a и b в качестве
;; двух предыдущих чисел в ряду
(defun fib (num &key (a 1) (b 1))
(if (eql num 1)
a
(fib (- num 1) :a b :b (+ a b))))

;; Вычисление 20000-го числа в ряду Фибоначчи
(fib 20000)

;; То же самое
(fib 20000 :a 1 :b 1)

6. Вычисление числа Фи ("Золотого сечения")
;; Вычисление числа Фи
;; Совмещённое вычисление двух последовательных
;; чисел в ряду Фибоначчи с последующим делением
;; следующего числа на предыдущее
(defun phi (&key (num 200) (a 1) (b 1))
(if (eql num 1)
(* 1.0 (/ b a))
(phi :num (- num 1) :a b :b (+ a b))))

(phi)

7. Вычисление факториала

И даже вычисление факториала, написанное в расчёте на оптимизацию хвостовой рекурсии, можно переписать в подобном же виде:

;;;; Вычисление факториала 20000

;; Вычисление факториала с применением хвостовой рекурсии
(defun fact (counter &optional (val 1))
(if (eql counter 0)
val
(fact (- counter 1) (* val counter))))

;; Вычисляем факториал 20000
(fact 20000)

Комментариев нет:

Отправить комментарий