
- •5. Применение функционалов
- •5.1 Функционалы работы со списками
- •5.1.1 Функции свертки fold, foldr и foldl
- •5.1.2 Отображающая функция map
- •5.1.3 Функция фильтрования filter
- •5.2 Функционал символьного дифференцирования
- •5.3 Элементы систем упрощения алгебраических выражений
- •5.4 Применение символьных функционалов
5. Применение функционалов
5.1 Функционалы работы со списками
Вспомним, мы как-то ранее рассмотрели две рекурсивно - суммирующие функции length и sum. Их определение было таким
> (define (length lst)
(if (null? lst) 0 (+ 1 (length (cdr lst)))))
и
> (define (sum lst)
(if (null? lst) 0 (+ (car lst) (sum (cdr lst)))))
Теперь посмотрим на них с позиции схемы «накопления».
5.1.1 Функции свертки fold, foldr и foldl
Пусть есть задача: на заданном списке lst провести операцию op, причём начальное её значение должно быть expr. Если в схеме накопления были явно указаны границы диапазона, то здесь границы естественны: это начало и конец списка, а значит вид функции должен быть проще. В LISPe есть соответствующая функция, которая называется «свертка» (fold). В некоторых Лисп’ах – это встроенная функция (в DrRacet’e её нет !!).
> (define (fold op expr lst)
(if (null? lst) expr
(op (car lst) (fold op expr (cdr lst)))))
Её действие таково: над каждым элементом списка lst поочередно производится операция op, начальное значение которой равно expr.
Результатом этой функции явится накопительный итог всей работы.
Заметим, что fold использует «хвостовую» рекурсию, т.е. операция совершается тут же. Иногда эту функцию называют правая свертка и вызывают как foldr.
В терминах этой абстракции можно определить разные полезные функции.
> (define (sum lst) (fold + 0 lst))
> (sum ‘(1 2 3 4) )
10
> (define (prod lst) (fold * 1 lst))
> (prod ‘(1 2 3 4) )
24
Надо заметить, что существуют еще и левая функции свертки – foldl, которая определяется так (в DrRacet’e – это встроенная функция):
> (define (foldl op expr list)
(if (null? list) expr
(foldl op (op expr (car list)) (cdr list))))
Различие их действий видно на таком примере.
> (foldl / 1 '(2 3))
В foldl действие выполняется с левой стороны, т.е. деления происходит в таком порядке: (1/2) / 3
А здесь действие выполняется справой стороны, а именно: (1/3) / (1/2)
> (foldr / 1 '(2 3))
Вообще, применение некоторого преобразования к каждому элементу в списке и формирование списка результатов - часто встречающийся функционал. А ничего другого не остаётся, поскольку, кругом одни списки!
Теперь рассмотрим процедуру, которая возводит в квадрат каждый элемент списка, возвращая результат как список квадратов:
> (define (sqr-list lst)
(if (null? lst) nil
(cons (sqr (car lst))
(sqr-list (cdr lst)))))
Заметим, что список образуется с помощью функции cons, значит, результирующий список получается последовательной подстановкой в начало списка очередного квадрата текущего элемента.
> (sqr-list ‘(1 2 3))
(1 4 9)
Мы можем выделить эту идею – последовательно выполнять некую функцию с каждым следующим элементом заданного списка и выразить её как функцию высшего порядка.
5.1.2 Отображающая функция map
Встроенная функция map определяется для двух параметров: некой заранее заданной функции и списка аргументов для этой функции. Функция map возвращает список результатов, полученных применением функции-параметра к каждому элементу списка аргументов. Традиционно map-функция называется отображающей функцией, поскольку она как бы отображает один список на другой.
Вот её определение.
> (define (map f lst)
(if (null? lst) nil
(cons (f (car lst))
(map f (cdr lst)))))
Посмотрим её работу.
> (map abs ‘(-1 2 -3.5 10))
(1 2 3.5 10)
Можно применить и «одномоментную» лямбда-функцию:
> (map (lambda (x) (* x 2)) ‘(1 2 3 4))
(2 4 6 8)
Дадим новое определение sqr-list в терминах map.
> (define (sqr-list lst) (map sqr lst))
И проверим
> (sqr-list '(1 2 3 4))
(1 4 9 16)
Итак, функции высшего порядка типа map или fold важны тем, что устанавливают более высокий уровень абстракции в обработке списков. Если в первоначальном определении функция sqr-list, появилась у нас как привычная рекурсивная функция, то определение через map скрыло процесс поэлементной обработки списка и предоставило возможность обратить внимание именно на само преобразование списка элементов в список результатов. Различие между этими двумя определениями только в том, что мы по-другому думаем о процессе получения результатов, возвышаясь над деталями.
Сравним теперь следующие два определения одной и той же функций - функции вычисления длины списка.
>(define (length1 lst)
(if (null? lst) 0 (+ 1 (length1 (cdr lst)))))
Другой способ.
> (define (length2 lst)
(sum (map (lambda (x) 1) lst) ))
Хитрость функции length2 в том, что мы проходим список дважды. В первый раз заменяем все элементы единицами (тем самым портим данные !!!), а во второй раз подсчитываем количество этих единиц. (Функцию sum надо определить заранее, т.к. в DrSchema’е её нет.)
Пробуем вторую функцию.
> (length2 '(a s d f (g h k)))
5