Упражнение 5.
Аритметика-продължение.
Вградени предикати

Ако сте решили задачата за питагорови тройки от миналия път, сте се убедили, че програма от вида:

p(X,Y,Z):-int(X), int(Y), int(Z),...

не върши работа, тъй като тя ще ви даде тройката (0,0,0) и ще забие. С този проблем вероятно сте се сблъскали, когато ви се е наложило да подредите множеството на рационалните числа в редица, тогава сте стигнали до извода, че трябва да се грижим едновременно и за числителя, и за знаменателя, за да не пропуснем някое. Сега ще разработим генератор за списъци от числа с произволна дължина, чиито елементи са по-малки от фиксирано число:

sortl(0,M,[]) :- !.
sortl(N,M,[X|Y]) :- betw(0,M,X),N1 is N - 1,sortl(N1,X,Y).

list(N,M,L):- sortl(N,M,L1),perm(L1,L).

Предикатът list(N,M,L)работи по следния начин: при зададена дължина на списъка- N и максимална стойност на елементите- M, в L при преудовлетворяване връща всевъзможните списъци от числа. За упражнение на практикума може да се опитате да направите аналогичния генератор, но да връща списъци без повтарящи се елементи.

2.Да се реши следната задача:
Да се намери съответствието между букви и цифри така, че да е вярно следното равенство:
GERALD
+
DONALD
---------
ROBERT
Като знаете, че Т = 0, а D = 5 и на различните букви съответстват различни цифри:

robert([A,B,E,G,L,N,O,R], R1):-
           R1 is R * 100000 + O * 10000 + B * 1000 + E * 100 + R * 10.

gerald([A,B,E,G,L,N,O,R], G1):-
           G1 is G * 100000 + E * 10000 + R * 1000 + A * 100 + L * 10 + 5.

donald([A,B,E,G,L,N,O,R], D1):-
          D1 is 500000 + O * 10000 + N * 1000 + A * 100 + L * 10 + 5.
 

problem(L):- perm(L,S),
                    gerald(S,G1),
                    robert(S,R1),
                   donald(S,D1),
                   R1 is D1 + G1,
                   write(G1), nl,
                   write(D1), nl,
                   write(R1), nl,nl.

gogo:-problem([1,2,3,4,6,7,8,9]).

Предикати за вход и изход. В различните версии на Пролог интерфейсът е организиран по различен начин. Стандартът изисква да има два предиката:
  -write, който извежда в изходния поток, аргумента му, като ако аргументът е неостойностена променлива извежда _А, където А е номерът не променливата в списъка на променливите; ако аргументът е константа, структура или остойностена променлива, извежда съответната константа или структура, ако искаме да отпечатаме произволен стринг, трябва да го оградим в кавички.
 -read обикновено се използва с аргумент свободна променлива, която се остойностява с въведената от клавиатурата константа или структура. Чете, докато срещне точка или нов ред. Естествено във всяка версия на Пролог има разработени и други предикати за организиране на интерфейс. В SP има широк кръг от предикати за работа с прозорци, които можете да разгледате в примерните програми и в help-а, а част от тях ще обсъдим на практикума.

Например, ако искаме да напишем предикат за въвеждане на нови факти в експертна система, можем да постъпим така:

учи:-repeat,write('>'), read(X), ins(X).

ins(end):-!.
ins(X):-assert(X), nl,fail.

Предикатът assert е от тъй наречените предикати за работа с базата от знания, които сега ще разгледаме:

  - assert(X)- прибавя в края на базата от знания стойността на Х, която трябва да е клауза, и пропада, ако Х не е клауза. Не се преудовлетворява.
 - assertа(X)- същото, както assert(X), но прибавя в началото на базата от знания.
 - retract(X)- изтрива първата клауза, която се унифицира със стойността на Х, ако има такава, и успява, ако няма- пропада.
 - retractall(X)- същото както по- горе, но трие всичките клаузи, които могат да се унифицират с Х, винаги успява.

Особености в SP:
В SP тези предикати ги има и във варианта assert_in, asserta_in, retract_in, които работят аналогично, но
променят не само текущата при изпълнение на програмата база от знания, а и текста на програмата, която се изпълнява в момента. Освен това е реализиран и предикатът set(P(A1,... AN)), който трие всички факти с предикатен символ Р и прибавя към базата от знания факта P(A1,... AN). Тази възможност е много удобна за моделирането на действието на глобални променливи.

Задача:
Да се реализира следния предикат, който е на три аргумента първият е променлива, която е свободна- Х, вторият е цел за Пролог от вида предикат(арг1,арг2,...), като всички аргументи освен един са конкретни, а този, който не е конкретизиран, е свободната променлива Х, и третият аргумент е свободна променлива. Предикатът винаги успява и в третия си аргумент връща списък от всички значения на Х, за които втория аргумент- целта успява.

findall(X,A,_):-asserta(fin(marker)),call(A), asserta(fin(X)), fail.
findall(_,_, L):-saberi([], M),!, L = M.
 

saberi(S,L):-edno(X),!,saberi([X|S], L).
saberi(L,L).

edno(X):-fin(X), retract(fin(X)),!,X \= marker.

Задачи за практикума:

1.Напишете генератор за случайни числа в интервала от 1 до някакво число - К, което е указано в първия аргумент, а вторият е свободна променлива, в която ще получите съответното случайно число. Използвайте следния алгоритъм:
-имате число, което ви дава началното семенце - А
-получавате случайното число, като вземете остатъка при деление на А на К и прибавите единица
-изчислявате новото семенце, което ще използвате при повторно стартиране на предиката по формулата:
(125*А+1)mod4096.

speed(1626).

random1(R,N):-speed(S),retract(speed(S)),N is (S mod R) + 1,Newspeed is (125 * S + 1) mod 4096,
                         assert(speed(Newspeed)),!.
 

2.Напишете предикат за избор на случаен елемент от списък.

len([], 0).
len([_|X], L):-len(X,L1), L is L1 + 1.

izb(L,N,_):-N =< 0,!, fail.
izb(L,N,_):-len(L,M), N > M,!, fail.
izb([X|L], 1,X):-!.
izb([X|L], N,Y):-N1 is N - 1,izb(L,N1,Y).

slizb(L,X):-len(L,N), random1(N,X1), izb(L,X1,X).

_________

Предикати за определяне на типове и работа със структури:
  -atom\1, предикатът се удовлетворява, ако аргументът му е атом или променлива остойностена с атом.
  -integer\1, предикатът се удовлетворява, ако аргументът му е цяло число или променлива, свързана с цяло число.
  -var\1, предикатът се удовлетворява, ако аргументът му е свободна променлива.
  -functor(структура, функтор, брой аргументи)- този предикат успява, ако първият аргумент се унифицира със структура, чието име съвпада с втория аргумент, а броят на аргументите й е третият аргумент. Може да се използва и по следите два начина. Първо с остойностен първи аргумент и другите два свободни, успява, ако първият аргумент е структура или атом и вторият аргумент се остойностява със функтора й, а третият с
броя аргументи. Атомите са структури с 0 аргументи и функтор самият атом. Ако вторият аргумент е свързан с атом, третият с естествено число, а първият е свободна променлива, тогава първият аргумент се свързва със съответната структура, на която аргументите са свободни променливи.
  -arg(номер, структура, аргумент)- трябва да се използва винаги със свързани първи и втори аргумент, предикатът успява, ако третият му аргумент съвпада със съответния аргумент на структурата, който има номер първия аргумент. Ако третият аргумент е свободна променлива, тя се свързва със съответния аргумент от структурата.
  -структура =..списък- Предикатът успява, ако списъкът се състои от функтора на структурата и след това са изредени аргументите й в съответния ред, както са в структурата. Предикатът може да се използва със свързана дясна част и свободна променлива от ляво и обратното като и в двата случая успява, като остойностява променливата.

Особености в SP:
В SP няма предикат atom и arg. Предикатът functor се удовлетворява, само ако първият аргумент е остойностен със структура (като, ако тя е на нулев брой аргументи, трябва да се опише във вида <функтор>()), а другите два са свободни променливи или когато имаме три свързани аргумента, първият от които е свързан със структура от вида <функтор>(), вторият с функтора, а третият с 0.
 

Задача:
Да се реализира предикат, който да работи по същия начин, както =..:

strsp(T,[F|A]):-nonvar(T),!, functor(T,F,N), sparg(1,T,A), len(A,N).
strsp(T,[F|A]):-atom(F),!, len(A,N), functor(T,F,N), sparg(1,T,A).

sparg(I,T,[Ai|A]):-arg(I,T,Ai),!, I1 is I+1,sparg(I1,T,A).
sparg(_,_,[]).

nonvar(X):-var(X),!, fail.
nonvar(_).
 

Задача за 7-тото упражнение на практикума:

На SP можем да реализираме предиката atom по следния начин:

atom1(X):-const1(X).
atom1([X|Y]):-const1(X),atom1(Y).

const1(X):-X = [],!,fail.
const1(X):-const(X).

atom(X):-not(var(X)),X = [],!.
atom(X):-atom1(X),!.

1. Като използвате горния предикат, реализирайте предикатите arg и functor, както са в стандарта на Пролог.

arg(X,T,A):-T=..L,N is X + 1,izb(L,N,A).
 

funct(X,X,0):-atom(X).
funct(X,Y,N):-not(var(X)),!, functor(X,Y1,N1),Y = Y1,N = N1.
funct(X,Y,N):-atom(Y),integer(N),!, N > 0,len1(Z,Y,N),X=..Z.
 
 

len2([],0).
len2([X|Y],N):-N1 is N - 1,len2(Y,N1).
len1([Y|L],Y,N):-len2(L,N),!.
 
 

2. Да се реализира предикатът силно равенство и силно неравенство. Силното равенство успява само ако аргументите му съвпадат, като ако аргументите му са аритметични изрази, не ги пресмята, а ги сравнява като структури; ако аргументите му са свободни променливи, те трябва да са съвместени, за да успее. Силното неравенство е обратен на силното равенство. Внимавайте, предикатите ви не трябва да правят излишни остойностявания на променливите, т. е. ако имате две съвместени неостойностени променливи, те след
изпълнението на предиката трябва да си останат такива.

svp(X,Y):-var(X),!,var(Y),X = 1,not(var(Y)).
svp(X,Y):-var(Y),!,fail.
svp(X,Y):-atomic(X),!,X = Y.
svp(X,Y):-atomic(Y),!,fail.
svp(X,Y):-is_list(X),!,is_list(Y),svsp(X,Y).
svp(X,Y):-is_list(Y),!,fail.
svp(X,Y):-X=..[F|Ax],Y=..[F|Ay],svsp(Ax,Ay).

svsp([],[]).
svsp([X|Ax],[Y|Ay]):-svp(X,Y),svsp(Ax,Ay).

atomic(X):-atom(X),!.
atomic(X):-integer(X),!.
atomic(X):-float(X).

is_list([]).
is_list([_|_]).

nsv(X,Y):-svp(X,Y),!,fail.
nsv(X,Y).

sv(X,Y):-nsv(X,Y),!,fail.
sv(X,Y).

Забележка: силното равенство е sv, а неравенство е nsv. За силното равенство се прави двойно отрицание, за да не свързваме променливите излишно.