%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Esercizio 1: albero genealogico
%% ovviamente questo e' un esempio.
genitore(X,Y) :-
	member(X,[pierina,temistocle]),
	member(Y,[basilio,clelia,maria,elena]).
genitore(X,Y) :-
	member(X,[aldo,valeria]),
	member(Y,[valentina,anna,silvia]).
genitore(X,Y) :-
	member(X,[valentina,basilio]),
	member(Y,[piera,marcello]).
genitore(anna,Y) :-
	member(Y,[paola,carla,giulio]).
genitore(silvia,Y) :-
	member(Y,[francesa,flavio,paolo,sergio,valeria,
		  maresa,pietro,giovanna,michele]).
genitore(piera,lucia).
genitore(marcello,X) :- member(X,[maria,giulia]).

%% relazioni di parentela 
fratello(X,Y) :-
	genitore(Z,X), genitore(Z,Y), X\=Y.
/* si noti che se X e Y hanno 2 genitori, l'uso del predicato per "generare" 
   i fratelli di qualcuno produrra' due volte la stessa risposta, perche'
   il prolog trova 2 modi diversi di dimostrare fratello(X,Y): uno in cui Z e'
   la madre di X e Y, e uno in cui Z e' il padre */
nonno(Nonno,X) :-
    genitore(Nonno,Padre), genitore(Padre,X).
zio(Zio,X) :-
    genitore(Padre,X), fratello(Padre,Zio).
cugino(X,Y) :-
    genitore(P1,X), genitore(P2,Y), fratello(P1,P2).
%% discendente(X,Y) = X e' un discendente di Y 
discendente(X,Y) :- genitore(Y,X).
discendente(X,Y) :- 
    genitore(Z,X), discendente(Z,Y).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Es 2: fattoriale

fact(0,1).
fact(N,F) :-
    N>0,
    M is N-1,
    fact(M,X),
    F is X*N.

%%%%%%%%%%%%%%%%
%% Es 3: palindroma

palindroma([]).
palindroma([_]).
palindroma([X|Rest]) :-
    %% utilizziamo append per "togliere" l'ultimo elemento di Rest
    append(Middle,[X],Rest),
    palindroma(Middle).

%%%%%%%%%%%%%%%%%%
% Es 4: maxlist
% maxlist(+L,?N): L è una lista di numeri
% e N è il massimo elemento della lista. Fallisce se L è vuota

maxlist([X],X).
maxlist([X|Rest],X) :-
    maxlist(Rest,Maxrest),
    X >= Maxrest, !.
maxlist([_|Rest],Maxrest) :-
    maxlist(Rest,Maxrest).

%%%%%%%%%%%%%%
% Es 5: split(+L,?P,?D)

pari(X) :- 0 is X mod 2.

split([],[],[]).
split([X|L],[X|Pari],Dispari) :- 
    pari(X), !, 
    split(L,Pari,Dispari).
split([X|L],Pari,[X|Dispari]) :- 
    split(L,Pari,Dispari).

%%%%%%%%%%%%%%%%%%%
%% Es 6: Hanoi

%% predicato principale che richiama hanoi a 4 argomenti
hanoi(N) :- hanoi(N,'A','B','C').

% hanoi(N,Start,Goal,Appoggio): 
% rappresentazione della lista di mosse per spostare N dischi da Start a Goal,
% usando Appoggio come appoggio
hanoi(1,Start,Goal,_) :-
	atomic_list_concat(['Sposto un disco da',Start,'a',Goal],' ',Out),
	writeln(Out).
hanoi(N,Start,Goal,Appoggio) :-
	N>1,
	M is N-1,
	hanoi(M,Start,Appoggio,Goal),
	hanoi(1,Start,Goal,Appoggio),
	hanoi(M,Appoggio,Goal,Start).

%%%%%%%%%%%%%%%%%%
% Es 7: prefisso
prefisso([],_).
prefisso([X|Pre],[X|L]) :- 
    prefisso(Pre,L).

%% oppure, usando append:

prefisso2(Pre,L) :-
	append(Pre,_,L).

%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 8: suffisso

suffisso(X,X).
suffisso(Suff,[_|Rest]) :-
    suffisso(Suff,Rest).

%% oppure, usando append:

suffisso2(Suf,L) :-
    append(_,Suf,L).

%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 9: sublist

% sublist(Sub,L) sse Sub e' un prefisso di un suffisso di L
sublist(Sub,L) :-
    append(_,Suffisso,L), %% oppure: suffisso(Suffisso,L)
    append(Sub,_,Suffisso). %% oppure: prefisso(Sub,Suffisso)

%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 10a: subset(+Sub,?Set)

subset([],_).
subset([X|L],Set) :-
    member(X,Set), 
    subset(L,Set).

%%%%%%%%%%%%%%%%
% 10b: rev(+X,?Y)

rev([],[]).
rev([X|Rest],Rev) :-
	rev(Rest,RevRest),
	append(RevRest,[X],Rev).

%%%%%%%%%%%%%%%%%%%%%%
% 10c: del_first(+X,+L,?Resto)

del_first(X,[X|L],L) :- !.
del_first(X,[Y|L],[Y|Resto]) :- 
    del_first(X,L,Resto).

%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 10d:  del(+X,+L,?Resto)

del(_,[],[]).
del(X,[X|L],Resto) :- 
    !, del(X,L,Resto).
del(X,[Y|L],[Y|Resto]) :- 
    del(X,L,Resto).

%%%%%%%%%%%%%%%%%%%%%%
% 10e: subst(+X,+Y,+L,-Nuova)

subst(_,_,[],[]).
subst(X,Y,[X|L],[Y|Nuova]) :-  
    !, subst(X,Y,L,Nuova).
subst(X,Y,[Z|L],[Z|Nuova]) :- 
    subst(X,Y,L,Nuova).

%%%%%%%%%%%%%%
% 10f: mkset(+L,-Set)

mkset([],[]).
mkset([X|L],Set) :-
    member(X,L), !, 
    mkset(L,Set).
mkset([X|L],[X|Set]) :-
    mkset(L,Set).

%%%%%%%%%%%%%%%%
% 10g: union(+A,+B,-Union)
%% ricorsione su A
union([],B,B).
union([X|A],B,Union) :-
    member(X,B), !,
    union(A,B,Union).
union([X|A],B,[X|Union]) :-
    union(A,B,Union).

%%%%%%%%%%%%%%%%%%%
% 11: cartprod(+A,+B,-Set)

%% casi base: uno dei due insiemi e' vuoto
cartprod(_,[],[]):-!.
cartprod([],_,[]):-!.
% questa clausola definisce, di fatto, la costruzione di una lista con tutte le 
% coppie della forma (X,Y), per X fissato e Y in una lista data
cartprod([X],[Y|Tail],[(X,Y)|R]):- 
    !, cartprod([X],Tail,R).
cartprod([H|T],L,R):- 
    cartprod([H],L,R1), 
    cartprod(T,L,R2),
    append(R1,R2,R).

%%%%%%%%%%%%%%%%%
% 12: insert

insert(X,L,[X|L]).
insert(X,[Y|Rest],[Y|Newrest]) :- 
    insert(X,Rest,Newrest).

%%%%%%%%%%%%
% 13: permut

permut([],[]).
permut([X|Rest],Permut) :-
    permut(Rest,Prest),
    insert(X,Prest,Permut).

%%%%%%%%%%%%%%
%14: search_subset(+IntList,+Tot,?Set)

% caso base: Tot=0. Unico risultato possibile: []
search_subset(_,0,[]) :- !.
% possiamo (provare a) inserire N nel risultato
search_subset([N|Set],Tot,[N|Subset]) :-
    N =< Tot,
    Newtot is Tot - N,
    search_subset(Set,Newtot,Subset).
% oppure no
search_subset([_|Set],Tot,Subset) :-
    search_subset(Set,Tot,Subset).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% alberi binari
% 16a: height(+T,?N)

height(t(_,empty,empty),0).
height(t(_,empty,Right),H) :-
	height(Right,R),
	H is R+1.
height(t(_,Left,empty),H) :-
	height(Left,L),
	H is L+1.
height(t(_,Left,Right), H) :-
	height(Left,L),
	height(Right,R),
	(L >= R, !, H is L+1 ; H is R+1).

% 16b: reflect(T,T1)

reflect(empty,empty).
reflect(t(X,Left,Right),t(X,R,L)) :-
    reflect(Left,L), reflect(Right,R).

% 16c: size(+T,?N)

size(empty,0).
size(t(_,Left,Right),N) :-
	size(Left,L), size(Right, R),
	N is L+R+1.

% 16d: labels(+T,-L)

labels(empty,[]).
labels(t(X,Left,Right), [X|List]) :-
	labels(Left,L1), 
	labels(Right,L2),
	append(L1,L2,List).

% 16e: branch(+T,?Leaf,?Path)

branch(t(Leaf,empty,empty),Leaf,[Leaf]).
branch(t(X,Left,_),Leaf, [X|Path]) :-
	branch(Left,Leaf,Path).
branch(t(X,_,Right),Leaf,[X|Path]) :-
	branch(Right,Leaf,Path).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 16. Ricerca di un cammino in un grafo

arc(a,b).
arc(a,e).
arc(b,a).
arc(b,c).
arc(c,c).
arc(c,d).
arc(d,c).
arc(d,b).
arc(e,c).

path(X,Y,P) :- 
    path(X,Y,P,[]).

path(X,X,[X],Visited) :- 
    \+ member(X,Visited).
path(X,Y,[X|P],Visited) :-
    \+ member(X,Visited),
    arc(X,Z),
    path(Z,Y,P,[X|Visited]).

%%%%%%%%%%%%
% 17. Logica proposizionale

%%Rappresentiamo la negazione  mediante l'operatore predefinito "-" 
%% (unario), che ha precedenza 200 e tipo fy.  
:- op(600,yfx,&).    % Congiunzione
:- op(650,yfx,v).    % Disgiunzione
:- op(670,yfx,=>).   % Implicazione
:- op(680,yfx,<=>).  % Doppia implicazione
%% 17a: nnf(+X,-Y)

nnf(P,P) :- atom(P).
nnf(-P,-P) :- atom(P).
nnf(A & B, A1 & B1) :-
    nnf(A,A1), nnf(B,B1).
nnf(A v B, A1 v B1) :-
    nnf(A,A1), nnf(B,B1).
nnf(A => B, A1 v B1) :-
    nnf(-A,A1), nnf(B,B1).
nnf(A <=> B, X) :-
    nnf((A=>B) & (B=>A),X).
nnf(- - A,A1) :-
    nnf(A,A1).
nnf(-(A & B), A1 v B1) :-
    nnf(-A,A1), nnf(-B,B1).
nnf(-(A v B), A1 & B1) :-
    nnf(-A,A1), nnf(-B,B1).
nnf(-(A => B), A1 & B1) :-
    nnf(A,A1), nnf(-B,B1).
nnf(-(A <=> B), X) :-
    nnf((A & -B) v (-A & B),X).

%%%%%%%%%%%%%%%%%%%%%
% 17b: holds(+F,+L).

holds(P,L) :- 
    atom(P), !, member(P,L).
holds(- A,L) :- 
    \+ holds(A,L).
holds(A & B,L) :- 
    holds(A,L), holds(B,L).
holds(A v B,L) :- 
    holds(A,L); holds(B,L).
holds(A => B,L) :- 
    \+ holds(A,L) ; holds(B,L).
holds(A <=> B,L) :- 
    (holds(A,L), holds(B,L)) ;
    (\+ holds(A,L), \+ holds(B,L)).


