=======Алгоритм преобразует алгоритм!======= {{ :tema:untitled-1.1.2.jpg?120x120}} При программировании на Delphi или Паскале иногда попадаются задачи, которые трудно "втиснуть" в стандартные конструкции языка. А решение лежит совсем рядом - в теории конечных автоматов. Мы не будем залезать в дебри, а просто покажем как это делается. =====Предыстория===== >Давным-давно, когда люди еще не придумали объектно-ориентированное программирование, модным направлением было программирование структурное. Шутки шутками, но в результате именно структурного подхода мы сейчас имеем Pascal и Delphi. >Почему я говорю то Паскаль, то Дельфи? Просто потому, что лингвистическая основа Delphi - это Object Pascal, сильно выросший из детских штанишек, но все же узнаваемый. И новые объектно-ориентированные возможности и замечательные библиотеки классов в совокупности с CASE-средствами так и не скрыли полностью длинные уши структурного языка (и это замечательно!). Они вылезают то здесь, то там, в отдельных процедурах, в обработчиках событий... Так вот, в те давние времена возникла следующая ситуация: * "Сочинение" алгоритмов решения различных задач - процесс творческий, а творчество очень не любит каких-либо ограничений. Cтало быть алгоритм может быть любым, сколь угодно запутанным, образующим петли и прочие нелинейности. * Стандартный Паскаль имеет очень ограниченное количество структурных инструкций ( if-then-else, while-do и т.д., вы это лучше меня знаете...) * А программу-то написать хочется! Что делать ? * А нельзя ли как-нибудь "втиснуть" этот наш премудрый алгоритм в куцый набор инструкций? * Можно! Причем используя вполне формальное преобразование. * Вот этим мы сейчас и займемся. =====Немного теории===== Историческая справка для любознательных: //По этому поводу тоже было немало дебатов: сколько же структур действительно основных, а какие следует считать производными. Левые радикалы даже дошли до того, что основных структур только две: SEQUENCE и WHILE, а все остальные можно построить из них. Самое смешное, что это действительно так. Правда, размер текста программы при этом распухает неимоверно, но это уже детали...// Итак, структурное программирование учит нас, что есть 5 основных конструкций, из которых как из кубиков строится любая процедура: ^ SEQUENCE ^ IF-THEN-ELSE ^ WHILE-DO ^ REPEAT-UNTIL ^ CASE ^ | {{http://pascal.sources.ru/articles/formal_seq.gif}} | {{http://pascal.sources.ru/articles/formal_if.gif}} | {{http://pascal.sources.ru/articles/formal_while.gif}} | {{http://pascal.sources.ru/articles/formal_repeat.gif}} | {{http://pascal.sources.ru/articles/formal_case.gif}} | В нашем запутанном алгоритме наверняка не все так ужасно, как кажется. Скорее всего, там можно найти несколько фрагментов, подходящих под определение чисто структурных конструкций. Вопрос лишь в том, как эти конструкции соединить между собой. А вот в этом как раз может помочь наша рабочая лошадка - непотопляемая конструкция REPEAT-CASE. При умелом применении эта нехитрая пара команд может "переварить" алгоритм любой сложности и запутанности. Главное, чтобы ВЫ четко представляли что делаете. ======Практика====== =====Типичный алгоритм===== Предположим, у нас есть алгоритм следующего вида: {{http://pascal.sources.ru/articles/formal_block1.gif}} Хитрый ли он?\\ Да нет, конечно! Если приглядеться, он легко разбивается на 3 вложенные стандартные структуры: {{http://pascal.sources.ru/articles/formal_block1a.gif}} Так что мы с легкой душой можем воплотить его в программе вроде такой: repeat while C1 do B1; if C2 then B2 else B3; until C3; Как было бы хорошо, если бы в жизни нам попадались только такие алгоритмы. Однако в таком случае, вам незачем было бы читать эту статью! =====Алгоритм требующий применения теории конечных автоматов===== {{http://pascal.sources.ru/articles/formal_block2.gif }}Выглядит вроде просто, это мы мигом!\\ Гмм.. да.. пробуем и так и эдак - в стандартный Паскаль это явно не укладывается. Можно, конечно, попытаться "расшить" процедурные блоки B1 и B3 или применить GOTO или EXIT из цикла. Но все это, согласитесь, выглядит как-то жалко и самодеятельно. Опять же надо каждый раз думать где разомкнуть цикл... И вот тут-то появляемся мы, (на белом коне ) с нашей универсальной отмычкой по имени REPEAT-CASE. ===Теперь мы можем выполнить несколько чисто формальных шагов:=== *Выделяем в нашем алгоритме фрагменты, которые хорошо укладываются в структурную модель (если такие есть). В нашем случае такой фрагмент только один: B2 + C2, т.е. последовательность из блока и условия. *Вне этих фрагментов ставим жирные точки в следующих местах: *на входе в модуль (обозначим ее 1) *на выходе модуля (обозначим 0) *на входах и выходах всех фрагментов, что мы нашли *во всех местах, где есть пересечение линий на блок-схеме Скорее всего, многие точки просто сольются - пусть, мы будем считать их за одну. Например, у нас точка 1 на входе модуля совпадает с точкой пересечения линий входящей и от B3. {{ http://pascal.sources.ru/articles/formal_block2a.gif}} *Пронумеруем оставшиеся точки произвольно. Позже мы еще поговорим о том, что могут на самом деле означать эти номера. В нашем примере получается 4 точки от 0 до 3. *Теперь мы готовы перейти к модели конечного автомата и написать-таки нашу программу. *Представьте, что есть некий блок, который может находиться в одном из 4 состояний. И есть набор действий, в результате которых блок переходит из одного состояния в другое. *Для отображения этого самого состояния, заведем в программе некоторую переменную, скажем, State. А внутри веток CASE будем изменять ее состояние. *Пишем нашу программу: var State:integer; begin State:=1; {для любого алгоритма} repeat case State of ... end; until State=0; {тоже для любого алгоритма} end; *Теперь пропишем ветки CASE. Не забудьте в конце каждой ветки уточнить состояние: case State of 1: begin B1; if C1 then State:=2 else State:=3 end; 2: begin B2; if C2 then State:=0 else State:=3 end; 3: begin B3; State:=1 end; end; *Все! Программа готова. Идите и попробуйте, она работает. И с точки зрения логики Паскаля все безупречно - никаких тебе GOTO и прочих неприятностей. ===Осознание=== А теперь, после того, как мы добились столь блестящего результата, давайте осознаем: что же мы сделали и что у нас получилось. ==Что сделали (или как все это назвать по-настоящему)== *Мы изобразили наш алгоритм как блок-схему или, другими словами, направленный граф *Затем провели инвариантное преобразование этого графа с выделением нескольких стационарных состояний программы - конечного автомата *В результате получили новый граф, который легко укладывается в структурные конструкции Паскаля (Delphi) ===Что из это следует=== Проводя указанные действия несколько раз для разных алгоритмов, можно заметить, что на самом деле наши произвольно расставленные точки-состояния не такие уж случайные и произвольные. Как правило, при более глубоком рассмотрении вашего конкретного алгоритма можно найти каждому из этих состояний свое название. Это название может быть гораздо более выразительным, чем просто 1-2-3, поскольку это действительно состояния вашей программы. >О чем я говорю? Пусть ваш алгоритм занимается, скажем, синтаксическим разбором HTML-файла. Тогда одно из состояний может звучать как "Обнаружен тэг BODY" или "Найден конец документа". >Паскаль предлагает нам замечательное средство для работы с такими обозначениями в символическом виде и об этом средстве сейчас часто забывают. Программа из нашего примера может выглядеть так: var State:(START, EOF_found, Line_Added, DONE); begin State:=START; {для любого алгоритма} repeat case State of START: begin B1; if C1 then State:=EOF_Found else State:=Line_Added end; EOF_Found: begin B2; if C2 then State:=DONE else State:=Line_Added end; Line_Added: begin B3; State:=START end; end; until State=DONE; {тоже для любого алгоритма} end; Замечательно, что Delphi все еще поддерживает эту спецификацию и даже показывает при отладке символьное представление состояния! Это очень удобно на практике. Спасибо, Borland! ===Другое следствие=== Возможно вы, как и я, проделав подряд несколько таких преобразований и войдя во вкус, заметите, что стали мыслить при программировании чуть-чуть иначе. Иногда, особенно когда задача несколько запутана, хочется сразу выделить несколько важных состояний и строить обработчик уже вокруг них. Это правильное желание, ему стоит потакать. >Кстати, сейчас тема конечных автоматов вновь стала актуальной и то и дело мелькает на страницах компьютерных журналов. =====Источники===== *http://pascal.sources.ru/articles/formal.htm#FORMAL *www.citforum.ru/programming/digest/formal.shtml *http://ru.wikipedia.org/wiki/Конечные_автоматы ===Дополнительная теория=== *http://ru.wikipedia.org/wiki/Теория_графов *http://algolist.manual.ru/ =====Древо целей=====