Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Fp_5.doc
Скачиваний:
0
Добавлен:
01.05.2025
Размер:
255.49 Кб
Скачать

5.1.3 Функция фильтрования filter

Иногда, проходя по списку, возникает потребность выбирать некоторые его элементы, по-другому – отфильтровывать элементы списка. Это воплощается в ещё одной полезной функции filter. На входе её два параметра – список и предикат. На выходе новый список, состоящий только тех элементов исходного списка, которые удовлетворяют заданному предикату, в нашем случае названном p.

> (define (filter p lst)

(cond ((null? lst) nil )

((p (car lst)) (cons (car lst) (filter p (cdr lst))))

(else (filter p (cdr lst)))))

Замечательный пример.

> (filter odd? (list 1 2 3 4))

(1 3)

А теперь – сумма квадратов элементов этого, отфильтрованного списка.

>

= 12 + 32

(sum (map sqr (filter odd? (list 1 2 3 4))))

10

5.2 Функционал символьного дифференцирования

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

Надо отметить, что задача «символьного дифференцирования» положила начало целому направлению исследований, получившему название «символьные вычисления» или «компьютерная алгебра». В результате возникли мощные системы, которые в настоящее время активно используются в прикладной математике и физике. С их помощью получен ряд важных результатов в области небесной механики, в физике элементарных частиц, квантовой электродинамики.

Для нас эта задача послужит примером «экспериментального» или «прототипного» программирования. Имеется в виду следующее.

Не всегда программа создаётся как готовый продукт. Иногда программы пишутся для проверки каких-нибудь идей, для того, чтобы убедиться в их плодотворности или, наоборот, в бесперспективности. В таких случаях нет смысла тратить время на создание «полноценной» программы, которая всё равно будет выброшена. Главное - быстро написать работающий прототип, модель решения, с которой можно поэкспериментировать.

Лисп очень хорошо подходит для такого рода задач. Вероятно, именно поэтому столь многие новшества пришли к нам из Лиспа.

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

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

номер правила

выражение (t)

производная (dt/dx)

унарность операции

1

const

0

2

x

1

3

u + v

u' + v'

2

4

u - v

u' - v'

2

5

u * v

u' * v + v' * u

2

6

u / v

(u' * v - v' * u) / v2

2

7

uv

v * uv-1* u' + v' * uv * ln u

2

8

eu

u' * eu

1

9

Ln u

u' / u

1

10

Sin u

u' * cos u

1

11

Cos u

u' * -sin u

1

Вызов нашей функции будет таков:

(diff выражение переменная)

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

Например, (diff '(+ 123 x) 'x) будет означать вызов нашей функции для получения производной по x от выражения 123 + x

Понятно, что исходные выражения будут представляться только списками (другой структуры данных просто нет!). Правда, если это не список – это простая переменная, простейший случай получение одинокой константы. А наша процедура diff должна будет преобразовывать входной список в выходной список по описанным выше правилам дифференцирования, обращая внимание на задаваемую переменную.

В основе алгоритма должно быть главное разветвление. Согласно таблице, мы должны будем различать выражение для дифференцирования: бинарное оно (это значит, что список верхнего уровня состоит из трёх членов: (op var1 var2) или унарное, т.е.список вида: (op var). Договоримся также, что выражения, не входящие в таблицу будут считаться ошибочными. Это ограничение не принципиальное, поскольку таблицу всегда можно расширить.

Итак, наша функция, написанная одним блоком.

>(define (diff expr x)

(define (dx t) ;----- знак

(if (pair? t) ;----- операции -- аргумент1 -- аргумент2 --

(if (null? (cddr t)) (unary (car t) (cadr t) )

(binary (car t) (cadr t) (caddr t)))

(if (eq? t x) 1 0)))

; Здесь анализируется выражение с двумя аргументами: (op var1 var2)

(define (binary op u v)

(cond

((eq? op '+) (list '+ (dx u) (dx v)))

((eq? op '-) (list '- (dx u) (dx v)))

((eq? op '*) (list '+

(list '* (dx u) v)

(list '* (dx v) u)))

((eq? op '/) (list '/

(list '-

(list '* (dx u) v)

(list '* (dx v) u))

(list 'expt v 2)))

((eq? op 'expt)(list '+

(list '*

(dx u) v

(list 'expt u (list '- v 1)))

(list '*

(dx v)

(list 'expt u)

(list 'log u))))

(else (begin

(display "** Неизвестная бинарная операция: ")

(display op)))))

; Выражение с одним аргументом: (op var)

(define (unary op u)

(list '*

(dx u)

(cond ((eq? op 'exp) (list 'exp u))

((eq? op 'log) (list '/ u))

((eq? op 'sin) (list 'cos u))

((eq? op 'cos) (list '- (list 'sin u)))

(else (begin

(display "* Неизвестная унарная операция: ")

(display op))))))

(dx expr))

Проверяем работу.

> (diff 'x 'x)

1

ЗАМЕЧАНИЕ. В папке examples-FP существует специально преобразованный файл 5-diff-tr.scm, который можно использовать для самостоятельного изучения работы функции diff с помощью функции trace.

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

> (diff '(+ 123 x) 'x)

(+ 0 1)

Должно быть: (123 + x)’ = 1

> (diff '(+ (* x 2)(* x x)) 'x)

(+ (+ (* 1 2) (* 0 x)) (+ (* 1 x) (* 1 x)))

Должно быть: (2*x + x2)’ = 2 + 2*x

> (diff '(sqrt x) 'x)

* Неизвестная унарная операция: sqrt

(* 1 #<void>)

А теперь пробуем найти производную функции x2 относительно x.

> (diff '(expt x 2) 'x)

(+ (* 1 2 (expt x (- 2 1))) (* 0 (expt x) (log x)))

Потребуется некоторое время, чтобы понять, что это выражение – в действительности есть 2*x. Правда, чтобы получить более приемлемый результат, можно обратиться попроще:

> (diff '(* x x) 'x)

(+ (* 1 x) (* 1 x))

Должно быть: (x2)’ = 2*x

Но все равно, ответ выглядит «не очень». Объясняется это просто - программа «не знает», что на самом деле: 1*2 = 2 или, что 0*x = 0 или 0 + 1 = 1, и т.п. Иными словами, программа не может упрощать выражения.!

В действительности, упрощение – не простая задача. В свое время на эту тему было написано много программ, причем как подзадач систем искусственного интеллекта (задач ИИ). Далее это вылилось в целое направления программирования – Системы Аналитических Вычислений (САВ), в англоязычной литературе получившее название Систем Компьютерной Алгебры (CAS).

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

Заодно, это станет и небольшой практикой в написании программ на LISP’е, помимо знакомства с ещё одним важным и мощным применением этого языка.

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