Как изменить программу, чтобы это учесть?
Указание. Состояний будет три: основное, внутри коммента-
рия, внутри строки.
5.1.5. Еще одна возможность многих реализаций паскаля - это
комментарии вида
i:=i+1; (* here i is increased by 1 *)
при этом закрывающая скобка должна соответствовать открываюшей
(то есть { ... *) не разрешается). Как удалять такие коммента-
рии?
5.2. Ввод чисел
Пусть десятичная запись числа подается на вход программы
символ за символом. Мы хотим "прочесть" это число (поместить в
переменную типа real его значение). Кроме того, надо сообщить об
ошибке, если число записано неверно.
Более конкретно, представим себе такую ситуацию. Последова-
тельность символов на входе делится на прочитанную и оставшуюся
части. Мы можем пользоваться функцией Next:char, которая возвра-
щает первый символ оставшей части, а также функцией Move, кото-
рая перводит забирает первый символ из оставшейся части, перево-
дя его в категорию прочитанных.
---------------------|--------------------------
прочитанная часть | Next | ? | ? | ? |
---------------------|--------------------------
Будем называть десятичной записью такую последовательность сим-
волов:
<0 или более пробелов> <1 или более цифр>
а также такую:
<0 или более пробелов> <1 или более цифр>.<1 или более цифр>
Заметим, что согласно этому определению '1.', '.1', '1. 1',
'-1.1' не являются десятичными записями. Сформулируем теперь за-
дачу точно:
5.2.1. Прочесть из входной строки максимальную часть, кото-
рая может быть началом десятичной записи. Определить, является
ли эта часть десятичной записью или нет.
Решение. Запишем программу на паскале (используя "перечис-
лимый тип" для наглядности записи: переменная state может прини-
мать одно из значений, указанных в скобках).
var state:
(Accept, Error, Initial, IntPart, DecPoint, FracPart);
state := Initial;
while (state <> Accept) or (state <> Error) do begin
| if state = Initial then begin
| | if Next = ' ' then begin
| | | state := Initial; Move;
| | end else if Digit(Next) then begin
| | | state := IntPart; {после начала целой части}
| | | Move;
| | end else begin
| | | state := Error;
| | end;
| end else if state = IntPart then begin
| | if Digit (Next) then begin
| | | state := IntPart; Move;
| | end else if Next = '.' then begin
| | | state := DecPoint; {после десятичной точки}
| | | Move;
| | end else begin
| | | state := Accept;
| | end;
| end else if state = DecPoint then begin
| | if Digit (Next) then begin
| | | state := FracPart; Move;
| | end else begin
| | | state := Error; {должна быть хоть одна цифра}
| | end;
| end else if state = FracPart then begin
| | if Digit (Next) then begin
| | | state := FracPart; Move;
| | end else begin
| | | state := Accept;
| | end;
| end else if
| | {такого быть не может}
| end;
end;
Заметьте, что присваивания state:=Accept и state:=Error не соп-
ровождаются сдвигом (символ, который не может быть частью числа,
не забирается).
Приведенная программа не запоминает значение прочитанного
числа.
5.2.2. Решить предыдущую задачу с дополнительным требовани-
ем: если прочитанный кусок является десятичной записью, то в пе-
ременную val:real следует поместить ее значение.
Решение. При чтении дробной части используется переменная
step - множитель при следующей десятичной цифре.
state := Initial; val:= 0;
while (state <> Accept) or (state <> Error) do begin
| if state = Initial then begin
| | if Next = ' ' then begin
| | | state := Initial; Move;
| | end else if Digit(Next) then begin
| | | state := IntPart; {после начала целой части}
| | | val := DigitValue (Next);
| | | Move;
| | end else begin
| | | state := Error;
| | end;
| end else if state = IntPart then begin
| | if Digit (Next) then begin
| | | state := IntPart; val := 10*val + DigitVal(Next);
| | | Move;
| | end else if Next = '.' then begin
| | | state := DecPoint; {после десятичной точки}
| | | step := 0.1;
| | | Move;
| | end else begin
| | | state := Accept;
| | end;
| end else if state = DecPoint then begin
| | if Digit (Next) then begin
| | | state := FracPart;
| | | val := val + DigitVal(Next)*step; step := step/10;
| | | Move;
| | end else begin
| | | state := Error; {должна быть хоть одна цифра}
| | end;
| end else if state = FracPart then begin
| | if Digit (Next) then begin
| | | state := FracPart;
| | | val := val + DigitVal(Next)*step; step := step/10;
| | | Move;
| | end else begin
| | | state := Accept;
| | end;
| end else if
| | {такого быть не может}
| end;
end;
5.2.3. Та же задача, если перед число может стоять знак
"минус" или знак "плюс" (а может ничего не стоять).
Формат чисел в этой задаче обычно иллюстрируют такой кар-
тинкой:
----- ---------
---| + |---->-| цифра |-------->--------------------->
| ----- | | --------- | | |
| ----- | | | | ----- --------- |
|-| - |--| |----<------| |-| . |->---| цифра |--|
| ----- | ----- | --------- |
| | |-----<-----|
|--->----|
5.2.4. Та же задача, если к тому же после числа может сто-
ять показатель степени десяти, как в 254E-4 (=0.0254) или в
0.123E+9 (=123000000). Нарисуйте соответствующую картинку.
5.2.5. Что надо изменить в программе задачи 5.2.2, чтобы
разрешить пустые целую и дробную части (как в '1.', '.1' или да-
же '.' - последнее число считаем равным нулю)?
Мы вернемся к конечным автоматам в главе 10 (Сравнение с
образцом).
Глава 6. Типы данных.
6.1. Стеки.
Пусть Т - некоторый тип. Рассмотрим (отсутствующий в паска-
ле) тип "стек элементов типа Т". Его значениями являются после-
довательности значений типа Т.
Операции:
Сделать_пустым (var s: стек элементов типа Т).
Добавить (t: T; var s: стек элементов типа Т).
Взять (var t: T; var s: стек элементов типа Т).
Пуст (s: стек элементов типа Т): boolean
Вершина (s: стек элементов типа Т): T
(Мы пользуемся обозначениями, наполняющими паскаль, хотя в
паскале типа "стек" нет.) Процедура "Сделать_пустым" делает стек
s пустым. Процедура "Добавить" добавляет t в конец последова-
тельности s. Процедура "Взять" определена, если последова-
тельность s непуста; она забирает из неё последний элемент, ко-
торый становится значением переменной t. Выражение "Пуст(s)" ис-
тинно, если последовательность s пуста. Выражение "Вершина(s)"
определено, если последовательность s непуста, и равно последне-
му элементу последовательности s.
Мы покажем, как моделировать стек в паскале и для чего он
может быть нужен.
Моделирование ограниченного стека в массиве.
Будем считать, что количество элементов в стеке не превос-
ходит некоторого числа n. Тогда стек можно моделировать с по-
мощью двух переменных:
Содержание: array [1..n] of T;
Длина: integer;
считая, что в стеке находятся элементы Содержание [1],...,Содер-
жание [длина].
Чтобы сделать стек пустым, достаточно положить
Длина := 0
Добавить элемент t:
{Длина < n}
Длина := Длина+1;
Содержание [Длина] :=t;
Взять элемент в переменную t:
t := Содержание [Длина];
Длина := Длина - 1;
Стек пуст, если Длина = 0.
Вершина стека равна Содержание [Длина].
Таким образом, вместо переменной типа стек в программе на паска-
ле можно использовать две переменные Содержание и Длина. Можно
также определить тип стек, записав
const N = ...
type stack = record
Содержание: array [1..N] of T;
Длина: integer;
end;
(Мы позволяем себе использовать имена переменных из русских
букв, хотя обычно паскаль этого не любит.) После этого могут
быть - в соответствии с правилами паскаля - описаны процедуры
работы со стеком. Например, можно написать
procedure Добавить (t: T; var s: stack);
begin
| {s.Длина , N}
| s.Длина := s.Длина + 1;
| s.Содержание [s.Длина] := t;
end;
Использование стека.
Будем рассматривать последовательности открывающихся и зак-
рывающихся круглых и квадратных скобок ( ) [ ]. Среди всех таких
последовательностей выделим правильные - те, которые могут быть
получены по таким правилам:
1) пустая последовательность правильна.
2) если А и В правильны, то и АВ правильна.
3) если А правильна, то [A] и (A) правильны.
Пример. Последовательности (), [[]], [()[]()][] правильны,
а последовательности ], )(, (], ([)] - нет.
6.1.1. Проверить правильность последовательности за время,
не превосходящее константы, умноженной на её длину. Предполага-
ется, что члены последовательности закодированы числами:
( 1
[ 2
) -1
] -2
Решение. Пусть a[1]..a[n] - проверяемая последовательность.
Рассмотрим стек, элементами которого являются открывающиеся
круглые и квадратные скобки (т. е. 1 и 2).
Вначале стек делаем пустым. Далее просматриваем члены пос-
ледовательности слева направо. Встретив открывающуюся скобку
(круглую или квадратную), помещаем её в стек. Встретив закрыва-
ющуюся, проверяем, что вершина в стеке - парная ей скобка; если
это не так, то можно утверждать, что последовательность непра-
вильна, если скобка парная, то заберем её (вершину) из стека.
Последовательность правильна, если в конце стек оказывается
пуст.
Сделать_Пустым (s);
i := 0; Обнаружена_Ошибка := false;
{прочитано i символов последовательности}
while (i < n) and not Обнаружена_Ошибка do begin
| i := i + 1;
| if (a[i] = 1) or (a[i] = 2) then begin
| | Добавить (a[i], s);
| end else begin {a[i] равно -1 или -2}
| | if Пуст (s) then begin
| | | Обнаружена_Ошибка := true;
| | end else begin
| | | Взять (t, s);
| | | Обнаружена ошибка := (t <> - a[i]);
| | end;
| end;
end;
Правильно := (not Обнаружена_Ошибка) and Пуст (s);
Убедимся в правильности программы. (1) Если последова-
тельность построена по правилам, то программа даст ответ "да".
Это легко доказать индукцией по построению правильной последова-
тельности. Надо проверить для пустой, для последовательности AB
в предположении, что для A и B уже проверено - и для последова-
тельностей [A] и (A) - в предположении, что для A уже проверено.
Для пустой очевидно. Для AB действия программы происходят как
для A и кончаются с пустым стеком; затем все происходит как для
B. Для [A] сначала помещается в стек открывающая квадратная
скобка и затем все идет как для A - с той разницей, что в глуби-
не стека лежит лишняя скобка. По окончании A стек становится
пустым - если не считать этой скобки - а затем и совсем пустым.
Аналогично для (A).
(2) Покажем, что если программа завершает работу с ответом
"да", то последовательность правильная. Рассуждаем индукцией по
длине последовательности. Проследим за состоянием стека в про-
цессе работы программы. Если он в некоторый промежуточный момент
пуст, то последовательность разбивается на две части, для каждой
из которых программа дает ответ "да"; остается воспользоваться
предположением индукции и определением правильности. Пусть стек