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

Лабы_ЭкспСист / Лабораторная №5

.doc
Скачиваний:
13
Добавлен:
09.02.2015
Размер:
69.12 Кб
Скачать

Лабораторная работа №5.

Консольные приложения.

Цель работы: Научиться создавать консольные приложения Visual Prolog. Освоить отсечения предикатов и основы работы со списками.

Задание: Написать два консольных приложения, для вычисления факториала и для вычисления длины списка.

Порядок выполнения:

1. Ознакомится с понятием отсечения.

2. Реализовать проект по вычислению факториала.

3. Получить основные сведения о списках.

4. Реализовать программу вычисления длины списка.

6.1. Отсечение

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

Проиллюстрируем использование отсечений на предикате, который находит факториал числа. Математики определяют факториал как:

factorial(0)→1

factorial(n)→n×factorial(n-1)

Используя хорновские предложения, это определение становится следующим:

fact(N, 1) :- N<1, !.

fact(N, N*F1) :- fact(N-1, F1).

Отсечение предотвращает попытку Пролога применить второе предложение для N=0. Другими словами, если вы поставите запрос

fact(0, F)?

программа успешно использует первое предложение для N=0, и ответит, что F=1. Без отсечения она могла бы подумать, что второе предложение определения

fact(N, 1) :- N<1, !.

fact(N, N*F1) :- fact(N-1, F1).

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

1.Создание нового проекта. Выберите пункт Project/New и заполните диалоговое окно Project Settings так:

General

Project Name: facfun

UI Strategy: console

Обратите внимание, что мы собираемся использовать консольную стратегию, не GUI.

2. Сборка. Выберите пункт Build/Build из панели задач, чтобы внести прототип класса facfun в дерево проекта. Отредактируйте facfun.pro, как показано ниже.

implement facfun

open core, console

class predicates

fact : (integer, integer) procedure (i,o).

clauses

classInfo(“facfun”, “1.0”).

fact(N, 1) :- N<1, !.

fact(N, N*F1) :- fact(N-1, F1).

run() :- console::init(), fact(read(), F), write(F), nl.

end implement facfun

goal mainExe::run(facfun::run).

Снова откомпилируйте программу, и запустите её, используя команду Run in Window (не Execute). Напишите число в приглашении к вводу, и вы получите его факториал.

5.2. Списки

Список это упорядоченная последовательность элементов, где упорядоченная означает, что порядок имеет значение. В Прологе список помещается между квадратными скобками, и подразумевается имеющим голову (head) и хвост (tail):

Список

Тип

Голова

Хвост

[3, 4, 5, 6, 7]

Integer*

3

[4, 5, 6, 7]

[“wo3”, “ni3”, “ta1”]

String*

“wo3”

[“ni3”, “ta1”]

[4]

Integer*

4

[]

[3.4, 5.6, 2.3]

Real*

3.4

[5.6, 2.3]

Вы можете соотнести шаблон переменных со списком. Например, если вы соотнесете шаблон

[X|Xs]

со списком [3.4, 5.6, 2.3], вы получите X=3.4 и Xs=[5.6, 2.3], то есть, X соответствует голове списка и Xs соответствует хвосту. Конечно же, вы можете использовать другие две переменные вместо X и Xs в шаблоне [X|Xs]. Так, [A|B], [Head|Tail], [First|Rest] – эквивалентные шаблоны.

Шаблон [X|Xs] соответствует только списку с как минимум одним элементом. Вот шаблон, который соответствует только спискам с как минимум двумя элементами:ок X

Шаблон

Список

Х1, Х2

Хs

[X1, X2|Xs]

[3, 5, 2, 7]

X1=3, X2=5

[2, 7]

[X1, X2|Xs]

[2, 7]

X1=2, X2=7

[]

[X1, X2|Xs]

[7]

Не соответствуют

[X1, X2|Xs]

[]

Не соответствуют

Пусть avg будет маленьким проектом, вычисляющим длину списка действительных чисел. Как и в случае facfun, убедитесь, что avg – консольное приложение.

General

Project Name: avg

UI Strategy: console

Откомпилируйте проект, чтобы вставить класс avg в дерево проекта. Затем, отредактируйте avg.pro, как показано ниже. Запустите, используя Run in Window, как и ранее.

%File: avg.pro

implement avg

open core, console

domains

rList= real*.

class predicates

len:(rList, real) procedure (i, o).

clauses

classInfo("avg", "1.0").

len([], 0) :- !.

len([_X|Xs], 1.0+S) :- len(Xs, S).

run():-

console::init(),

List= read(),

len(List, A),

write(A), nl.

end implement avg

goal

mainExe::run(avg::run).

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

domains

rList=real*.

Затем, вы определил предикат len/2, который получает список через первый аргумент, и выводит его длину через второй аргумент. Наконец, вы определили предикат run(), чтобы протестировать программу:

run() :-

console::init(), %Инициализировали консоль

List=read(), %Прочитали List с консоли

len(List, A), %Нашли длину списка

write(A), nl. %Вывели длину на экран

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

Ниже вы найдёте ту же программу без объявления домена.

%File: avg.pro

implement avg

open core, console

class predicates

len:(real*, real) procedure (i, o).

clauses

classInfo("avg", "1.0").

len([], 0) :- !.

len([_X|Xs], 1.0+S) :- len(Xs, S).

run():- console::init(),

L= read(),

len(L, A),

write(A), nl.

end implement avg

goal

mainExe::run(avg::run).

Эта программа имеет недостаток: len/2 можно использовать только для подсчёта длины списка действительных чисел. Было бы неплохо, если бы len/2 мог принимать любой тип списка. Это должно быть возможным, если подставлять переменную типа для real*. Однако, до того, как проверять, действительно ли эта схема работает, необходимо придумать способ извещать L=read() что будет конечным типом вводимого списка, и передать эту информацию в len/2. Visual Prolog имеет предикат, который создаёт переменную любого заданного типа. Ниже вы узнаете, как его использовать.

%File: avg.pro

implement avg

open core, console

class predicates

len:(Element*, real) procedure (i, o).

clauses

classInfo("avg", "1.0").

len([], 0) :- !.

len([_X|Xs], 1.0+S) :- len(Xs, S).

run():- console::init(),

hasdomain(real_list, L), L= read(),

len(L, A), write(A), nl.

end implement avg

goal mainExe::run(avg::run).

Следующий шаг – добавить предикаты sum/2 к avg.pro:

class predicates

sum:(rList, real) procedure (i, o).

clauses

sum([], 0) :- !.

sum([X|Xs], S+X) :- sum(Xs, S).

Давайте посмотрим, что произойдёт, если вызвать sum([3.4, 5.6, 2.3], S).

1. sum([3.4, 5.6, 2.3], S[5.6, 2.3]+X), где S[5.6, 2.3]+X=S[3.4, 5.6, 2.3],

соответствует предложению sum([X|Xs], S+X) :- sum(Xs, S), с X=3.4, Xs=[5.6, 2.3], дает sum([3.4, 5.6, 2.3], S[5.6, 2.3]+3.4) :- sum([5.6, 2.3], S[5.6, 2.3])

2. sum([5.6, 2.3], S[2.3]+X), где S[2.3]+X=S[5.6, 2.3],

соответствует предложению sum([X|Xs], S+X) :- sum(Xs, S), с X=5.6, Xs=[2.3], дает sum([5.6, 2.3], S[2.3]+5.6) :- sum([2.3], S[2.3])

3.sum([2.3], S[]+X), где S[]+X=S[2.3], соответствует предложению sum([X|Xs], S+X) :- sum(Xs, S), с X=2.3, Xs=[], дает sum([2.3], S[]+2.3) :- sum([], S[])

4.sum([], S[]), соответствует предложению sum([], 0.0) :- !, дает S[]=0.

После достижения низа рекурсии, компьютер должен откатиться к началу подсчетов. Что еще хуже, он должен хранить каждый X, который он найдет по пути, чтобы произвести сложение S+X во время отката. Традиционный путь предотвращения отката – использование накопителей. Вот определение, которое использует накопитель для суммирования элементов списка:

add([], A, A).

add([X|Xs], A, S) :- add(Xs, X+A, S).

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

1. add([3.4, 5.6, 2.3], 0.0, S)

соответствует предложению add([X|Xs], A, S) :- add(Xs, X+A, S)

дает add([5.6, 2.3], 0+3.4, S)

2. add([5.6, 2.3], 0.0+3.4, S)

соответствует предложению add([X|Xs], A, S) :- add(Xs, X+A, S)

дает add([2.3], 0+3.4+5.6, S)

3. add([2.3], 0.0+3.4+5.6, S)

соответствует предложению add([X|Xs], A, S) :- add(Xs, X+A, S)

дает add([], 0+3.4+5.6+2.3, S)

что соответствует предложению add([], A, A), возвращая S=11.3.

Вы можете использовать add/3 для вычисления среднего значения списка чисел.

len([], 0) :- !.

len([_X|Xs], 1.0+S) :- len(Xs, S).

add([], A, A) :- !.

add([X|Xs], A, S) :- add(Xs, X+A, S).

sum(Xs, S) :- add(Xs, 0.0, S).

avg(Xs, A/L) :- sum(Xs, A), len(Xs, L).

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

%File: avg.pro

implement avg

open core, console

class predicates

avg:(real*, real, real, real) procedure (i, i, i, o).

clauses

classInfo("avg", "1.0").

avg([], S, L, S/L).

avg([X|Xs], S, L, A) :-

avg( Xs,

X+S, %добавить X к накопителю суммы

L+1.0, %увеличить на единицу накопитель длины

A).

run():- console::init(),

hasdomain(rList, List), List= read(),

avg(List, 0, 0, A),

write(A), nl.

end implement avg

goal

mainExe::run(avg::run).

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

Соседние файлы в папке Лабы_ЭкспСист