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

КН Лекции 1-9

.pdf
Скачиваний:
16
Добавлен:
23.02.2015
Размер:
2.64 Mб
Скачать

(< (abs (- (sqr y) x)) 0.001)) (define (improve y)

(/ (+ y (/ x y)) 2)) (define (iter y)

(if (good-enough? y) y

(iter (improve y)))) (iter 1.0))

Такое вложение определений, называемое блочной структурой (block structure), дает правильное решение для простейшей задачи упаковки имен. Но здесь есть еще одна идея. Помимо того, что мы можем вложить определения вспомогательных процедур внутрь главной, мы можем их упростить.

Поскольку переменная x связана в определении sqrt, процедуры goodenough?, improve и sqrt-iter, которые определены внутри sqrt, находятся в области действия x. Таким образом, нет нужды явно передавать x в каждую из этих процедур. Вместо этого мы можем сделать x свободной переменной во внутренних определениях, как это показано ниже. Тогда x получит свое значение от аргумента, с которым вызвана объемлющая их процедура sqrt. Такой порядок называется лексической сферой действия (lexical scoping) переменных.

§ 2. Рекурсия и итерация

Для начала рассмотрим функцию факториал n! = n (n − 1) (n − 2) 3 2 1

Существует множество способов вычислять факториалы. Один из них состоит в том, чтобы заметить, что n! для любого положительного целого числа n

n! = n (n − 1)!

Добавляем условие, что 1! равен 1. Получаем процедуру:

(define (factorial n) (if (= n 1)

1

(* n (factorial (- n 1)))))

Он порождает процесс:

(factorial 3)

(* 3 (factorial 2))

(* 3 (* 2 (factorial 1)))

(* 3 (* 2 (* 1 (factorial 0)))) (* 3 (* 2 (* 1 1)))

(* 3 (* 2 1)) (* 3 2)

6

Теперь рассмотрим вычисление факториала с другой точки зрения. Для вычисления n! мы сначала умножаем 1 на 2, затем результат умножаем на 3, затем на 4, и так пока не достигнем n.

Мы можем описать это вычисление, сказав, что счетчик и произведение с каждым шагом одновременно изменяются согласно правилу

произведение = счетчик произведение счетчик = счетчик + 1

Заканчивать вычисления нужно, когда счетчик становится больше, чем n.

(define (factorial n) (fact-iter 1 1 n))

(define (fact-iter product counter max-count) (if (> counter max-count)

product

(fact-iter (* counter product) (+ counter 1)

max-count)))

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

(define (factorial n)

(define (iter product counter) (if (> counter n)

product

(iter (* counter product) (+ counter 1))))

(iter 1 1))

Он порождает процесс:

(factorial 3) (iter 1 1) (iter 1 2) (iter 2 3) (iter 6 4)

6

Первый процесс строит цепочку отложенных операций (deferred operations), в данном случае цепочку умножений. Сжатие происходит тогда, когда выполняются эти отложенные операции. Такой тип процесса, который характеризуется цепочкой отложенных операций, называется рекурсивным процессом (recursive process). Говорят о прямом и обратном ходе рекурсии.

При вычислении n! длина цепочки отложенных умножений, а следовательно, и объем информации, который требуется, чтобы ее сохранить, растет линейно с ростом n (пропорционален n), как и число шагов.

Второй способ требует помнить лишь текущие значения переменных product, counter и max-count. Это итеративный процесс (iterative process).

Рассмотрим пример вычисления n-го числа Фибоначчи.

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

1, 1, 2, 3, 5, 8, 13, 21, 34 и т.д.

Плохой пример реализации в точности по определению (рекурсия).

(define (fib_bad n) (if (< n 2) 1

(+ (fib_bad (- n 1)) (fib_bad (- n 2)))))

Хорошая реализация (итерационный алгоритм)

(define (fib n) (define (it a b k)

(if (= k 0) b (it b (+ a b) (- k 1)))) (it 1 1 (- n 1)))

Алгоритмический анализ. Лекция 9.

Программирование на Scheme

§ 3. Лямбда-функции

Вспомним синтаксис описания функций.

(define (square x) (* x x))

Так мы определяли функцию возведения в квадрат.

На самом деле в Scheme создание функции, например f от одного аргумента, делится на собственно создание одноаргументной функции и на присваивании ей имени f.

Можно создавать функцию, не давая ей имени.

(lambda (<аргументы>) <инструкции>)

примеры:

(lambda () (+ 2 5)) (lambda (x) (* x x)) (lambda (x y) (+ x y))

Это соответствует в лямбда-исчислении таким выражениям:

(+ 2 5) (λx.* x x) (λxy.+ x y)

Вызов таких безымянных функций также аналогичен λ-исчислению.

((lambda (x) (* x x)) 4) ; получим 16 ((lambda (x y) (+ x y)) 6 7) ; получим 13

Описание функции с присвоением ей имени – это обычный define

(define square (lambda (x) (* x x))) ; то же, что и (define (square x) (* x x)) (define sum (lambda (x y) (+ x y))) ; то же, что и (define (sum x y) (+ x y))

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

§4. Область видимости переменных

ВScheme применяется привычный всем подход к областям видимости переменных (так называемый Lexical Scope):

Переменные определѐнные внутри некоторой функции видны во всех функциях, определѐнных внутри данной, но не видны вне еѐ.

(define a 3) (define (f)

(define b 4) (define (g)

(write b)) ; мы видим эту переменную так как g определена внутри f

(g)

(write a) ; мы видим переменную т.к. она в охватывающем окружении (write b)) ; это наша локальная переменная

(write b) ; ошибка b - определена внутри f и не видна уровнем выше.

Локальные переменные затеняют глобальные:

(define a 3) (define (f)

(define a 4) (write a))

(f) ; будет напечатано локальное a=4, а не 3,

параметры у функций являются локальными переменными со всеми вытекающими последствиями:

(define x 4) (define (f x)

(write x))

(f 5) ; будет напечатано 5, а не 4.

(define (func) (define a 5) (+ a 3))

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

(define (func) ((lambda (a)

(+ a 3)) 5))

Попробуем понять что же произошло. Мы создали функцию с параметром, который назвали 'a', поместили в неѐ всѐ что нам необходимо, и после этого запустили еѐ придав параметру требуемое значение '5'.

Попробуем ещѐ, вместо:

(define x 5) (define y 6) (write x)

(+ x (* x y))

мы можем написать:

((lambda (x y) (write x)

(+ x (* x y))) 5 6)

Данный приѐм настолько популярен, что имеет общепринятое

сокращение - let. Приведѐнные выражения в сокращѐнном виде записываются так:

(define (func) (let ((a 5))

(+ a 3))) (let ((x 5) (y 6))

(write x)

(+ x (* x y)))

Если немного поразмышлять, то мы получили не просто способ объявления локальных переменных, а возможность делать блоки с локальными переменными в произвольном месте кода, например:

(define a 3)

(write a) ; будет напечатано 3 (let ((a 5))

(write a)) ; будет напечатано 5 (write a) ; будет напечатано 3

У этого приѐма есть один существенный недостаток, поскольку формальные параметры инициализируются независимо друг от друга и в неопределѐнном порядке, мы не можем использовать одни из них для инициализации других, например в примере с двумя параметрами нельзя у задать равным x. Применим одну маленькую хитрость - будем связывать переменные по очереди:

(let ((x 3)) (let ((y x))

.....

Тогда всѐ получится, на момент определения 'y', 'x' уже известен и проинициализирован. Этот приѐм тоже очень распространѐн, а потому тоже

имеет общепринятое сокращение - let*.

(let* ((x 3) (y x))

.....

§ 5. Процедуры в качестве аргументов

Рассмотрим следующие три процедуры. Первая из них вычисляет сумму целых чисел от a до b:

(define (sum-integers a b) (if (> a b)

0

(+ a (sum-integers (+ a 1) b))))

Вторая вычисляет сумму квадратов целых чисел в заданном диапазоне:

(define (sum-sqr a b) (if (> a b)

0

(+ (square a) (sum-sqr (+ a 1) b))))

Третья вычисляет сумму последовательности термов в ряде

1/(1*3) + 1/(5*7) + 1/(9*11) + …

который (очень медленно) сходится к π/8.

(define (pi-sum a b) (if (> a b)

0

(+ (/ 1.0 (* a (+ a 2))) (pi-sum (+ a 4) b))))

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

(define (hимяi a b)

(if (> a b) 0

(+ (hтермi a)

(hимяi (hследующийi a) b))))

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

(define (sum term a next b) (if (> a b)

0

(+ (term a)

(sum term (next a) next b))))

Например, с ее помощью (вместе с процедурой inc, которая увеличивает свой аргумент на 1), мы можем определить sum-sqr:

(define (inc n) (+ n 1)) (define (sum-sqr a b) (sum square a inc b))