Изменить стиль страницы

Ваша программа должна сохранять симметричную роль обеих цепочек. Не начинайте проверять результат пробега цепочки а, не пробежав цепочки b, и изучайте обе цепочки разом.

Возьмем общую ситуацию:

а пройдена вплоть до i включительно;

b пройдена вплоть до j включительно;

обе части совпадают с точностью до пробелов.

ВЫПОЛНЯТЬ

  продвинуть <i>i</i> на следующий символ в <i>а</i>, не являющийся пробелом;

  продвинуть <i>j</i> на следующий символ в <i>b</i>, не являющийся пробелом;

  ЕСЛИ таких нет в <i>а</i> И таких нет в <i>b</i> ТО

  r := ИСТИНА;

    КОНЧЕНО КОНЕЦ_ЕСЛИ;

  ЕСЛИ таких нет в a ИЛИ таких нет в b ТО

  r := ЛОЖЬ;

    КОНЧЕНО КОНЕЦ_ЕСЛИ;

  ЕСЛИ <i>a</i>[<i>i</i>] ≠ <i>b</i>[<i>j</i>] ТО <i>r</i> := ЛОЖЬ;

  КОНЧЕНО КОНЕЦ_ЕСЛИ;

ВЕРНУТЬСЯ

Эта программа совершенно симметрична относительно а и b

Головоломка 33.

Нужно работать по модулю n. Удобнее всего пронумеровать элементы вектора от 0 до n − 1. Все элементы спускаются вниз на m по модулю n. Элемент, который переходит в 0, имеет номер m; элемент, который переходит в m, имеет номер 2m по модулю n; элемент, который переходит в 2m, имеет номер 3m по модулю n… Таким образом, мы получаем цепочку чисел, кратных m по модулю n. Весь вопрос в том, чтобы узнать, порождает ли последовательность чисел, кратных m по модулю n, последовательность всех целых от 0 до n − 1.

Это так, если m и n взаимно просты. В противном случае пусть с наибольший общий делитель m и n:

m = m'с, n = n'c,

n' * m = n' * m' * с = m' * n = 0 по модулю n.

Эта цепочка возвращается в 0 за n' = n/с операций. При этом пробегается не весь вектор, а только его элементы, сравнимые с 0 по модулю с.

Беря в качестве исходных элементов различных циклов последовательно целые числа от 0 до c − 1, вы разместите все элементы вектора, причем каждый из них будет перемещаться в точности один раз…

Головоломка 34.

Рассмотрите более общую задачу, что заставит вас открыть одно из этих знаменитых «преобразований программы», столь полезных, когда желательно улучшить уже существующие программы. Обозначим через t и u два условия, а через a и b — две последовательности инструкций. Вот простой цикл:

ПОКА <i>t</i> ВЫПОЛНЯТЬ

  ЕСЛИ <i>u</i> ТО <i>a</i> ИНАЧЕ <i>b</i>

КОНЕЦ_ЕСЛИ

ВЕРНУТЬСЯ

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

— проверяется условие t,

— если оно истинно, то проверяется u,

— если u истинно, то выполняется a, и все возобновляется.

Допустим, что условия t и u таковы, что я имею возможность проверить u, даже если проверка условия t дает значение ЛОЖЬ[29]. Тогда, пока условия t и u истинны, в цикле выполняется а.

Вот другая последовательность, которая может встретиться:

— проверяется условие t,

— если оно истинно, то проверяется u,

— если u ложно, то выполняется b, и все возобновляется.

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

ПОКА <i>t</i> ВЫПОЛНЯТЬ

  ПОКА <i>t</i> И <i>u</i> ВЫПОЛНЯТЬ <i>а</i> ВЕРНУТЬСЯ

  ПОКА <i>t</i> И НЕ <i>u</i> ВЫПОЛНЯТЬ <i>b</i> ВЕРНУТЬСЯ

ВЕРНУТЬСЯ

Мы перепишем программу для определения равнин, чтобы придать ей форму ПОКА, заключенного в скобки ЕСЛИ:

<i>i</i> := 1; <i>р</i> : = 0;

ПОКА <i>i</i> ≤ <i>n</i> ВЫПОЛНЯТЬ

  ЕСЛИ <i>a</i>[<i>i</i>] = <i>a</i>[<i>i</i> − <i>р</i>]

    ТО <i>x</i> := <i>a</i>[i]; <i>р</i> := <i>р</i> + 1; <i>i</i> := <i>i</i> + 1

    ИНАЧЕ <i>i</i> := <i>i</i> + 1

  КОНЕЦ_ЕСЛИ

ВЕРНУТЬСЯ

Мы обнаруживаем, что в нашем случае мы не можем объединить два условия с помощью операции И: если i не удовлетворяет условию, что i не больше n, то нельзя поставить вопрос относительно a[i]. Обрисуем трудность подходящим образом:

— нужно либо добавить в таблицу а поле, которое содержит какую-нибудь несущественную для нас величину (мы к этой величине не обращаемся);

— либо нужно допустить, что операция И не коммутативна. Для вычисления t и u мы вычисляем t, и если результат есть ЛОЖЬ, то все кончено и притом с результатом ЛОЖЬ. В противном случае результат есть значение условия u.

Тогда можно использовать наше преобразование:

<i>i</i> := 1; <i>р</i> := 0;

ПОКА <i>i</i> ≤ <i>n</i> ВЫПОЛНЯТЬ

  ПОКА <i>i</i> ≤ <i>n</i> И <i>а</i>[<i>i</i>] = <i>a</i>[<i>i</i> − <i>р</i>] ВЫПОЛНЯТЬ

    <i>x</i> := <i>а</i>[<i>i</i>]; <i>р</i> := <i>р</i> + 1; <i>i</i> := <i>i</i> + 1

  ВЕРНУТЬСЯ

  ПОКА <i>i</i> ≤ <i>n</i> И <i>а</i>[<i>i</i>] ≠ <i>a</i>[<i>i</i> − <i>р</i>] ВЫПОЛНЯТЬ

    <i>i</i> : = <i>i</i> + 1

  ВЕРНУТЬСЯ

ВЕРНУТЬСЯ

Первый цикл движется по таблице а, пока обнаруживается, что элементы равны между собой. Более точно, р и i изменяются одинаково, так что разность iр остается постоянной. Все элементы a[i] сравниваются с одним и тем же элементом, и величина x остается постоянной, равной этому элементу, на протяжении всего цикла.

вернуться

29

Вот пара условий, которая не обладает этим свойством: t: x ≠ 0; u: sin(1/x) > 0. — Примеч. ред.