Ярославский Государственный Университет им. П.Г. Демидова
Опубликован: 06.11.2008 | Доступ: свободный | Студентов: 987 / 62 | Оценка: 4.50 / 4.00 | Длительность: 10:47:00
Лекция 5:

Язык РЕФАЛ: дополнительные виды памяти

< Лекция 4 || Лекция 5: 12 || Лекция 6 >
Аннотация: Рекурсивные вычисления и функция порождения процесса: функция apply; пример вычислений на Рефале функции n!. Стековая память Рефала: копилка и вид ее содержимого; функции работы с копилкой; пример сортировки массива. Обменная статическая и динамическая память: ящики и обменные функции; статические и динамические ящики; функции работы с динамическими ящиками; пример программы, работающей с поисковым списком (инициализация списка, добавление элемента списка, поиск элемента по ключу, удаление элемента списка).
Ключевые слова: нормальный алгоритм Маркова, алгоритм, Рефал, анализ, алгоритмическая, поле памяти, описание рефал-функции, поле зрения, выражение, рекурсия, значение, память, порожденные процессы, вызов функции, apply, синтаксис, рефал-функция, терм, функция, поле, стек полей зрения, создание функций, factory, символ-число, fact, порождает процесс, Произведение, аргумент функции, вычисление, цепочка символов, печать, остаток, копилка, стек, Рефал-программа, рефал-выражение, buried, копилк, replaceability, запись, сортировка массива, вершина стека, поиск, пробел, синтаксический анализ, очередь, деки, поисковые списки, граф, разреженные матрицы, обменная память, ящик, произвольное, содержимое ящика, обменная функция, обмен информацией, статический ящик, динамический ящик, директива, swapping, символ-ссылка, сборка мусора, программа, ссылка, ptr, голова списка, init, find, удаление элемента, меню, PL/I, определение, элемент списка, ключ, разность

Дополнительные виды памяти

Поскольку язык нормальных алгоритмов Маркова является вычислительно полным (можно реализовать любой алгоритм), то и Рефал обладает этим свойством. Однако вышеуказанными средствами Рефала не всегда можно достаточно просто описать любой алгоритм. В случае необходимости (временно запомнить промежуточные результаты, а потом их использовать выборочно для продолжения вычислений) мы можем пока использовать только накопление этих результатов в структурных выражениях, а необходимый при этом постоянный анализ таких выражений ведет к неэффективным алгоритмическим конструкциям. Поэтому с целью повысить эффективность разработки алгоритмов введены к обычным видам памяти ( поле памяти, в котором находятся описание рефал-функций, и поле зрения, в котором находится преобразуемое выражение) дополнительные виды памяти.

Рекурсивные вычисления и функция порождения процесса

Функции Рефала могут быть рекурсивными, т. е. обращаться сами к себе, как мы это видели на примерах предыдущего раздела. Но эти примеры не были связаны с запоминанием в памяти промежуточных вычислений рекурсии. Так, например, при вычислении функции n! происходит рекурсивный спуск от значения аргумента n к значению n-1 с запоминанием значения n к тому моменту, когда будет вычислено значение (n-1)! и надо будет умножить это значение на запомненное n. Такой рекурсивный спуск требует, чтобы рефал-машина имела не одно поле зрения (память) для выполнения процесса вычислений, а много таких полей зрения для порожденных процессов, которые могли бы выполнять вычисления в своей памяти, т. е. в своих полях зрения.

Вызов функции apply имеет другой синтаксис, чем вызов обычной рефал-функции:

<apply /Рефал-функция/ аргументРефал-функции>

Здесь полужирные уголковые скобки являются обязательными синтаксическими единицами в отличие от обычных уголковых скобок, служащих для описания синтаксиса. При вызове apply порождается новое поле зрения и в него помещается терм

k/Рефал-функция/ аргументРефал-функции.

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

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

В качестве примера мы рассмотрим создание функции вычисления факториала n!.

FACT     START
         ENTRY    Factorial
         EXTRN    prout, card, numb, symb, mul, first, apply
Factorial  =  k/fact/ k/numb/ k/prout/'n: '. k/card/...

fact  /0/   =  k/pr/ '!=1'
       E1   =  k/pr/ k/symb/E1.'!=' k/symb/ k/f0/ E1...
           
f0     E1   =  <apply /f/ E1>

f     /1/   =  /1/
       E1   =  k/mul/ (E1) k/f0/ k/m1/ E1...
           
pr          =
       E1   =  k/pr1/ k/first/ /80/ E1..
           
pr1 (E1)E2  =  k/prout/ E1. k/pr/ E2.
    '*'E1   =  k/prout/ E1.
	   
END

В этой программе функция Factorial вводит цепочку цифр данного, преобразует к символу-числу и вызывает функцию fact, которая вычисляет значение факториала от аргумента и готовит полученное значение к выводу на экран.

Функция fact для вычислений вызывает функцию f0, которая при помощи функции apply порождает процесс вычисления функции f. Эта функция накапливает произведение аргумента на значение факториала от аргумента, уменьшенного на 1, для чего снова вызывает функцию f0 с этим уменьшенным значением аргумента. Функция f0 снова порождает процесс вычисления f с новым значением аргумента и так делается до тех пор, пока значение аргумента не станет равно 1.

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

Функция fact вызовет после вычислений преобразование результата к цепочке символов и вызов функции pr для вывода на экран результата.

Здесь следует обратить внимание на то, что полученная цепочка символов может не уместиться в одной строке экрана. Поэтому функция pr отщепляет от цепочки 80 символов при помощи функции first и передает на печать функцию pr1, которая печатает отщепленную часть и возвращает остаток функции pr для следующего отщепления. Так происходит до тех пор, пока вся цепочка символов не будет выведена.

Стековая память Рефала

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

Содержимое копилки перед началом работы рефал-программы всегда пусто, а в процессе работы рефал-программы имеет следующий вид:

('<ИмяСтека1>=<Выраж1>'). . . ('<ИмяСтекаN>=<ВыражN>'),

где <ИмяСтека1> ... <ИмяСтекаN> - произвольные имена стеков (необязательно различные), а выражения <Выраж1>...<ВыражN> - произвольные рефал-выражения, записанные в эти стеки. При записи в стек записываемое выражение добавляется вместе с именем стека в копилку слева.

Первичная функция br1от bury - закопать. '<ИмяСтека>=<Выраж>' добавляет в копилку слева выражение аргумента и возвращает пустое выражение. Например, в результате выполнения

k/br/ 'X=A'. k/br/ 'X=B'.

в копилку слева добавится выражение ('X=B')('X=A'), т. е. образуется стек с именем X (если его ранее не было в копилке ) и в него добавится сначала символ 'A', а затем - символ 'B'.

Первичная функция dg2от dig out - выкопать. '<ИмяСтека>' ищет в копилке слева структурное выражение ('<ИмяСтека>=<Выраж>') с именем стека <ИмяСтека>, удаляет его из копилки (т. е. из стека с этим именем) и возвращает выражение <Выраж> ; в поле зрения. Если в копилке не найдется имени стека, то будет возвращено пустое выражение. Например, пусть копилка имеет вид:

('X=A')('Y=B')('Y=C').

Тогда после выполнения

k/dg/ 'Y'.

копилка примет вид:

('X=A')('Y=C'),

а функция возвратит значение 'B'.

Первичная функция cp3от copy - скопировать. <ИмяСтека> возвращает то же, что и функция dg. но не удаляет ничего из копилки ( копилка не изменяется). Например, после выполнения

k/dg/ 'Y'.

для результатов предыдущего примера копилка не изменится, а функция возвратит значение 'C'.

Первичная функция rp4от replace - заменить. '<ИмяСтека>=<Выраж>', так же как и функция br, добавляет в копилку слева структурное выражение с аргументом, но удаляет перед этим последнюю (самую левую) запись с именем стека в копилке, если она там была. Возвращает пустое выражение. Например, пусть копилка имеет вид:

('X=A')('Y=B')('Y=C').

Тогда после выполнения

k/rp/ 'Y=C'.

копилка примет вид: ('X=A')('Y=C')('Y=C').

Первичная функция dgall5от dig out all - выкопать все. возвращает в поле зрения все выражение копилки и очищает копилку полностью. Например, пусть копилка имеет вид:

('X=A')('Y=B')('Y=C').

Тогда после выполнения

k/dgall/ .

в поле зрения вернется ее выражение ('X=A')('Y=B')('Y=C') и копилка очистится.

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

Каждое вводимое число z сравнивается с числом, находящимся в вершине стека Y, и если оно меньше, то идет поиск его места в стеке X, а если оно не меньше, то числа из Y переписываются в X до тех пор, пока z не станет меньше, чем в вершине Y (или Y не станет пустым), после чего оно записывается в Y.

При поиске места z в X оно сравнивается с числом, находящимся в вершине X, и если оно меньше, то числа из X переписываются в Y до тех пор, пока z не станет не меньше, чем в вершине X - тогда оно записывается в X.

При вводе нуля (признак окончания ввода) остаток в X переписывается в Y, а затем извлекается из стека Y для вывода через пробел на экран.

SORT    START
        ENTRY    Sort
        EXTRN    prout, card, numb, symb, sub, first, br, dg, cp
			 
*                    ввод 1-го числа в стек Y
Sort    =        k/n1/ k/br/'Y=' k/numb/
                          k/prout/'Последовательность чисел: '. k/card/....
										 
*                  ввод очередного числа z
n1    E1    =  k/n2/ k/br/'Z=' k/numb/ k/card/... k/cp/'Z'..

*                   z=0? X–>Y : z–y
n2  /0/     =  k/t1/ k/cp/'X'..
    S(N)1   =  k/n3/ k/sub/ (S1) k/cp/'Y'...
	   
*                  z<y?
n3  '–'E1   =  k/nx/ k/cp/'X'..
    E1      =  k/ny/ k/cp/'Y'..
	  
*                  X=0? z->X : z–x
nx          =  k/n1/ k/br/'X=' k/dg/'Z'...
    S(N)1   =  k/n4/ k/sub/ (k/cp/'Z'.) S1..

*                       z<x? X->Y : Z->X
n4   '–'E1       =  k/br/'Y=' k/dg/'X'.. k/nx/ k/cp/'X'..
     E1          =  k/n1/ k/br/'X' k/dg'Z'...
	   
*                       Y=0? Z->Y : z–y
ny               =  k/n1/ k/br/'Y=' k/dg/'Z'...
     S(N)1       =  k/n5/ k/sub/ (k/cp/'Z'.) S1..
		
*                       z<y? Z->Y : Y->X
n5   '–'E1       =  k/br/'Y=' k/dg/'Z'.. k/n1/ k/cp/'X'..
     E1          =  k/br/'X' k/dg'Y'.. k/ny/ k/cp/'Y'..
		
*                       X=0? –>вывод Y : X–>Y
t1               =  k/m2/ () k/cp/'Y'..
     E1          =  k/t1/ k/br/'Y' k/dg/'X'..k/cp/'X'..
		 
*                       Y=0? –>печать (E) : –>вывод Y
m2  (E1)         =  k/pr/ E1.
    (E1)S(N)2    =  k/m2/ (E1' 'k/symb/S2.) k/dg/'Y'..
		
*                  выделение 80 символов для печати
pr          =
     E1     =  k/pr1/ k/first/ /80/ E1..
		 
*                  печать выделенного или остатка
pr1 (E1)E2  =  k/prout/ E1. k/pr/ E2.
    '*' E1  =  k/prout/ E1.
		 
END
< Лекция 4 || Лекция 5: 12 || Лекция 6 >