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

Costas-Tyros_rus_MS

.pdf
Скачиваний:
31
Добавлен:
14.04.2015
Размер:
5 Мб
Скачать
☆

[[1],[1,1]]

[[1,1]]

[[1,1,1],[1]]

[[1,1,1]]

implement main open core

domains

ms= integer*. hs= ms*.

class predicates

append:(E*, E*, E*) nondeterm anyFlow. takesome:(ms, hs, hs) nondeterm anyFlow. move:(hs, hs) nondeterm anyFlow.

clauses

classInfo("main", "nimgame").

append([], Y, Y).

append([U|X], Y, [U|Z]) : append(X, Y, Z).

takesome([_X], V, V).

takesome([_X, X1|Y], V, [[X1|Y]|V]). takesome([_X|T], V, Y) : takesome(T, V, Y).

move(X, Y) : append(U, [X1|V], X), takesome(X1, V, R), append(U, R, Y).

run(): console::init(), L= [[1, 1, 1], [1, 1]], stdio::write("Possible moves from ", L, ": "), stdio::nl, move(L, Resp),

stdio::write(Resp), stdio::nl, fail; succeed(). end implement main

goal mainExe::run(main::run).

implement main open core

domains

ms= integer*. hs= ms*.

class predicates

append:(E*, E*, E*) nondeterm anyFlow. takesome:(ms, hs, hs) nondeterm anyFlow. move:(hs, hs) nondeterm anyFlow. winningMove:(hs, hs) nondeterm (i, o).

clauses

classInfo("main", "nimgame").

append([], Y, Y).

91

append([U|X], Y, [U|Z]) : append(X, Y, Z).

takesome([_X], V, V).

takesome([_X, X1|Y], V, [[X1|Y]|V]). takesome([_X|T], V, Y) : takesome(T, V, Y).

move(X, Y) : append(U, [X1|V], X), takesome(X1, V, R), append(U, R, Y).

winningMove(X, Y) : move(X, Y), not(winningMove(Y, _)).

run(): console::init(), L= [[1, 1, 1], [1, 1]], winningMove(L, S),

stdio::write("Winning move: ", S), stdio::nl, fail; succeed().

end implement main

goal mainExe::run(main::run).

Сердце этой программы – предикат

winningMove(X, Y) : move(X, Y), not(winningMove(Y, _)).

Если выигрышная стратегия существует, этот потрясающий предикат в одну строчку найдёт её! Ниже вы найдёте пример игры против компьютера. Как вы можете видеть, если есть шанс выиг

рать, машина им воспользуется.

[[1,1,1],[1,1]]

Your move: [[1], [1,1]] My move: [[1],[1]] Your move: [[1]]

My move: [] I win

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

% Файл main.pro implement main

open core, stdio clauses

classInfo("main", "nimgame 1.0").

run(): console::init(), L= [[1, 1, 1], [1, 1]], write(L), nl, nim::game(L), ! ; succeed().

end implement main

goal mainExe::run(main::run).

Интерфейс класса экспортирует метод game/1 и домены, которые вам нужны для описания пози ции в игре. Он определён ниже.

% Файл nim.cl class nim

open core domains

ms= integer*. hs= ms*.

92

predicates

classInfo : core::classInfo. game:(hs) determ (i).

end class nim

% Файл nim.pro implement nim

open core, stdio

class predicates

append:(hs, hs, hs) nondeterm anyFlow. takesome:(ms, hs, hs) nondeterm anyFlow. move:(hs, hs) nondeterm anyFlow. winningMove:(hs, hs) nondeterm (i, o). iwin:(hs) determ (i).

youwin:(hs, hs) determ (i, o).

clauses

classInfo("nim", "1.0").

append([], Y, Y).

append([U|X], Y, [U|Z]) : append(X, Y, Z).

takesome([_X], V, V).

takesome([_X, X1|Y], V, [[X1|Y]|V]). takesome([_X|T], V, Y) : takesome(T, V, Y).

move(X, Y) : append(U, [X1|V], X), takesome(X1, V, R), append(U, R, Y).

winningMove(X, Y) : move(X, Y), not(winningMove(Y, _)).

iwin([]) : !, write("I win"), nl.

youwin(L, L2) : winningMove(L, L2), !. youwin(_L, _L2) : write("You win"), nl, fail.

game(L1) : write("Your move: "),

L= console::read(), move(L1, LTest), L= LTest, youwin(L, L2), !, write("My move: "), write(L2), nl, not(iwin(L2)), game(L2).

end implement nim

Стратегия, использованная в этой очень простой программе, называется алгоритмом минимакса (minmax algorithm). Этот алгоритм может применяться в любой игре с двумя соперниками, где оба

имеют совершенно точное знание о состоянии игры. Примеры таких игр: шахматы, Го, шашки,, кре стики нолики, и Ним. Он также может быть использован для контролирования роботов; фактически,

это разновидность фильтра, основанного на стратегии минимакса , похожая на фильтр Калмана (Kal­ man).

93

Рисунок 10.1 Дерево минимакса.

В стратегии минимакса, состояния игры организованы в дерево, которое называется деревом поиска. Когда наступает ход компьютера, он создаёт состояния минимального веса для оппонента. С другой стороны, он пытается следовать веткам дерева, которые ведут к выигрышному положению. В про стой игре вроде Ним1, компьютер способен найти путь к гарантированной победе. В более сложных играх, как шахматы, это не всегда возможно; требуется прерывать поиск до достижения уверенности в победе. В этом случае, игровые стратеги разрабатывают функции, которые присваивают опреде лённые веса игровым позициям, даже если эти позиции не являются выигрышными или проигрыш ными.

1 Данная программа может найти выигрышный ход только для небольшого количества спичек. С точки зрения математики задача поиска выигрышной стратегии для игры Ним далеко не тривиальна. Стратегия игры для общего случая описана в [Sterling and Shapiro]

94

Глава 11: Факты

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

addItem(journal("AI in focus", "MIT Press")),

Предикат addItem/1 может быть определён так:

class predicates addItem:(volume).

clauses addItem(V) :

num := num+1, assert(item(V, num)).

Выполнение addItem(journal("AI", "AldinePress")) добавляет предложение

item(journal("AI", "AldinePress")).

к базе данных, и увеличивает переменную num. Объявление

class facts bib num:integer := 0.

item:(volume, integer) nondeterm.

создаёт базу данных фактов (fact database) bib, с переменной num и предикатом item/2; преди кат nondeterm, а переменная single. Домен volume, предоставляющий возможность типизации запи сей в этой маленькой базе данных, определён как

domains

name= string.

author= n1(name); n2(name, name); etal(name). publisher= string.

title= string.

volume= journal(title, publisher); book(title, author).

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

% Файл main.pro implement main open core

domains

name= string.

author= n1(name); n2(name, name); etal(name). publisher= string.

title= string.

volume= journal(title, publisher); book(title, author). class facts bib

num:integer := 0.

item:(volume, integer) nondeterm.

95

class predicates addItem:(volume). prtDataBase:().

clauses

classInfo("main", "facttest"). addItem(V) : num := num+1, assert(item(V, num)). prtDataBase() : item(V, I),

stdio::write(I, "=", V), stdio::nl, fail.

prtDataBase(). clauses

run(): console::init(),

addItem(journal("AI in focus", "MIT Press")), addItem(book( "Databases in Prolog", n1("Wellesley Barros"))), file::save("bibliography.fac", bib), prtDataBase().

end implement main goal

mainExe::run(main::run).

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

% Файл main.pro implement main open core

domains

name= string.

author= n1(name); n2(name, name); etal(name). publisher= string.

title= string.

volume= journal(title, publisher); book(title, author). class facts bib

num:integer := 0.

item:(volume, integer) nondeterm. class predicates

prtDataBase:(). clauses

classInfo("main", "factread"). prtDataBase() : item(V, I),

stdio::write(I, "=", V), stdio::nl, fail.

prtDataBase(). clauses

run(): console::init(), file::consult("bibliography.fac", bib), prtDataBase().

end implement main goal

mainExe::run(main::run).

96

Вам надо переместить файл

bibliography.fac

созданный приложением facttest, в папку factread/exe. Затем запустите программу

factread.exe

Вы увидите, что программа прочтёт базу данных и распечатает её.

11.1.Класс file

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

11.1.1.Чтение и вывод строки

Вам часто требуется читать текстовый файл, делать что то с ним, и записывать его обратно. В прошлом, когда у компьютеров было не так много памяти, было необходимо читать потоки симво лов. Сегодня же даже ёмкость памяти персональных компьютеров исчисляется гигабайтами. Следо вательно, лучший подход к решению проблемы – это прочитать весь файл в строку, и использовать мощный класс string для её обработки. Пролог имеет два предиката для подобной работы:

readString:(string FileName, boolean IsUnicodeFile)> string String procedure (i,o).

writeString:(string Filename, string Source, boolean IsUnicodeFile) procedure (i,i,i).

Оба предиката имеют версии, не требующие указания, является ли файл Unicode.

readString:(string FileName)

> string String procedure (i).

writeString:(string Filename, string Source) procedure (i,i).

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

Я думаю, вы вполне сможете разобрать функциональность класса file из справки Visual Prolog. Таким образом, я не буду задерживаться на этой теме.

11.2.Константы

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

•Создайте новый проект.

Project Name: janua

UI Strategy: Object oriented GUI (pfc/gui)

Между прочим, JANUA – это латинское слово, означающее дверь; окно – это маленькая дверь, или JANELLA; входная дверь года это MENSIS JANUARIUS, или January – январь.

97

•Создайте новую форму в новом пакете; пусть форма называется txtWin. Через диалого вое окно Properties добавьте TaskMenu к txtWin.

•Постройте проект. Используйте закладку Events диалогового окна Properties для того, что бы вставить следующий фрагмент кода в MenuItemListener:

clauses

onMenuItem(_Source, M) :

Item= resourceIdentifiers::id_help_contents, M= Item, !,

stdio::write("Hello"). onMenuItem(_Source, _M) .

•Включите в TaskMenu пункт File/New и добавьте фрагмент

clauses

onFileNew(S, _MenuTag) :

X= txtWin::new(S), X:show().

к TaskWindow.win/CodeExpert/Menu/TaskMenu/id_file/id_file_new.

% Файл main.pro implement main open core

constants

className = "main". classVersion = "filetest".

clauses

classInfo(className, classVersion). clauses

run(): console::init(),

Str= file::readString("test.txt", Unicode), stdio::write(Str),

S_Upper= string::toUpperCase(Str), file::writeString("upper.txt", S_Upper, Unicode), succeed(). % place your own code here

end implement main goal

mainExe::run(main::run).

Рисунок 11.1 Чтение строки из файла

98

Глава 12: Классы и объекты

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

•Создайте новый консольный проект.

Project Name: classexample

UI Strategy: console

•Build/Build приложение.

•Создайте новый класс: account. Не убирайте галочку Creates Objects.

•Измените файл account.i как показано:

%Файл account.i

interface account open core

predicates

ssN:(string SocialSecurity) procedure (o). setSocialSecurity:(string SSN) procedure(i). deposit:(real).

end interface account

Класс account создаст новый объект account, когда вы вызовете метод

A=account::new().

Вы можете посылать сообщения в этот объект. Например, вы можете установить номер Social Security клиента:

A:setSocialSecurity("AA345"),

Впоследствии, вы можете извлечь этот самый Social Security.

A:ssN(SocialSecurity),

И ssN/1 и setSocialSecurity/1 должны быть объявлены в интерфейсе, который находится в файле account.i. Банковскому счёту [собственно account – прим. пер.] требуются и другие методы, кроме ssN/1 и setSocialSecurity/1. Например, вам могут понадобиться методы для депозитов, для паро лей, и т.д. Я оставлю создание этих методов вашему воображению. Интересным дополнением могли бы быть методы криптографии.

•Измените account.pro как показано ниже.

%Файл account.pro

implement account open core

facts customer funds:real := 3.0.

personalData:(string Name, string SocialSecurity) single.

clauses

classInfo("account", "1.0"). personalData("", "").

ssN(SS) : personalData(_, SS).

99

setSocialSecurity(SSN) : personalData(N, _), assert(personalData( N, SSN)).

deposit(Value) : funds := funds+Value. end implement account

•Измените run/0 в файле classexample.pro, для того, чтобы протестировать новый класс.

run(): console::init(),

A= account::new(), A:setSocialSecurity("AA345"), A:ssN(SocialSecurity), stdio::write(SocialSecurity), stdio::nl.

12.1.Фактыобъектов

Объекты имеют факты, которые могут быть использованы для хранения их состояний. Философ Витгенштейн (Wittgenstein) очень любил факты и состояния вещей. Давайте посмотрим на первые несколько предложений его Логико Философского трактата (Tratactus Logicus­Philosophicus1).

1.Мир есть всё, что имеет место. (The World is everything that is the case2) В немецком, философ использовал слово Fall: Die Welt ist alles, was der Fall ist. Латинское слово casus

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

своими кубиками. Как минимум, это то, во что верили римляне. Помните, что Юлий Це зарь сказал Alea jacta est3? Я знаю! Он сказал это по гречески, а Суэтоний (Suetonius) пе ревёл это на латынь, но смысл остался.

•Мир есть совокупность фактов, а не вещей (The world is the totality of facts, not of things). = Die Welt ist die Gesamtheit der Tatsachen, nicht der Dinge. Вы заметили,

как Витгенштейн любил факты? Позже, вы увидите, что он думал, что факты должны иметь состояния.

•Мир распадается на факты (The world divides into facts). Отсюда, я избавлю вас от немецкого оригинала.

•Мир определён фактами и тем, что это все факты (The world is determined by the facts, and by these begin all the facts).

•Потому что совокупность всех фактов определяет как всё то, что имеет место, так и всё то, что не имеет места (For the totality of facts determines both what is the case, and also all that is not the case). Обратите внимание, что Вит генштейн рассматривал и факты, которые случились (то, что имеет место), и также те, что являются лишь возможностями (то, что не имеет место).

2.Что имеет место, является фактом, – это существование атомарных фактов

(What is the case, the fact, is the existence of atomic facts).

1Перевод основан на классическом переводе Tratactus Logicus Philosophicus на русский язык Витгенштейн, Л. Логико философский трактат / Пер. с нем. Добронравова и Лахути Д.; Общ. ред. и предисл.

Асмуса В. Ф. — М.: Наука, 1958. — 133 с.

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

2Здесь весьма тонкий момент, вызывающий много споров среди переводчиков «Трактата». Мы перевели это так, как обычно переводится немецкая идиома “der Fall ist” и английская ”is the case”, которым перевели его Рамсей и Огден в 1922 г. – "имеет место". Но дело в том, что немецкое Fall само по себе может означать "случай, происшествие, падение"; английское case (происходящее от латинского casus – случай, казус) тоже означает “случай», поэтому некоторые переводчики переводят это предложение как «Мир есть все, что

случается (или: происходит)», хотя мы (и другие) с этим не согласны. А английское fall не означает «случай», но означает «падение, падать». Автор играет разными смыслами немецкого и английского Fall/fall: «что падает в

мире? Падают кости Фортуны», и т.д. – прим. Д.Г. Лахути. 3 Лат. Жребий брошен.

100

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]