Пример разработки системы Пример разработки системы для построения и обхода дерева решений На рисунке показан фрагмент дерева решений, позволяющий выбрать нужную персональную ЭВМ среди компьютеров, выпущенных в 1986 г. Пример разработки системы для построения и обхода дерева решений Создать описания дерева решений позволяют факты вида верш(Верш_ид, Вопрос,Дуги) Верш_ид - идентифицирует вершины, Вопрос - это вопрос или решение, Дуги - список дуг, выходящих из вершины. Вопрос определяется списком строк, каждая из которых имеет тип "строка". Дуга представляется термином (Отв, Верш_ид), означающим, что при ответе Отв следует перейти к вершине с идентификатором Верш_ид. Вершина "Портативная?" записывается следующим образом: верш(1,[Портативная?],[дуга(да,2),дуга(нет,3)]) Т.о. мы присвоили идентификаторы 1,2 и 3 вершинам "Портативная?", "Карманная?" и "IBM-совместимая" соответственно. Систему будем разрабатывать в 2 этапа. Сначала создадим удобный интерфейс для пользователя, а затем напишем целевые утверждения для построения и обхода дерева решений. Создание интерфейса пользователя Меню возможных действий пользователя выглядит следущим образом: меню:Каждое действие будет осуществляться repeat,nl, соответствующим утверждением выполнить. write ('Хотите ли вы'),nl, Утверждение выполнить должно write ('1)загрузить дерево'),nl, закончиться возникновением состояния неудачи write ('2)сохранить дерево'),nl, для всех вариантов от 1 до 7, чтобы после write ('3)добавить вершину'),nl, совершения требуемого действия на экране write ('4)убрать вершину'),nl, снова показалось меню. write ('5)добавить дугу'),nl, write ('6)убрать дугу'),nl, write ('7)совершить обход дерева'),nl, write ('8)стоп'),nl, write ('Введите 1-9'),nl, write (' '),nl, readb (Вариант),nl, выполнить (Вариант). Создание интерфейса пользователя (дерево) Дерево загружается с помощью предиката reconsult: выполнить (1):write('имя файла?'), write(Ф),nl, write('чтение файла...'), reconsult(Ф), write('...закончилось'),nl,!,fail. Для того чтобы сохранить дерево, прежде всего надо поместить специальный маркер в конец списка вершин. Если маркер становится аргументом предиката сохр_верш, то данный предикат убирает маркер и попытки доказательства заканчиваются успехом. В противном случае сохр_верш записывает вершину в файле и доказательство завершается неудачей. Начинает работать механизм возврата, выбирается следующая вершина, которая сохраняется в том случае, если не является маркером. выполнить(2):write('имя файла?'), readb(Ф), open(Ф,блок,write),!,nl, write('запись в файл...'), write(Ф), assrtz(верш(-1,-1,-1,)), верш(Вид,В,Дуги), сохр_верш(Вид,В,Дуги,блок), write('...закончилась'),nl, close(блок),!,fail. Создание интерфейса пользователя (вершины) Добавить вершину можно при условии, что ее дуги не ведут к ней самой. Другими словами, добавление вершины не должно приводить к возникновению циклов. Целевое утверждение цикл(Вид,Дуги) доказывается успешно, если одна из Дуг ведет к Вид, и заканчивается неудачей в противном случае. При успешном доказательстве цели цикл определяется и пользователю показывается циклический путь. Целевое утверждение чит_нов_ид вводит новый идентификатор вершины, чит_вопрос вводит вопрос, а чит_дуги вводит список дуг. выполнить(3):чит_нов_ид(Вид), write('Введите вопрос(в конце напишите слово 'конец'):'),nl, чит_вопрос(В), чит_дуги(Дуги), not(цикл(Вид,Дуги)), assertz(верш(Вид,В,Дуги)),!,fail. Вершина уничтожается следующим образом: выполнить(4):чит_стар_ид(Вид), уничт_верш(Вид),!,fail. Чтобы сохранить вершину, мы записываем ее в память при условии, что она не является специальным маркером: сохр_верш(-1,-1,-1,_):-!, retract(верш(-1,-1,-1)). сохр_верш(Вид,В,Дуги,Б):writeq(Б,верш(Вид,В,Дуги)), write(Б,'.'), nl(Б),!,fail. Создание интерфейса пользователя (дуги) Дуга, выходящая из вершины, добавляется в том случае, если при этом не образуется цикл: выполнить(5):write('Из вершины'), чит_стар_ид(Вид), чит_дугу(Дуга), not(цикл(Вид,[Дуга])), доб_дугу(Вид,Дуга),!,fail. Выход из меню: выполнить(8):-nl,write('Досвидания'),nl,!. Уничтожение дуги: выполнить(6):чит_стар_ид(Вид), чит_дугу(Дуга), уничт_дугу(Вид,Дуга),!,fail. Обход дерева: выполнить(7):чит_стар_ид(Вид), обход(Вид),!,fail. уничт_дугу(Вид,Дуга),!,fail. Создание интерфейса пользователя (вопросы) Чтобы прочитать заданный вопрос, надо читать все строки на экране до тех пор, пока не встретится строка 'конец'. Для чтения строк используется встроенный предикат readline. чит_вопрос(В):write('>'), readline(Строка), продолжать(Строка,В). продолжать('конец',[]):-!. продолжать(Строка,[Строка|В]):чит_вопрос(В). Вопрос выводится на дисплей построчно: запис_вопр([]):-!. запис_вопр([H|T]):nl,write(H), запис_вопр(T). Описание целевых утверждений для построения и обхода дерева решений При уничтожении вершины мы уничтожаем также все входящие в нее дуги: уничт_верш(Вид):retract(верш(Вид,В,Дуги)), уничт_все_вх_дуги(Вид), nl,write('вершина уничтожена'),nl. Чтобы получить доступ ко всем вершинам, помещаем маркер в конец списка вершин и используем механизм возврата: уничт_все_вх_дуги(Вид):assertz(верш(-1,-1,-1)), retract(верш(Вид2,В,Дуги)), уничт2(Вид2,В,Дуги,Вид),!. уничт2(-1,-1,-1,_). уничт2(Вид2,В,Дуги,Вид):уничт_список(Дуги,дуга(Д,Вид),Дуги2), assertz(верш(Вид2,В,Дуги2)),!, fail. Описание целевых утверждений для построения и обхода дерева решений Целевое утверждение уничт_список(Дуги,дуга(Д,Вид),Дуги2) уничтожает дугу дуга(Д,Вид) в списке Дуги, в результате чего получается список Дуги2. Предикат уничт_список импортируется из модуля список. Добавляя дугу, следует проверить, не содержится ли она уже в списке дуг: доб_дугу(Вид,Д):верш(Вид,В,Дуги), принадлежит(Д,Дуги),!, nl,write('уже существует!!'),nl. доб_дугу(Вид,дуга(Д,Вид2)):верш(Вид2,_,_), retract(верш(Вид,В,Дуги)), assertz(верш(Вид,В,[дуга(Д,Вид2)|Дуги])), nl,write('дуга добавлена'),nl. Утверждение уничт_дугу позволяет уничтожить дугу Д, выходящую из вершины с идентификатором Вид: уничт_дугу(Вид,Д):retract(верш(Вид,В,Дуги)), assertz(верш(Вид,В,Дуги2)), nl,write('дуга уничтожена'),nl. Описание целевых утверждений для построения и обхода дерева решений Будем говорить, что вершина Вид1 ведет к вершине Вид2, если имеет место одна из трех ситуаций: Вид2 есть Вид1, Вид2 находится в конце дуги, выходящей из Вид1, или же вершина, находящаяся в конце дуги, выходящей из Вид1, ведет к Вид2. Доказательство целевого утверждения ведет заканчивается успехом, если существует путь, ведущий от дуги (первого аргумента утверждения) к вершине (второму аргументу). В противном случае доказательство завершается неудачей. При успешном доказательстве утверждения ведет в качестве третьего аргумента возвращается искомый путь. ведет([дуга(Д,Вид)|Т],Вид2,П):вести(Вид,Вид2,П),!. ведет([_|Т],Вид2,П):-ведет(Т,Вид2,П). вести(Вид,Вид,[Вид,Вид]):вести(Вид1,Вид2,[Вид1,Вид2]):верш(Вид1,В,Дуги), принадлежит(дуга(Д,Вид2),Дуги),!. вести(Вид1,Вид2,[Вид1|П]):верш(Вид1,В,Дуги), ведет(Дуги,Вид2,П). Для того чтобы пройти по дереву, начиная с вершины СВид, нужно вывести на экран вопрос, содержащийся в вершине. Если вершина не имеет исходящих дуг, работа заканчивается. В противном случае система читает ответ пользователя и использует предикат принадлежит для выборки следующей вершины: обход(СВид):верш(СВид,В,Дуги), обраб_дуги(Дуги,В). обраб_дуги([],В):-!, писать_вопрос(В), обраб_дуги(Дуги,В):ввести_ответ(В,Дуги,Отв), принадлежит(дуга(Отв,Вид),Дуги), обход(Вид). Описание целевых утверждений для построения и обхода дерева решений Ответ пользователя на вопрос, содержащийся в вершине, вводится с помощью следующего утверждения: ввести_ответ(В,Дуги,Отв):repeat, писать_вопрос(В), readb(Отв), принадлежит(дуга(Отв,_),Дуги),!. Предикат ввести_ответ гарантирует совпадение ответа пользователя хотя бы с одним ответом, расположенным на дуге. Последнее, что нам осталось сделать - это осуществить импорт предиката уничт_список из модуля список: :-import(список,[уничт_список]). Помимо предиката уничт_список, модуль список содержит и другие целевые утверждения для обработки списков. :-module(список). :-visa([visa,secure],[присоединить,...,уничт_список,...]) ... уничт_список([],Е,[]):-!. уничт_список([Е|Т],Е,Т2):-!, уничт_список(Т,Е,Т2). уничт_список([Н|Т],Е,[Н|Т2]):уничт_список(Т,Е,Т2). ... :-endmodule(список). Пример диалога пользователя с разработанной системой OK, дерево Пролога ?-меню. Хотите ли вы: 1)загрузить дерево 2)сохранить дерево 3)добавить вершину 4)убрать вершину 5)добавить дугу 6)убрать дугу 7)совершить обход дерева 8)выйти из системы Введите 1-9? 3 Идентификатор новой вершины? IBM_портативная. Введите вопрос(в конце напишите слово 'конец'): >>Вы должны купить портативную ЭВМ фирмы IBM >>Она стоит 1632 фунта стерлингов, >>использует процессор 8088, имеет 256К >>оперативной памяти >> >>конец Сколько дуг имеет вершина? 0 Хотите ли вы: [n.меню Введите 1-9? 3 Идентификатор новой вершины? sharp_pc7000 Введите вопрос (Вконце напишите слово 'конец'): >>Вы должны купить портативную ЭВМ фирмы sharp_pc7000 >>Она стоит 2577 фунта стерлингов, >>использует процессор 8088, имеет 320К >>оперативной памяти >> >>конец Сколько дуг имеет вершина? 0 Хотите ли вы: [меню Введите 1-9? 3 Идентификатор новой вершины? цена Введите вопрос (Вконце напишите слово 'конец'): >>Сколько денег вы можете затратить на >>покупку: >>а.2000 фунтов стерлингов >>б.3000 фунтов стерлингов >>? >>конец Пример диалога пользователя с разработанной системой Сколько дуг имеет вершина? 2 Дуга1 Ответ? а К вершине IBM_портативная Дуга2 Ответ? б К вершине? sharp_pc7000 Хотите ли вы: [меню Введите 1-9? 3 Идентификатор новой вершины? карманная Введите вопрос (В конце напишите слово 'конец'): >>Должна ли ЭВМ помещаться в вашем кармане? >>конец Сколько дуг имеет вершина? 1 Дуга1 Ответ? нет К вершине? цена И т.д.