Ремонт принтеров, сканнеров, факсов и остальной офисной техники


назад Оглавление вперед




[70]

(define (random-gcd-test trials initial-x)

(define (iter trials-remaining trials-passed x) (let ((x1 (rand-update x))) (let ((x2 (rand-update xl))) (cond ((= trials-remaining 0)

(/ trials-passed trials)) ((= (gcd xl x2) 1) (iter (- trials-remaining 1) (+ trials-passed 1)

x2))

(else

(iter (- trials-remaining 1) trials-passed

x2))))))

(iter trials 0 initial-x))

Хотя программа по-прежнему проста, в ней обнаруживается несколько болезненных нарушений принципа модульности. В первой версии программы нам удалось, используя rand, выразить метод Монте-Карло напрямую как обобщенную процедуру monte-carlo, которая в качестве аргумента принимает произвольную процедуру experiment. Во втором варианте программы, где у генератора случайных чисел нет локального состояния, random-gcd-test приходится непосредственно возиться со случайными числами x1 и x2 и передавать в итеративном цикле x2 в качестве нового входа rand-update. Из-за того, что обработка случайных чисел происходит явно, структура накопления результатов тестов начинает зависеть от того, что наш тест использует именно два случайных числа, тогда как для других тестов Монте-Карло может потребоваться, скажем, одно или три. Даже процедура верхнего уровня estimate-pi вынуждена заботиться о том, чтобы предоставить начальное значение случайного числа. Поскольку внутренности генератора случайных чисел просачиваются наружу в другие части программы, задача изолировать идею метода Монте-Карло так, чтобы применять ее затем к другим задачам, осложняется. В первом варианте программы присваивание инкапсулирует состояние генератора случайных чисел внутри процедуры rand, так что состояние генератора остается независимым от остальной программы.

Общее явление, которое мы наблюдаем на примере с методом Монте-Карло, таково: с точки зрения одной части сложного процесса кажется, что другие части изменяются со временем. Они обладают скрытым локальным состоянием. Если мы хотим, чтобы структура программ, которые мы пишем, отражала такое разделение на части, мы создаем вычислительные объекты (например, банковские счета или генераторы случайных чисел), поведение которых изменяется со временем. Состояние мы моделируем при помощи локальных переменных, а изменение состояния - при помощи присваивания этим переменным.

Здесь возникает соблазн закрыть обсуждение и сказать, что, введя присваивание и метод сокрытия состояния в локальных переменных, мы обретаем способность структурировать системы более модульным образом, чем если бы нам пришлось всем состоянием манипулировать явно, с передачей дополнительных параметров. К сожалению, как мы увидим, все не так просто.


Упражнение 3.5.

Интегрирование методом Монте-Карло (Monte Carlo integration) - способ приближенного вычисления определенных интегралов при помощи моделирования методом Монте-Карло. Рассмотрим задачу вычисления площади фигуры, описываемой предикатом P(x, у), который истинен для точек (x,у), принадлежащих фигуре, и ложен для точек вне фигуры. Например, область, содержащаяся в круге с радиусом 3 и центром в точке (5, 7), описывается предикатом, проверяющим (x - 5)2 + (у - 7)2 < 32. Чтобы оценить площадь фигуры, описываемой таким предикатом, для начала выберем прямоугольник, который содержит нашу фигуру. Например, прямоугольник с углами (2, 4) и (8,10), расположенными по диагонали, содержит вышеописанный круг. Нужный нам интеграл - площадь той части прямоугольника, которая лежит внутри фигуры. Мы можем оценить интеграл, случайным образом выбирая точки (x,y), лежащие внутри прямоугольника, и проверяя для каждой точки P(x,y), чтобы определить, лежит ли точка внутри фигуры. Если мы проверим много точек, доля тех, которые окажутся внутри области, даст нам приближенное значение отношения площадей фигуры и прямоугольника. Таким образом, домножив это значение на площадь прямоугольника, мы получим приближенное значение интеграла.

Реализуйте интегрирование методом Монте-Карло в виде процедуры estimate-integral, которая в качестве аргументов принимает предикат P, верхнюю и нижнюю границы прямоугольника xi, x2, yi и y2, а также число проверок, которые мы должны осуществить, чтобы оценить отношение площадей. Ваша процедура должна использовать ту же самую процедуру monte-carlo, которая выше использовалась для оценки значения п. Оцените п при помощи estimate-integral, измерив площадь единичного круга.

Вам может пригодиться процедура, которая выдает число, случайно выбранное внутри данного отрезка. Нижеприведенная процедура random-in-range решает эту задачу, используя процедуру random, введенную в разделе 1.2.6, которая возвращает неотрицательное число меньше своего аргумента.8

(define (random-in-range low high) (let ((range (- high low))) (+ low (random range))))

Упражнение 3.6.

Полезно иметь возможность сбросить генератор случайных чисел, чтобы получить последовательность, которая начинается с некоторого числа. Постройте новую процедуру rand, которая вызывается с аргументом. Этот аргумент должен быть либо символом generate, либо символом reset. Процедура работает так: (rand generate) порождает новое случайное число; ((rand reset) (новое-значение)) сбрасывает внутреннюю переменную состояния в указанное (новое-значение). Таким образом, сбрасывая значения, можно получать повторяющиеся последовательности. Эта возможность очень полезна при тестировании и отладке программ, использующих случайные числа.

3.1.3 Издержки, связанные с введением присваивания

Как мы только что видели, операция set! позволяет моделировать объекты, обладающие внутренним состоянием. Однако за это преимущество приходится платить. Наш язык программирования нельзя больше описывать при помощи подстановочной модели применения процедур, которую мы ввели в

8В MIT Scheme есть такая процедура. Если random на вход дается точное целое число (как в разделе 1.2.6), она возвращает точное целое число, но если ей дать десятичную дробь (как в этом примере), она и возвращает десятичную дробь.


разделе 1.1.5. Хуже того, не существует простой модели с «приятными» математическими свойствами, которая бы адекватно описывала работу с объектами и присваивание в языках программирования.

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

Чтобы понять, как присваивание усложняет ситуацию, рассмотрим упрощенную версию make-withdraw из раздела 3.1.1, которая не проверяет, достаточно ли на счете денег:

(define (make-simplified-withdraw balance) (lambda (amount)

(set! balance (- balance amount)) balance))

(define W (make-simplified-withdraw 25)) (W 20)

5

(W 10)

-5

Сравним эту процедуру со следующей процедурой make-decrementer, которая не использует set!:

(define (make-decrementer balance) (lambda (amount)

(- balance amount)))

make-decrementer возвращает процедуру, которая вычитает свой аргумент из определенного числа balance, но при последовательных вызовах ее действие не накапливается, как при использовании make-simplified-with-draw:

(define D (make-decrementer 25)) (D 20)

5

(D 10) 15

Мы можем объяснить, как работает make-decrementer, при помощи подстановочной модели. Например, рассмотрим, как вычисляется выражение

((make-decrementer 25) 20)

Сначала мы упрощаем операторную часть комбинации, подставляя в теле make-decrementer вместо balance 25. Выражение сводится к



[стр.Начало] [стр.1] [стр.2] [стр.3] [стр.4] [стр.5] [стр.6] [стр.7] [стр.8] [стр.9] [стр.10] [стр.11] [стр.12] [стр.13] [стр.14] [стр.15] [стр.16] [стр.17] [стр.18] [стр.19] [стр.20] [стр.21] [стр.22] [стр.23] [стр.24] [стр.25] [стр.26] [стр.27] [стр.28] [стр.29] [стр.30] [стр.31] [стр.32] [стр.33] [стр.34] [стр.35] [стр.36] [стр.37] [стр.38] [стр.39] [стр.40] [стр.41] [стр.42] [стр.43] [стр.44] [стр.45] [стр.46] [стр.47] [стр.48] [стр.49] [стр.50] [стр.51] [стр.52] [стр.53] [стр.54] [стр.55] [стр.56] [стр.57] [стр.58] [стр.59] [стр.60] [стр.61] [стр.62] [стр.63] [стр.64] [стр.65] [стр.66] [стр.67] [стр.68] [стр.69] [стр.70] [стр.71] [стр.72] [стр.73] [стр.74] [стр.75] [стр.76] [стр.77] [стр.78] [стр.79] [стр.80] [стр.81] [стр.82] [стр.83] [стр.84] [стр.85] [стр.86] [стр.87] [стр.88] [стр.89] [стр.90] [стр.91] [стр.92] [стр.93] [стр.94] [стр.95] [стр.96] [стр.97] [стр.98] [стр.99] [стр.100] [стр.101] [стр.102] [стр.103] [стр.104] [стр.105] [стр.106] [стр.107] [стр.108] [стр.109] [стр.110] [стр.111] [стр.112] [стр.113] [стр.114] [стр.115] [стр.116] [стр.117] [стр.118] [стр.119] [стр.120] [стр.121] [стр.122] [стр.123] [стр.124] [стр.125] [стр.126] [стр.127] [стр.128] [стр.129] [стр.130] [стр.131] [стр.132] [стр.133] [стр.134] [стр.135] [стр.136] [стр.137] [стр.138] [стр.139] [стр.140] [стр.141] [стр.142] [стр.143] [стр.144] [стр.145] [стр.146] [стр.147] [стр.148] [стр.149] [стр.150] [стр.151] [стр.152] [стр.153] [стр.154] [стр.155] [стр.156] [стр.157] [стр.158] [стр.159] [стр.160] [стр.161] [стр.162] [стр.163] [стр.164] [стр.165] [стр.166] [стр.167] [стр.168] [стр.169] [стр.170] [стр.171] [стр.172] [стр.173] [стр.174] [стр.175] [стр.176] [стр.177] [стр.178] [стр.179] [стр.180] [стр.181] [стр.182] [стр.183] [стр.184] [стр.185] [стр.186] [стр.187] [стр.188] [стр.189] [стр.190] [стр.191] [стр.192] [стр.193] [стр.194] [стр.195] [стр.196]