Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

MULISP8

.doc
Скачиваний:
16
Добавлен:
09.02.2015
Размер:
68.1 Кб
Скачать

File: MULISP6.LES (c) 03/10/92 СПбЭТУ

CLRSCRN

В течение этого урока мы обсудим примитивные числовые функции

muLISPа и технику программирования математических функций.

muLISP позволяет работать с целыми числами с неограниченным

числом знаков и рациональными числами с заданной точностью. Это озна-

чает, что ограничение на длину целых чисел задает лишь доступный объем

памяти. В принципе можно оперировать целыми числами, состоящими из

тысяч цифр. Это делает возможным использование muLISPа для исследова-

ний в области теории чисел.

По принципу умолчания muLISP обеспечивает вычисления над рацио-

нальными числами с точностью 7 знаков. ( В главе 5 Руководства по

использованию muLISPа (muLISP Reference Manual) описывается функция

PRECISION, использование которой позволяет повысить точность арифмети-

ческих действий над действительными числами.)

CONTINUE

Начнем обсуждение с примитивных числовых функций muLISPа. Функции

+, -, *, и / обозначают сложение, вычитание, умножение и деление,

соответственно. Во многих распространенных языках программирования Вы

можете записывать арифметические выражения с использованием инфиксной

нотации (в которой знак операции ставится между операндами). Например:

3 * 4 + 5

Поскольку LISP является языком функционального программирования,

для написания арифметических выражений употребляется функциональная

нотация, при которой знак операции стоит на первом месте, а затем пе-

речисляются операнды. Например, предыдущее арифметическое выражение

будет записано следующим образом:

$ (+ (* 3 4) 5)

Во время паузы потренируйтесь в вычислениях с использованием ука-

занных четырех арифметических действий. Когда Вам захочется вернуться

в среду урока, обратитесь к функции (RETURN).

BREAK

CLRSCRN

Одно из преимуществ функциональной нотации перед инфиксной заклю-

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

число операндов. Функции +, *, -, и / позволяют употреблять различное

число аргументов. Например:

$ (+ 45 -23 57)

$ (* 1 2 3 4 5)

Во время паузы с помощью тестирования функций - и / выясните, что

возвращается в качестве результата, если при обращении употреблено бо-

лее двух аргументов.

BREAK

CLRSCRN

Если при вызове числовой функции Вы употребили нечисловые аргу-

менты, то происходит прерывание и появляется сообщение об ошибке

"Nonnumeric Argument" (Нечисловой аргумент), за которым следует

подсказка о необходимости выбрать одну из опций (режимов):

Abort, Break, Continue, Top-level, Restart, System?

(Прекратить, Прервать, Продолжить, Верхний-уровень, Рестарт, Система?)

В таком случае Вы должны выбрать одну из опций, нажав на клавишу

с латинской буквой, совпадающей с первой буквой названия выбранного

Вами режима. Подробное описание функции BREAK (Прервать) приводится в

главе 5 Руководства по использованию muLISPа (muLISP Reference

Manual). На следующем экране Вы увидите пояснения, которые помогут Вам

выбирать нужную опцию.

CONTINUE

Сведения о режимах, которые можно выбирать при прерывании из-за

ошибки:

Abort (Прекратить): прекращает выполнение, передает управление на те-

кущий уровень цикла "прочитать-оценить-напечатать"

(the read-eval-print loop) и выдает подсказку для

пользователя, означающую, что можно ввести новое

предложение.

Break (Прервать): временно приостанавливает выполнение и выдает под-

сказку для пользователя, свидетельствующую о воз-

можности ввода. Функция, вызвавшая диагностику

ошибки, может быть определена по значению перемен-

ной BREAK. Для возвращения из режима приостановки

вычислений нужно ввести (RETURN expn), где <expn>

(expression - выражение) определяет значение, ко-

торое Вас интересует.

CONTINUE

Continue (Продолжить): продолжает выполнение, вызывая ту функцию, ко-

торая была прервана при выдаче сообшения об ошибке.

Top-level (Верхний-уровень): прекращает выполнение, передает управле-

ние на верхний уровень цикла "прочитать-оце-

нить-напечатать" (the read-eval-print loop) и вы-

дает подсказку для пользователя, означающую, что

можно ввести новое предложение.

Restart (Рестарт): ликвидирует существующую среду muLISPа и снова вы-

зывает muLISP.

System (Система): прекращает работу интерпретатора muLISP и передает

управление операционной системе.

CONTINUE

Пожалуй, достаточно говорить об ошибках, давайте вернемся к мате-

матике ! Функция "факториал" от числового аргумента имеет большое зна-

чение в теории вероятностей. Факториал от неотрицательного целого N,

обозначаемый N!, может быть рекурсивно определен с помощью следующих

формул:

0! = 1,

N! = N*(N-1)! если N > 0

Соответствующая запись на muLISPе для функции FACTORIAL:

$ (DEFUN FACTORIAL (N)

((EQL N 0) 1)

(* N (FACTORIAL (- N 1))) )

$ (FACTORIAL 5)

Это эффективное определение, однако при больших значениях N воз-

можно появление ошибки из-за полного израсходования памяти при пере-

полнении стека. Во время паузы напишите итеративный вариант определе-

ния функции FACTORIAL и проверьте его работу на примерах. Подсказка:

Вам придется ввести накапливающую переменную для получения результата.

BREAK

Приведем итеративное определение функции FACTORIAL и пример ее

тестирования:

$ (DEFUN FACTORIAL (N

% Local: % RSLT)

(SETQ RSLT 1)

(LOOP

((ZEROP N) RSLT)

(SETQ RSLT (* N RSLT))

(SETQ N (- N 1)) ) )

$ (FACTORIAL 100)

Как показывает приведенный пример, muLISP позволяет работать с

очень большими числами. В определении функции для вычисления факториа-

ла мы использовали примитивную функцию-распознаватель ZEROP (предикат,

выясняющий, имеет ли аргумент нулевое значение) . Обращение (ZEROP N)

эквивалентно обращению (EQL N 0), но несколько более эффективно.

CONTINUE

А теперь рассмотрим новый пример.

Числовая последовательность, имеющая название "числа Фибоначчи",

встречается в природе в самых неожиданных местах. N-ое число Фибонач-

чи, обозначаемое как F(N), может быть рекурсивно определено следующим

образом:

F(0) = 1,

F(1) = 1,

F(N) = F(N-1) + F(N-2) для N > 1.

Эквивалентная запись на muLISPе определения функции FIBONACCI :

$ (DEFUN FIBONACCI (N)

((ZEROP N) 1)

((EQL N 1) 1)

(+ (FIBONACCI (- N 1)) (FIBONACCI (- N 2))) )

$ (FIBONACCI 10)

Во время паузы попробуйте оценить эффективность написанного алго-

ритма определения функции FIBONACCI, вызывая эту функцию для аргумен-

тов, образующих возрастающую последовательность.

BREAK

CLRSCRN

Как Вы, наверное, убедились, это очень неэффективный алгоритм.

Дело в том, что при вычислении N-го числа Фибоначчи (N-2)-ое число Фи-

боначчи придется вычислять дважды, (N-3)-ье - три раза, (N-4)-ое -

пять раз, а все последующие - все большее число раз.

Поскольку это рекурсивный алгоритм, большинство программистов

пришло бы к заключению, что виновата рекурсия, и некоторые пользовате-

ли попытались бы создать более трудно воспринимаемое итеративное опре-

деление для повышения эффективности вычислений. Однако проблема вовсе

не в применении рекурсии, а в самом алгоритме.

Вместо того, чтобы начинать вычисления с попытки определить N-ое

число Фибоначчи и при этом в рекурсивном процессе продвигаться "вниз"

к нулю, более эффективный способ - начать с нуля и продвигаться

"вверх" к N-ому числу Фибоначчи, запоминая два самых последних из вы-

численных чисел Фибоначчи как значения двух переменных.

Во время паузы попробуйте создать более эффективное рекурсивное

определение функции Фибоначчи с названием FIB. Сравните эффективность

работы функций FIB и FIBONACCI.

BREAK

Эффективное рекурсивное определение функции вычисления чисел Фи-

боначчи:

$ (DEFUN FIB (N F1 F2)

((ZEROP N) F1)

(FIB (- N 1) (+ F1 F2) F1) )

$ (FIB 10 1 0)

$ (FIBONACCI 10)

FIB является функцией трех аргументов. Первый аргумент - это само

число N, второй аргумент 1, а третий аргумент 0. Если Вам более по ду-

ше функция Фибоначчи с одним аргументом, Вы можете определить "обрам-

ляющую" ( a "front-end" ) функцию, которая вызывает FIB с подходящими

значениями второго и третьего аргументов.

Тем, кто еще не убедился в элегантности и эффективности рекурсии,

мы предлагаем написать итерационное определение функции вычисления

чисел Фибоначчи и сравнить его с приведенным выше определением. Если

Вы уже поверили в рекурсию, можете продолжать.

CONTINUE

Возведение выражения в целую степень - это важная математическая

операция. Для того, чтобы возвести N в M-ую степень, Вы должны просто

напросто умножить N само на себя M раз.

Во время паузы напишите итеративное определение функции возведе-

ния в степень POWER как функции двух аргументов.

BREAK

Итеративное определение функции POWER:

$ (DEFUN POWER (N M

% Local: % RSLT )

(SETQ RSLT 1)

(LOOP

((ZEROP M) RSLT)

(SETQ RSLT (* N RSLT))

(SETQ M (SUB1 M)) ) )

$ (POWER 2 10)

Вызов примитивной функции SUB1 в приведенном выше примере эквива-

лентен вызову (- N 1), однако этот вызов предпочтительнее с точки зре-

ния эффективности вычислений. ADD1 также представляет собой примитив-

ную функцию muLISPа.

Существует еще более эффективный способ вычисления функции возве-

дения в целую степень. Перейдем к следующему экрану.

CONTINUE

Примем во внимание следующие обстоятельства:

1. Если M - четное число, то возведение N в M-ую степень эквива-

лентно возведению "квадрата" N в (M/2)-ую степень.

2. Если M - нечетное число, то возведение N в M-ую степень можно

представить как произведение N на N, возведенное в (M-1)-ую

степень.

Для реализации алгоритма Вам потребуется примитивная функ-

ция-распознаватель EVENP (even - четный). Эта функция возвращает зна-

чение T в том и только в том случае, когда ее аргумент является четным

числом; в противном случае она возвращает значение NIL.

Во время паузы напишите рекурсивное определение функции РOWER,

используя приведенные выше соображения.

BREAK

Эффективное рекурсивное определение функции POWER:

$ (DEFUN POWER (N M)

((ZEROP M) 1)

((EVENP M)

(POWER (* N N) (/ M 2)) )

(* N (POWER N (SUB1 M))) )

$ (POWER 10 100)

CONTINUE

Примитивная функция TRUNCATE (усекать, обрезать) полезна для пре-

образования рациональных чисел в целые. Если <n> является числом, то

обращение (TRUNCATE n) "усекает" <n> до ближайшего целого в направле-

нии к нулю:

$ (TRUNCATE 7/3)

$ (TRUNCATE -0.95)

Если функция TRUNCATE вызывается с двумя аргументами, то она

возвращает "усеченное" частное. Заметьте, что (TRUNCATE n m) эквива-

лентно (TRUNCATE (/ n m)):

$ (TRUNCATE 7 3)

$ (TRUNCATE -46.3 5.2)

CONTINUE

Функция TRUNCATE возвращает "усеченное" целое частное. Примитив-

ная функция REM возвращает остаток от деления первого аргумента на

второй. Функции TRUNCATE и REM определены таким образом, что они вы-

числяют целую часть частного и остаток от деления. Если (TRUNCATE n m)

возвращает целое q, а (REM n m) возвращает число r, то

n = q*m + r

Иногда нужно получить целую часть частного и остаток от деления

одного числа от другого обращением к одной функции. Примитивная функ-

ция DIVIDE возвращает точечную пару, содержащую целую часть частного и

остаток от деления первого аргумента на второй.

$ (TRUNCATE 7 3)

$ (REM 7 3)

$ (DIVIDE 7 3)

CONTINUE

Наибольший Общий Делитель (Greatest Common Divisor или сокращенно

GCD) двух целых чисел - это наибольшее целое, на которое без остатка

делится каждое из двух чисел. Алгоритм Евклида для нахождения наиболь-

шего общего делителя двух чисел может быть сформулирован следующим об-

разом:

1. Если N = 0, то GCD (N, M) = M;

2. В противном случае GCD (N, M) = GCD (M mod N, N).

Во время паузы попробуйте запрограммировать вычисление наибольше-

го общего делителя, определив функцию GCD с использованием алгоритма

Евклида, и проверьте ее работу на тестовых примерах. Используйте функ-

цию REM для определения M mod N.

BREAK

Рекурсивное определение функции GCD с использованием алгоритма

Евклида:

$ (DEFUN GCD (N M)

((ZEROP N) M)

(GCD (REM M N) N) )

$ (GCD 21 56)

В действительности функция определения наибольшего общего делите-

ля GCD и функция определения наименьшего общего кратного LCM (Least

Common Multiple) - примитивные функции muLISP. Эти примитивные функции

работают быстрее и позволяют задавать произвольное число аргументов.

CONTINUE

В заключение необходимо упомянуть примитивные функции, вычисляющие

отношения =, /=, <, >, <=, и >=. Например:

$ (= 34 34.0) ;Проверка на равенство

$ (/= 3/4 0.75) ;Проверка на неравенство

$ (< 67 45) ;Проверка на "меньше чем"

$ (>= 19 17 17) ;Проверка на "больше или равно"

Как показывает последний пример, функции, вычисляющие значение

отношения, могут иметь более двух аргументов. Если при вызове употреб-

лены нечисловые аргументы, то возникает прерывание и сообщение об

ошибке: "Nonnumeric Argument" (Нечисловой аргумент).

CONTINUE

Чтобы узнать, принадлежит ли введенное число заданному числовому

интервалу, можно вызвать функцию "<" с 3 аргументами. Например, чтобы

определить, является ли "g" строчной буквой, введите

$ (< 96 (ASCII 'g) 123)

Функция ASCII возвращает ASCII-код заданного знака. Если задать

число в пределах от 0 до 256, то функция ASCII возвратит обозначение

соответствующей буквы.

Во время паузы напишите определение функции-распознавателя

LOWERCASE, которая определяет, является ли введенный символ строчной

буквой.

BREAK

Функция-распознаватель LOWERCASE :

$ (DEFUN LOWERCASE (CHAR)

(< 96 (ASCII CHAR) 123) )

$ (LOWERCASE 'g)

CONTINUE

Давайте завершим урок написанием функции, которая производит сор-

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

мание вопросу эффективности сортировки, поэтому реализуем простой ал-

горитм "вставки", который будет вполне удовлетворительно работать с

короткими списками.

Прежде всего нам понадобится функция, которая вставляет число на

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

Во время паузы напишите функцию INSERT-NUM, которая вставляет заданное

число NUM в сортированный список LST. Используйте либо итерацию, либо

рекурсию в соответствии с Вашим вкусом .

BREAK

Рекурсивное определение функции INSERT-NUM:

$ (DEFUN INSERT-NUM (NUM LST)

((NULL LST)

(LIST NUM) )

((< NUM (CAR LST))

(CONS NUM LST) )

(CONS (CAR LST) (INSERT-NUM NUM (CDR LST))) )

$ (INSERT-NUM 12 '(5 9 11 14 19 21))

Во время паузы напишите функцию NUMBER-SORT для сортировки задан-

ного числового списка с использованием только что написанной функции

INSERT-NUM.

BREAK

Рекурсивное определение функции NUMBER-SORT:

$ (DEFUN NUMBER-SORT (LST)

((NULL LST) NIL)

(INSERT-NUM (CAR LST) (NUMBER-SORT (CDR LST))) )

$ (NUMBER-SORT '(34 23 -14 27 56 22 83))

Встроенная функция сортировки SORT основана на использовании бо-

лее эффективного алгоритма слияния. Если <test> - знак операции срав-

нения, (SORT list test) сортирует исходный список <list> и возвращает

в качестве значения список, сортированный в соответствии с <test>.

Например:

$ (SORT '(34 23 -14 27 56 22 83) '<)

На этом мы завершим обсуждение техники числового программирова-

ния на muLISPе. Поздравляем Вас с успешным освоением материала урока.

CONTINUE

$ (RDS)

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]