Структурное программирование Неудобные ситуации в программировании Гиперфункция и оператор продолжения ветвей Свойства гиперфункций Правила доказательства.

Презентация:



Advertisements
Похожие презентации
7. Технология предикатного программирования Базовые трансформации Подстановка определения на место вызова Замена хвостовой рекурсии циклом Склеивание переменных.
Advertisements

3. Математические основы 3.3. Матиндукция 5. Построение языка предикатного программирования. Методы доказательства корректности предикатных программ Методы.
Однозначность предикатов Однозначность предикатов рекурсивного кольца Теорема об однозначности предикатов 4. Система правил доказательства корректности.
Лекция 2 Предикатное программирование 2. Постановка задачи дедуктивной верификации Программа в виде тройки Хоара, однозначность программы, тотальность.
Глава 6. УПРАВЛЯЮЩИЕ СТРУКТУРЫ Оператор присваивания Простой и составной операторы Условный оператор Оператор множественного выбора Оператор цикла с предусловием.
Математические основы Отношения порядка Наименьшая неподвижная точка Язык исчисления вычислимых предикатов (прод.) Рекурсивные определения типов Логическая.
Лекция 7. Структура языка С/С++. Операторы ветвления: условный оператор if. Полное ветвление. Неполное ветвление. Оператор множественного выбора switch.
Что нужно знать: динамическое программирование – это способ решения сложных задач путем сведения их к более простым задачам того же типа динамическое.
Язык исчисления вычислимых предикатов (прод.) Конструктор предиката Конструктор массива Программа на языке CCP Предикаты в качестве параметров предикатов.
Министерство образования Республики Беларусь Белорусский государственный университет Управляющие структуры языков программирования.
Массивы 9 класс. Основные теоретические сведения Примеры решения задач.
1 Программирование на языке Паскаль Ветвления. 2 Разветвляющиеся алгоритмы Задача. Ввести два целых числа и вывести на экран наибольшее из них. Идея решения:
Виды алгоритмических структур: –блок-схема. –линейный алгоритм. –алгоритмическая структура «ветвление». –алгоритмическая структура «выбор». –алгоритмическая.
1 Кубенский А.А. Функциональное программирование. Глава 4. Основы лямбда-исчисления Рекурсия в лямбда-исчислении fac = λn.(if (= n 0) 1 (* n (fac.
Операторы языка Си Лекция 5.
Язык программирования Delphi. Алфавит языка 53 буквы латинского алфавита и символ подчеркивания Цифры от 0 до 9 23 спец.символа
1 Программирование на языке Паскаль Циклы. 2 Цикл – это многократное выполнение одинаковой последовательности действий. цикл с известным числом шагов.
Автор: учитель информатики МКОУ Плесской средней общеобразовательной школы Юдин Андрей Борисович Часть 1.
Информационные технологии Выбор вариантов 2 1.Выполнение последовательности операторов. 2.Выполнение определенной последовательности операторов.
ЦИКЛИЧЕСКИЙ АЛГОРИТМ Цели: -Познакомиться с понятием циклического алгоритма. -Освоить языковые средства для реализации циклических алгоритмов.
Транксрипт:

Структурное программирование Неудобные ситуации в программировании Гиперфункция и оператор продолжения ветвей Свойства гиперфункций Правила доказательства корректности гиперфункций Пример. Вычисление суммы чисел в строке Пример. Решение системы линейных уравнений Лекция 8 Предикатное программирование

7. Технология предикатного программирования 7.4. Гиперфункции

Структурное программирование Эдвард Дейкстра begin ; end if then else end while do end

type SEQ(int p, q) = array p..q of T; int n; //номер последнего элемента списка SEQ(0, n) K;... if (n 0) return ; T b = K[1]; for (int i = 1; ;) { T a = K[i - 1]; for (int j = i; ; j = j – 1) { if (a b) { K[j] = b; break} elseif (j = 1) { K[0] = b; K[1] = a; break } else{ K[j] = a; a = K[j - 2] } } if (i = n) break else{ i = i + 1; b = K[i] } } b = K[i] ….. a b для a = K[i - 1] K[j] = b избыточный

Неудобные ситуации elemTwo(list(int) s: int e, bool exists ) {if (s = nil s.cdr = nil) exsts = false else { e = s.cdr.car || exists = true } } post exists = (s nil & s.cdr nil) & (exists e = s.cdr.car) elemTwo(s: e, exists); if (exists) B(e…) else D(…) (7.13) if ( s = nil s.cdr = nil ) (7.14) { exist = false; if (exist) B(e…) else D(…) } else {{e = s.cdr.car || exist = true }; if (exist) B(e…) else D(…)} if (s = nil s.cdr = nil) D(…) else { e = s.cdr.car; B(e…) }

P(x) { A(x: y, z) } Q(x, y, z), x, y и z возможно пустые Q(x, y, z) (C(x) S(x, y)) ( C(x) R(x, z)) (7.16) Предикаты S(x, y) и R(x, z) функции S: x y и R: x z. C(x) истинно Q(x, y, z) совпадает с S: x y C(x) ложно Q(x, y, z) совпадает с R: x z. Q(x, y, z) результат склеивания функций S: x y и R: x z. В Q(x, y, z) не определено значение набора z, если C(x) истинно. Аналогично, не определено значение y при ложном C(x). Лемма Если предикат S(x, y) тотален в области P(x) & C(x), а R(x, z) в области P(x) & C(x), то спецификация предиката A(x: y, z) является тотальной. Лемма Если предикат S(x, y) является однозначным в области P(x) & C(x), а R(x, z) в области P(x) & C(x), то спецификация предиката A(x: y, z) является однозначной.

P(x) { A(x: y, z) } Q(x, y, z) Q(x, y, z) (C(x) S(x, y)) ( C(x) R(x, z)) (7.16) Пусть вызов A используется во фрагменте вида: A(x: y, z); if (C(x)) B(y: u) else D(z: u) (7.17) Вычисление C(x) может оказаться трудоемким. Очевидно, что значение C(x) можно вычислить внутри определения предиката A. Введем логическую переменную c и следующим образом инструментируем определение предиката A, превратив его в A(x: c, y, z). Если некоторая ветвь тела предиката A завершается присваиванием набору y, то в конце этой ветви вставляется оператор c = true; если ветвь завершается вычислением z, то в конце ее вставляется c = false. Тогда фрагмент (7.17) можно заменить более эффективным: A(x: c, y, z); if (c) B(y: u) else D(z: u) (7.18)

Пример. Решение системы линейных уравнений type VEC(nat k) = array (real, 1..k); type MATRICE(nat k) = array(real, 1..k, 1..k); Lin (nat n, MATRICE(n) a, VEC(n) b: VEC(n) x) pre det(n, a) 0 // > post LinEq(n, a, x, b); Иногда требуется решить более общую задачу: Lin1(nat n, MATRICE(n) a, VEC(n) b: bool c; VEC(n) x) post c = (det(n, a) 0) & (c LinEq(n, a, x, b)); Lin1(n, a, b: c; x); if (c) B(x…) else D(…) LinEq(a, x, b)

P(x) { A(x: y, z) } Q(x, y, z) Q(x, y, z) (C(x) S(x, y)) ( C(x) R(x, z)) (7.16) A(x: y, z); if (C(x)) B(y: u) else D(z: u) (7.17) A(x: c, y, z); if (c) B(y: u) else D(z: u) (7.18) Рассмотрим подстановку определения A на место вызова в (7.18) с последующим упрощением. Обнаруживается, что втягивание условного оператора внутрь подставленного тела A может привести к появлению нескольких копий вызовов B(y…) и D(z…). Применим другое преобразование. Альтернативы условного оператора пометим метками 1 и 2. Получим оператор: if (c) 1: B(y: u) else 2: D(z: u) (7.19) Далее в подставленном теле A всякий оператор c = true заменим на #1, а c = false на #2. Операторы #1 и #2 операторы goto 1 и goto 2. Например: if ( s = nil s.cdr = nil ) #2 else { e = s.cdr.car; #1 }; if (exist) 1: B(e…) else 2: D(…)

Q(x, y, z) (C(x) S(x, y)) ( C(x) R(x, z)) (7.16) A(x: y, z); if (C(x)) B(y: u) else D(z: u) (7.17) A(x: c, y, z); if (c) B(y: u) else D(z: u) (7.18) if (c) 1: B(y: u) else 2: D(z: u) (7.19) Модифицированный фрагмент (7.18) запишем в виде: A(x: y, z); if (c) 1: B(y: u) else 2: D(z: u) (7.20) A блок, результат подстановки определения A и замены c = true и c = false на #1 и #2. Заменим оператор (7.19) последовательностью: 1: B(y: u); goto 3; 2: D(z: u); 3: (7.21) Композицию вида (7.21) запишем в следующем виде: case 1: B(y: u) case 2: D(z: u) (7.22) case 1: B(y: u) и case 2: D(z: u) операторы продолжения ветви, а вместе обработчик ветвей. Обработчику ветвей предшествует оператор, содержащий операторы перехода. Если предшествующий оператор нормально завершается (не оператором перехода), то обработчик ветвей пропускается.

A(x: y, z); if (C(x)) B(y: u) else D(z: u) (7.17) A(x: c, y, z); if (c) B(y: u) else D(z: u) (7.18) if (c) 1: B(y: u) else 2: D(z: u) (7.19) A(x: y, z); if (c) 1: B(y: u) else 2: D(z: u) (7.20) 1: B(y: u); goto 3; 2: D(z: u); 3: (7.21) case 1: B(y: u) case 2: D(z: u) (7.22) Отметим, что оператор (7.19) эквивалентен: if (c) #1 else #2; case 1: B(y: u) case 2: D(z: u) (7.23) Отметим также, что блок A(x: y, z) эквивалентен композиции: A(x: c, y, z); if (c) #1 else #2 (7.24) В итоге, исходный фрагмент (7.17) эквивалентен: A(x: y, z) case 1: B(y: u) case 2: D(z: u) (7.25) Исходный предикат A(x: y, z) будем записывать в виде A(x: y #1: z #2) и называть гиперфункцией с двумя ветвями. Метки 1 и 2 являются метками ветвей.

P(x) { A(x: y, z) } Q(x, y, z) Q(x, y, z) (C(x) S(x, y)) ( C(x) R(x, z)) (7.16) A(x: y, z); if (C(x)) B(y: u) else D(z: u) (7.17) case 1: B(y: u) case 2: D(z: u) (7.22) A(x: y, z); case 1: B(y: u) case 2: D(z: u) (7.25) Определение гиперфункции : A(x: y #1: z #2) pre P(x); pre 1: C(x) (7.26) { A(x: y, z) } post 1: S(x, y); post 2: R(x, z); Блок A(x: y, z) тело гиперфункции, P(x) общее предусловие, C(x) предусловие первой ветви гиперфункции, S(x, y) постусловие первой ветви, R(x, z) постусловие второй ветви. Фрагмент (7.17) записывается следующим образом: A(x: y #1: z #2) case 1: B(y: u) case 2: D(z: u) (7.27) Для композиции (7.27) допускается более компактная запись: A(x: y : z) case B(y: u) case D(z: u) (7.28)

elemTwo(list (int) s : int e : ) pre 1: s nil & s.cdr nil {if (s = nil s.cdr = nil) #2 else { e = s.cdr.car #1 } } post 1: e = s.cdr.car; elemTwo(list (int) s : int e #1 : #2) elemTwo(s: e: ) case B(e…) case D(…) elemTwo B(e…) D(…)

A(x: y #1: z #2) pre P(x); pre 1: C(x) (7.26) { A(x: y, z) } post 1: S(x, y); post 2: R(x, z); V(x, u) pre P V (x) { A(x: y : z) case B(y: u) case D(z: u) } post Q V (x, u); (7.29) P(x) { A(x: y, z) } Q(x, y, z), {P B (y)} B {Q B (y, u)}, {P D (z)} D {Q D (z, u)}, Q(x, y, z) (C(x) S(x, y)) ( C(x) R(x, z)) (7.16) V(x, u) pre P V (x) {A(x: y, z); if (C(x)) B(y: u) else D(z: u)} post Q V (x, u);(7.30) H(x, y, z, u) pre P H (x, y, z ){if (C(x)) B(y: u) else D(z: u)} post Q H (x, y, z, u) Получим формулы для предусловия и постусловия предиката H: P H (x, y, z ) (C(x) P B (y)) ( C(x) P D (z)) Q H (x, y, z, u) (C(x) Q B (y, u)) ( C(x) Q D (z, u)) Определение (7.30) эквивалентно следующему: V(x, u) pre P V (x) {A(x: y, z); H(x, y, z, u)} post Q V (x, u); (7.31) Построим правила RS1 и RS2 для суперпозиции в определении (7.31): Правило RS1. P V (x) P(x) y,z (Q(x, y, z) P H (x, y, z )) Правило RS2. P V (x) & y,z (Q(x, y, z) & Q H (x, y, z, u)) Q V (x, u) Далее подставим формулы для Q, P H и Q H. Получим правила: Правило RS1. P V (x) P(x) y,z((C(x) S(x, y)) ( C(x) R(x, z)) (C(x) P B (y)) ( C(x) P D (z))) Правило RS2. P V (x) & y,z ((C(x) S(x, y)) ( C(x) R(x, z)) & (C(x) Q B (y, u)) ( C(x) Q D (z, u))) Q V (x, u) Специализация по C(x)

A(x: y #1: z #2) pre P(x); pre 1: C(x) (7.26) { A(x: y, z) } post 1: S(x, y); post 2: R(x, z); V(x, u) pre P V (x) { A(x: y : z) case B(y: u) case D(z: u) } post Q V (x, u); (7.29) P(x) { A(x: y, z) } Q(x, y, z), {P B (y)} B {Q B (y, u)}, {P D (z)} D {Q D (z, u)}, Q(x, y, z) (C(x) S(x, y)) ( C(x) R(x, z)) (7.16) Правило RS9. P V (x) P(x) Правило RS10. P V (x) C(x) y (S(x, y) P B (y)) Правило RS11. P V (x) C(x) z (R(x, z) P D (z)) Правило RS12. P V (x) C(x) & y (S(x, y) & Q B (y, u)) Q V (x, u) Правило RS13. P V (x) C(x) & z (R(x, z) & Q D (z, u)) Q V (x, u) В итоге, справедлива следующая лемма: Лемма Пусть предусловие P V (x) истинно. Допустим, предикаты A, B и D корректны. Если правила RS9 RS13 истинны, то предикат V, представленный определением (7.29) является корректным.

A(x: y #1: z #2) pre P(x); pre 1: C(x) (7.26) { A(x: y, z) } post 1: S(x, y); post 2: R(x, z); V(x, u) pre P V (x) { A(x: y : z) case B(y: u) case D(z: u) } post Q V (x, u); (7.29) P(x) { A(x: y, z) } Q(x, y, z), {P B (y)} B {Q B (y, u)}, {P D (z)} D {Q D (z, u)}, Q(x, y, z) (C(x) S(x, y)) ( C(x) R(x, z)) (7.16) Правила серии L для (7.29) Для (7.31) построим правила LS1 и LS2: Правило LS1. P V (x) P(x) Правило LS2. P V (x) & Q V (x, u) & Q(x, y, z) P H (x, y, z) & Q H (x, y, z, u) Для правила LS2 подставим формулы для Q, P H и Q H. Получим: Правило LS2. P V (x) & Q V (x, u) & (C(x) S(x, y)) ( C(x) R(x, z)) (C(x) P B (y)) ( C(x) P D (z)) & (C(x) Q B (y, u)) ( C(x) Q D (z, u)) Специализация по условию C(x) дает следующие правила: Правило LS10. P V (x) P(x) Правило LS11. P V (x) & Q V (x, u) & C(x) & S(x, y) P B (y) & Q B (y, u) Правило LS12. P V (x) & Q V (x, u) & C(x) R(x, z) P D (z) & Q D (z, u) Лемма Допустим, спецификация предиката V реализуема, предикаты A, B и D однозначны в области предусловий и корректны, а их спецификации однозначны. Если правила LS10, LS11 и LS12 истинны, то предикат V, представленный определением (7.29) является корректным.

Пример 7.4. Определим гиперфункцию Comp следующей спецификацией: Comp(list (int) s: : int d, list (int) r) pre 1: s = nil post 2: d = s.car & r = s.cdr; Построим определение предиката elemTwo через гиперфункцию Comp без полей car, cdr и конструктора nil. elemTwo(list (int) s: int e #1: #2) pre 1: s != nil & s.cdr != nil {int e1; list (int) s1, s2; Comp(s: : e1, s1) case #2 case { Comp(s1: : e, s2) case #2 case #1 } } post 1: e = s.cdr.car;

Если оператор продолжения ветви содержит только оператор перехода, то оператор перехода переносится в конец соответствующей ветви вызова гиперфункции. elemTwo(list (int) s: int e #1: #2) pre 1: s != nil & s.cdr != nil {Comp(s: #2: int e1, list (int) s1) case Comp(s1: #2: e, list(int) s2 #1); } post 1: e = s.cdr.car; Реализован перенос описаний локальных переменных e1, s1 и s2 в позиции их определения. Если обработчик ветвей состоит из единственного оператора продолжения ветви, а предшествующий оператор не имеет нормального завершения исполнения, то обработчик ветвей вырождается. В этом случае оператор продолжения ветви устраняется, а находящийся в нем оператор композируется с предыдущим оператором в виде оператора суперпозиции. elemTwo(list (int) s: int e #1: #2) pre 1: s != nil & s.cdr != nil {Comp(s: #2: _, list (int) s1); Comp(s1: #2: e, _ #1); } post 1: e = s.cdr.car ; Вхождения локальных переменных e1 и s2 заменены стандартным именем _, означающим, что результат игнорируется при исполнении.

Гиперфункция и оператор продолжения ветвей H(x: y : z) (C(x) P(x, y)) ( C(x) Q(x, z)) H(x: y : z) case R(y: …) case S(z: …) x y z H R S C(x) dom(H) H(x: Y y, Z z) (C(x) P(x, y)) ( C(x) Q(x, z)) ветвь гиперфункции

if ( (x)) H(x…: … : …) { A; B } case B else { C; D } case D H(X x…: … : …) { if ( (x)) {A #1} else {C #2} } Свойства гиперфункций

Пример. Сумма чисел в строке СуммаСтроки(string s: nat n) post P(s, n); Предикат P(s, n) определяется рекурсивным образом. цифра(char c) c {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; число(string s) s nil & s1,s2 string c char (s = s1 + c + s2 цифра(c)) val(string s: nat n) pre число(s) post n значение числа s. P(string s, nat n) (s = nil n = 0) & & (число(s) n = val(s)) & ( s1 string c char. цифра(c) & s = c + s1 P(s1, n)) & & ( s1,s2 string c char. цифра(c) & число(s1) & s = s1 + c + s2 m. P(s2, m) & n = val(s1) + m) string есть list(char), а + обозначает конкатенацию строк. Лемма Спецификация предиката СуммаСтроки тотальна.

Обобщение исходной задачи : Сумма(string s, nat m: nat n) post x. P(s, x) & n = m + x; Ввиду истинности СуммаСтроки(s: n) Сумма(s, 0: n) корректно следующее определение предиката: СуммаСтроки(string s: nat n) {Сумма(s, 0: n)} post P(s, n); Алгоритм: Находится первая цифра. Вычисляется число, начинающееся с этой цифры. Для оставшейся части строки действие алгоритма повторяется. При нахождении первой цифры возможна ситуация, когда строка не содержит цифр. Данную ситуацию обозначим предикатом: мусор(string s) s1,s2 string c char (s = s1 + c + s2 цифра(c)) В частности, предикат истинен для пустой строки. Определение первой цифры выражается гиперфункцией: перваяЦифра(string s: : char e, string r) pre 1: мусор(s) post 2: w. мусор(w) & s = w + e + r & цифра(e); Лемма Спецификация гиперфункции перваяЦифра тотальна.

Отметим, что можно было бы использовать гиперфункцию вида: перваяЦифра1(string s: : string r), где r начинается с первой цифры, однако в реализации это приведет к повторному доступу к одной и той же литере в строке. В практике типично решение: перваяЦифра2(string s: char e, string r) Здесь отсутствие цифр в строке s кодируется некоторым значением e, отличным от цифры. Вред подобных трюков обычно проявляется для сложных программ. Решение в виде гиперфункции дает более короткую и эффективную программу. Вторая часть алгоритма выделение числа в строке и его вычисление представляется предикатом: числоВстроке(char e, string r: nat v, string t) pre цифра(e) post w. конецЧисла(r, w, t) & v = val(e + w); конецЧисла(r, w, t) r = w + t & (w = nil число(w)) & (t = nil цифра(t.car); Лемма Спецификация предиката числоВстроке тотальна.

Сумма(string s, nat m : nat n) {перваяЦифра(s: : char e, string r) case n = m case {числоВстроке(e, r: nat v, string t); Сумма(t, m + v: n)} } post x. P(s, x) & n = m + x measure len(s); A(x: y, z); if (C(x)) B(x, y: u) else D(x, z: u)(7.17) Правило FG1.R(x, u) P(x) Правило FG2.R(x, u) & С(x) & S(x, y) LS(B(x, y: u)) Правило FG3.R(x, u) & С(x) & G(x, z) LS(D(x, z: u)) FG2 : lemma Q_Сумма(s,m,n) & P1_ перваяЦифра(s) => n = m; FG3: Q_Сумма(s,m,n) & not P1_ перваяЦифра(s) & Q2_ перваяЦифра(s,e,r) LS(числоВстроке(e, r: nat v, string t); Сумма(t, m + v: n)) Правило FS5.R(x, y) P(x) правила для суперпозиции Правило FS6.R(x, y) & Q(x, z) LS(B(x, z: y)) FS6 : Q_Сумма(s,m,n) & Q2_ перваяЦифра(s,e,r) & Q_ числоВстроке(e,r,v, t) LS(Сумма(t, m + v: n)) числоВстроке(char e, string r: nat v, string t) pre цифра(e) post w. конецЧисла(r, w, t) & v = val(e + w); конецЧисла(r, w, t) r = w + t & (w = nil число(w)) & (t = nil цифра(t.car); перваяЦифра(string s: : char e, string r) pre 1: мусор(s) post 2: w. мусор(w) & s = w + e + r & цифра(e);

перваяЦифра(string s: : char e, string r) pre 1: мусор(s) { switch (s) { case nil: #1 case cons(a, u): if (цифра(a)) { r = u || e = a #2} else перваяЦифра(u: #1 : e, r #2) } } post 2: w. мусор(w) & s = w + e + r & цифра(e) measure len(s); Это эквивалентно: if (s = nil) P1_перваяЦифра(s) else if (цифра(s.car)) { r = s.cdr || e = s.car || мусор(s) } else { перваяЦифра(s.cdr : : e, r ) || !!! if (мусор(s.cdr)) мусор(s) else мусор(s) } Q_перваяЦифра(s, e, r) = мусор(s) or Q2_перваяЦифра(s, e, r) пЦ: lemma Q_перваяЦифра(s, e, r) => s = nil ? мусор(s) : цифра(s.car) ? r = s.cdr & e = s.car & мусор(s) : len(s.cdr) < len(s) & Q_перваяЦифра(s.cdr, e, r) & ( мусор(s.cdr)? мусор(s) : мусор(s) )

Чтобы получить хвостовую рекурсию для предиката числоВстроке необходимо ввести накопитель для вычисления значения очередного числа. Определим предикат: взятьЧисло(string r, nat p: nat v, string t) post w. конецЧисла(r, w, t) & val1(w, p, v); val(s: n) pre число(s) post val1(s, 0, n); formula val1(stringw, nat p, n) = w = nil ? n = p : val1(w.cdr, p 10 + valD(w.car), n) где valD(e) значение цифры e. Тогда корректно следующее определение предиката: числоВстроке(char e, string r: nat v, string t) pre цифра(e) { взятьЧисло(r, val(e): v, t) } post w. конецЧисла(r, w, t) & v = val(e + w); конецЧисла(r, w, t) r = w + t & (w = nil число(w)) & (t = nil цифра(t.car);

взятьЧисло(string r, nat p: nat v, string t) { if (r = nil) {v = p || t = r} else {{char b = r.car || string q = r.cdr}; if (цифра(b)) взятьЧисло(q, p 10 + val(b): v, t) else {v = p || t = q} } } post w. конецЧисла(r, w, t) & val1(w, p, v) measure len(r); Доказательство. Пусть истинно постусловие взятьЧисло. Докажем истинность тела. Пусть истинно условие r = nil. Тогда w = nil и t = nil. Далее, v = p. Это доказывает истинность оператора v = p || t = r. Пусть истинно условие r nil. Докажем истинность второй альтернативы внешнего условного оператора. Истинны предусловия операций car и cdr. Далее достаточно доказать истинность внутреннего условного оператора при b = r.car и q = r.cdr. Тогда r = b + q. Пусть истинно условие цифра(b). Докажем истинность рекурсивного вызова. Поскольку len(q) < len(r), то рекурсивный вызов можно заменить его постусловием: h. конецЧисла(q, h, t) & val1(h, p 10 + val(b), v) (7.37) Докажем истинность (7.37). Поскольку r = b + q и цифра(b), то w = b + h для некоторого h и истинно конецЧисла(q, h, t). Из определения val1 при r nil следует, что val1(w, p, v) val1(h, p 10 + val(b), v). В итоге, истинна формула (7.37). конецЧисла(r, w, t) r = w + t & (w = nil число(w)) & (t = nil цифра(t.car);

Сумма: n m; s r, t; Склеивания перваяЦифра: s r, u; e a; числоВстроке: s r, t;замена на s для упрощения взятьЧисло: s r, q, t; v p; СуммаСтроки(string s: nat n){Сумма(s, 0: n)}; Сумма(string s, nat n : nat n) {перваяЦифра(s: : char e, string s) case n = n case {числоВстроке(e, s: nat v, string s); Сумма(s, n + v: n)} } перваяЦифра(string s: : char e, string s) { switch (s) { case nil: #1 case cons(e, s): if (цифра(e)) { s = s || e = e #2} else перваяЦифра(s: #1 : e, s #2) }

числоВстроке(char e, string s: nat v, string s) { взятьЧисло(s, val(e): v, s) } взятьЧисло(string s, nat v: nat v, string s) { if (s = nil) {v = v || s = s} else {{char b = s.car || s = s.cdr }; if (цифра(b)) взятьЧисло(s, v 10 + val(b): v, s) else {v = v || s = s} } }; Замена хвостовой рекурсии циклом. Удаляются операторы вида n = n. Удаляется оператор продолжения case n = n СуммаСтроки(string s: nat n){Сумма(s, 0: n)}; Сумма(string s, nat n : nat n) {for (; ;) { перваяЦифра(s: #M1 : char e, string s #2) case 2:{числоВстроке(e, s: nat v, string s); n = n + v} } M1: }

перваяЦифра(string s: #1 : char e, string s #2) {for (; ;) { switch (s) { case nil: #1 case cons(e, s): if (цифра(e)) #2 else {} } числоВстроке(char e, string s: nat v, string s) { взятьЧисло(s, val(e): v, s) } взятьЧисло(string s, nat v: nat v, string s) {for (; ;) { if (s = nil) break else {{char b = s.car || s = s.cdr}; if (цифра(b)) v = v 10 + val(b) else break } };

Подставим определения предикатов взятьЧисло и перваяЦифра на место вызовов. Проведем очевидные упрощения. Сумма(string s, nat n : nat n) {for (; ;) { for (; ;) { switch (s) { case nil: #M1 case cons(char e, s): if (цифра(e)) #2 } case 2:{числоВстроке(e, s: nat v, string s); n= n + v} } M1: } числоВстроке(char e, string s: nat v, string s) { v = val(e); for (; ;) { if (s = nil) break {char b = s.car || s = s.cdr}; if (цифра(b)) v = v 10 + val(b) else break } };

Поскольку внутренний цикл в теле предиката Сумма не имеет нормального выхода, можно заменить оператор #2 на break и убрать скобки оператора продолжения ветви. Подставим определение числоВстроке на место вызова, а затем определение предиката Сумма в тело СуммаСтроки. СуммаСтроки(string s: nat n) (7.38) {n = 0; for (; ;) { if (s = nil) return { char e = s.car || s = s.cdr }; if (цифра(e)) break } nat v = val(e); for (; ;) { if (s = nil) break {char b = s.car || s = s.cdr }; if (цифра(b)) v = v 10 + val(b) else break } n = n + v }

Кодирование последовательностей Необходимо кодировать начальное значение строки s и текущее. Начальное состояние вырезка S[m..p], текущее S[j..p]. Массив S и величины j, m, p должны быть определены в программе, причем j переменная, а m и p могут быть константами. type STR = array (char, M..N); STR S; int j = m; Значения границ M и N должны быть достаточными, т.е. M m, p, j N. Операции с последовательностью s кодируются следующим образом: s = nil j > p s.car S[j] s = s.cdr j = j + 1

type STR = array (char, M..N);(7.39) STR S; nat n = 0; for (int j = m; ;) { for (; ;) { if (j > p) return char e = S[j]; j = j + 1; if (цифра(e)) break } nat v = val(e); for (;j

Имеется недостаток программы (7.39): если строка s завершается цифрой, то проверка исчерпания строки реализуется дважды. Чтобы исправить недостаток, следует вместо предиката числоВстроке(e, r: v, t) использовать гиперфункцию числоВстроке1(e, r: v1: v2, t), в которой первая ветвь реализуется при исчерпании входной строки r. Возможен другой более простой алгоритм. На очередном шаге вычисляется значение очередного числа в строке в виде предиката числоВстроке(s: v, t), где v значение очередного числа, а t остаток непустой строки s. Если первый элемент s не является цифрой, то v = 0. Реализация данного алгоритма дает более короткую программу, чем (7.39), однако менее эффективную. По сравнению с (7.39) лишними действиями являются операторы v = 0 и n = n + v для каждого символа, отличного от цифры.

int n=0, j=0; while (j < N) { e=S[j++]; w = 0; while (j < N && String.IsNumber(e)) { w = int.Parse(e) + w * 10; e = S[j++]; } n = n + w; }