Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Логическое программирование1 / 1-5_LR_4KSM_Logichne_progr_2014-15

.pdf
Скачиваний:
16
Добавлен:
07.02.2016
Размер:
498.87 Кб
Скачать

111

Спис1 = [X | Остальные], Спис2 = [Y | Остальные],

( Х < Y,

!,

Z = X,

% Z - голова Спис3

слить(Остальные1, Спис2, Остальные3); Z = Y,

слить(Спис1, Остальные2, Остальные3)), Спис3 = [Z | Остальные3].

Вот более предпочтительный вариант, не использующий точек с запятой:

слить( [ ], Спис, Спис). слить( Спис, [ ], Спис).

слить([X | Остальные1], [Y | Остальные2], [X | Остальные3]) :- Х < Y, !,

слить(Остальные1, [Y | Остальные2], Остальные3). слить( Спис1, [Y | Остальные2], [Y | Остальные3]): -

слить( Спис1, Остальные2, Остальные3 ).

Г.3.2. Табличная организация длинных процедур

Длинные процедуры допустимы, если они имеют регулярную структуру. Обычно эта структура представляет собой множество фактов,

соответствующее определению какого-либо отношения в табличной форме. Преимущества такой организации длинной процедуры состоят в том, что:

-ее структуру легко понять;

-ее удобно совершенствовать (улучшать ее можно, просто добавляя новые факты);

-ее легко проверять и модифицировать (просто заменяя отдельные факты, независимо от остальных).

112

Г.3.3. Комментирование

Программные комментарии должны объяснять в первую очередь, для чего программа предназначена и как ею пользоваться, и только затем - подробности используемого метода решения и другие программные детали.

Главная цель комментариев - обеспечить пользователю возможность применять программу, понимать ее и, может быть, модифицировать.

Комментарии должны содержать в наиболее краткой форме всю необходимую для этого информацию.

Недостаточное комментирование - распространенная ошибка, однако, программу можно и перенасытить комментариями (объяснения деталей, которые ясны из самого текста программы, являются ненужной перегрузкой).

Длинные фрагменты комментариев следует располагать перед текстом, к которому они относятся, а короткие комментарии - вкраплять в текст.

Информация, которую в самом общем случае следует включать в комментарии, должна охватывать следующие вопросы:

-что программа делает, как ею пользоваться (какую цель следует активизировать, вид ожидаемых результатов и т.п.), примеры ее применения;

-какие предикаты относятся к верхнему уровню;

-как представлены основные понятия (объекты);

-время выполнения и требования по объему памяти;

-ограничения на программу;

-использует ли программа средства, связанные с конкретной операционной системой;

-каков смысл предикатов программы, каковы их аргументы, какие аргументы являются "входными" и какие "выходными", если это известно (в момент запуска предиката, входные аргументы имеют полностью определенные значения, не содержащие неконкретизированных переменных);

-алгоритмические и реализационные детали.

113

Г.4. Отладка

Когда программа не делает того, чего от нее ждут, главной проблемой становится отыскание ошибки (или ошибок).

Всегда легче найти ошибку в какой-нибудь части программы (или в отдельном модуле), чем во всей программе.

Следует придерживаться следующего хорошего принципа: проверять сначала более мелкие программные единицы и только после того, как вы убедились, что им можно доверять, начинать проверку большего модуля или всей программы.

Отладка в Прологе облегчается двумя обстоятельствами:

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

2)в реализациях Пролога обычно имеются специальные средства отладки. Следствием является то, что отладка программ на Прологе может произво-

диться значительно эффективнее, чем в других языках программирования. Основным средством отладки является трассировка (tracing). Трассировать цель означает предоставить пользователю информацию,

относящуюся к достижению цели в процессе ее обработки Пролог-системой. Эта информация включает:

-входную информацию (имя предиката и значения аргументов в момент активизации цели);

-выходную информацию (в случае успеха, значения аргументов, удовлетворяющих цели; в противном случае - сообщение о неуспехе);

- информацию о повторном входе, т. е. об активизации той же цели

врезультате автоматического возврата.

Впромежутке между входом и выходом, можно получить трассировочную информацию для всех подцелей цели.

114

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

Такая детальная трассировка может оказаться непрактичной из-за непомерно большого количества трассировочной информации.

Поэтому пользователь может применить "селективную" трассировку. Существуют два механизма селекции:

1)подавляет выдачу информации о целях, расположенных ниже некоторого уровня;

2)трассирует не все предикаты, а только некоторые, указанные пользователем.

Средства отладки приводятся в действие при помощи системно-зависимых встроенных предикатов.

Обычно используется следующий стандартный набор предикатов:

- trace (запускает полную трассировку всех целей, следующих за trace); - notrace (прекращает дальнейшее трассирование);

- spy(P) («следи за Р», устанавливает режим трассировки предиката Р); - nospy(Р) (прекращает "слежку" за предикатом Р).

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

"Следить" можно сразу за несколькими предикатами.

Трассировка ниже определенной глубины может быть подавлена во время выполнения программы при помощи специальных команд.

Существуют и другие команды отладки, такие как возврат к предыдущей точке процесса вычислений.

После такого возврата можно, например, повторить вычисления с большей степенью детализации трассировки.

115

Г.5. Эффективность

Существует несколько аспектов эффективности программ, включая такие наиболее общие, как: время выполнения и требования по объему памяти; время, необходимое программисту для разработки программы.

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

Вызовет ли это трудности в практических приложениях, зависит от задачи. Фактор времени практически не имеет значения, если Пролог-программа, которую запускают по несколько раз в день, занимает 1 секунду процессорного

времени, а соответствующая программа на другом языке - 0.1 секунды. Разница в эффективности становится существенной, если эти две

программы требуют 50 и 5 минут соответственно.

С другой стороны, во многих областях применения Пролога он может существенно сократить время разработки программ.

Программы на Прологе, вообще говоря, легче писать, понимать и отлаживать, чем программы, написанные на традиционных языках.

Задачи, тяготеющие к "сфере Пролога", включают в себя обработку символьной, нечисловой информации, структурированных объектов данных и отношений между ними.

Пролог успешно применяется, в частности. в таких областях, как символьное решение уравнений, планирование, базы данных, автоматическое решение задач, машинное макетирование, реализация языков программирования, дискретное и аналоговое моделирование, архитектурное проектирование, машинное обучение, понимание естественного языка, экспертные системы и другие задачи искусственного интеллекта.

116

С другой стороны, применение Пролога в области вычислительной математики вряд ли можно считать естественным.

Прогон откомпилированной программы обычно имеет большую эффективность, чем интерпретация.

Если Пролог-система содержит как интерпретатор, так и компилятор, следует пользоваться компилятором, если время выполнения критично.

Если программа страдает неэффективностью, то ее обычно можно кардинально улучшить, изменив сам алгоритм.

Однако для того, чтобы это сделать, необходимо изучить процедурные аспекты программы.

Простой способ сокращения времени выполнения состоит в нахождении более удачного порядка предложений в процедуре и целей - в телах процедур.

Другой, относительно простой метод заключается в управлении действиями системы посредством отсечений.

Полезные идеи, относящиеся к повышению эффективности, обычно возникают только при достижении более глубокого понимания задачи.

Более эффективный алгоритм может привести к улучшениям двух видов:

-повышение эффективности поиска путем скорейшего отказа от ненужного перебора и вычисления бесполезных вариантов;

-применение cтруктур данных, более приспособленных для представления объектов программы, с целью реализовать операции над ними более эффективно.

Изучим оба вида улучшений на примерах.

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

Вместо того, чтобы вычислять их снова, программа отыщет их в базе данных как уже известные факты.

117

Г.5.1. Повышение эффективности решения задачи о восьми ферзях

В качестве простого примера повышения эффективности, вернемся

кзадаче о восьми ферзях.

Впрограмме Y-координаты ферзей перебираются последовательно - для каждого ферзя пробуются числа от 1 до 8.

Этот процесс был запрограммирован в виде цели

принадлежит( Y, [1, 2, 3, 4, 5, 6, 7, 8] ).

Процедура принадлежит работает так: пробует Y = 1, Y = 2, Y = 3 и т.д. Поскольку ферзи расположены один за другим в смежных вертикалях

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

Всоответствии с этим наблюдением, можно попытаться повысить эффективность, изменив порядок рассмотрения координат-кандидатов.

Например: принадлежит(Y, [1, 5, 2, 6, 3, 7, 4, 8]).

Это маленькое изменение уменьшит время, необходимое для нахождения первого решения, в 3-4 раза.

Вследующем примере такая же простая идея, связанная с изменением порядка, превращает неприемлемую временную сложность в тривиальную.

Г. 5. 2. Повышение эффективности программы раскраски карты

Задача раскраски карты состоит в приписывании каждой стране на заданной карте одного из четырех заданных цветов с таким расчетом, чтобы ни одна пара соседних стран не была окрашена в одинаковый цвет.

Существует теорема, которая гарантирует, что это всегда возможно.

Пусть карта задана отношением соседства соседи(Страна, Соседи), где Соседи - список стран, граничащих со страной Страна.

118

При помощи этого отношения, карта Европы с 20-ю странами будет представлена (в алфавитном порядке) так:

соседи( австрия, [венгрия, запгермания, италия, лихтенштейн, чехословакия, швейцария, югославия]),

соседи( албания, [греция, югославия]). соседи( андорра, [испания, франция]).

. . .

Решение представим в виде списка пар вида Страна / Цвет, которые устанавливают цвет для каждой страны на данной карте.

Для каждой карты названия стран всегда известны заранее, так что задача состоит в нахождении цветов.

Таким образом, для Европы задача сводится к отысканию подходящей конкретизации переменных C1, C2, СЗ и т.д. в списке

[австрия/C1, албания/С2, андорра/С3, . . .]

Теперь определим предикат цвета( СписЦветСтран), который истинен, если СписЦветСтран удовлетворяет ограничениям, наложенным на раскраску отношением соседи.

Пусть четырьмя цветами будут желтый, синий, красный и зеленый. Условие запрета раскраски соседних стран в одинаковый цвет можно

сформулировать на Прологе так:

цвета( [ ]).

цвета( [Страна/Цвет | Остальные] ) :- цвета( Остальные), принадлежит( Цвет, [желтый, синий, красный, зеленый]), not( принадлежит( Страна1/Цвет, Остальные),

сосед( Страна, Страна1) ).

сосед( Страна, Страна1) :- соседи( Страна, Соседи), принадлежит( Страна1, Соседи).

Здесь принадлежит( X, L) - отношение принадлежности к списку.

119

Для простых карт с небольшим числом стран, такая программа будет работать. Что же касается Европы, то результат проблематичен.

Если считать, что мы располагаем встроенным предикатом setof, то можно попытаться раскрасить карту Европы следующим образом.

Определим вспомогательное отношение страна(С) :- соседи(С, _ ). Тогда вопрос для раскраски карты Европы можно сформулировать так:

?- sеtоf(Стр/Цвет, страна(Стр), СписЦветСтран), цвета(СписЦветСтран).

Цель setof - построить "шаблон" списка СписЦветСтран, где в элементах вида страна/ цвет вместо цветов будут неконкретизированные переменные.

Предполагается, что после этого цель цвета конкретизирует их.

Такая попытка скорее всего потерпит неудачу вследствие неэффективности работы программы.

Тщательное исследование способа, при помощи которого Пролог-система пытается достичь цели цвета, обнаруживает источник неэффективности.

Страны расположены в списке в алфавитном порядке, а он не имеет никакого отношения к их географическим связям.

Порядок, в котором странам приписываются цвета, соответствует их расположению в списке (с конца к началу), что не связано с отношением соседи.

Поэтому процесс раскраски начинается в одном конце карты, продолжается в другой и т.д., перемещаясь по ней более или менее случайно.

Это легко может привести к ситуации, когда при попытке раскрасить очередную страну окажется, что она окружена странами, уже раскрашенными во все четыре доступных цвета.

Подобные ситуации приводят к возвратам, снижающим эффективность. Ясно поэтому, что эффективность зависит от порядка раскраски стран. Интуиция подсказывает простую стратегию раскраски, которая должна

быть лучше, чем случайная: начать со страны, имеющей иного соседей, затем перейти к ее соседям, затем - к соседям соседей и т.д.

120

В случае Европы, хорошим кандидатом для начальной страны является Западная Германия (как имеющая наибольшее количество соседей - 9).

Понятно, что при построении шаблона списка элементов вида страна/цвет, Западную Германию следует поместить в конец этого списка, а остальные страны - добавлять со стороны его начала.

Алгоритм раскраски, который начинает работу с конца списка, в начале займется Западной Германией и продолжит работу, переходя от соседа к соседу.

Новый способ упорядочивания списка стран резко повышает эффективность по отношению к исходному, алфавитному порядку, и теперь возможные раскраски карты Европы будут получены без труда.

Можно было бы построить правильно упорядоченный список стран вручную, но в этом нет необходимости. Эту работу выполнит процедура создспис, которая начинает построение с указанной страны (Западной Германии) и собирает остальные страны в список под названием Закрытый.

Каждая страна сначала попадает в другой список, названный Открытый, а потом переносится в Закрытый. Всякий раз, когда страна переносится из

Открытый в Закрытый, ее соседи добавляются в Открытый.

создспис(Спис) :- собрать([запгермания], [ ], Спис). собрать([ ], Закрытый, Закрытый).

% Кандидатов в Закрытый больше нет

собрать( [X | Открытый], Закрытый, Спис) :- принадлежит( Х | Закрытый), !, % Х уже собран ? собрaть( Открытый, Закрытый, Спис). % Отказаться от Х

собрать( [X | Открытый], Закрытый, Спис) :- соседи(X, Соседи), % Найти соседей Х

конк(Соседи, Открытый, Открытый1), % Поместить их в Открытый собрать( Открытый1, [X | Закрытый], Спис). % Собрать остальные

Отношение конк - отношение конкатенации списков.