Sei sulla pagina 1di 52

EXPERIMENT 1

AIM. ​Write down two intelligent programs for the tic-tac toe problem.

THEORY.
​Tic-tac-toe (also known as noughts and crosses or Xs and Os) is a game for two players, X
and O, who take turns marking the spaces in a 3×3 grid. The player who succeeds in placing
three of their marks in a horizontal, vertical, or diagonal row wins the game. The program
draws the game board, asks the user for the coordinates of the next mark, changes the players
after every successful move, and pronounces the winner. However, in order to fill the empty
square, the system must first verify all the possible moves of opponent to win the game (i.e.,
a heuristic search must use). All these sequences require further production rules. The control
system decides which production rules to use, and in what sequence. A magic square is a
square array of numbers consisting of the distinct positive integers 1, 2, ..., arranged such that
the sum of the numbers in any horizontal, vertical, or main diagonal line is always the same
number. Here, we will be using Target based Approach and Magic Squares to solve the
problem.

ALGORITHM.
This application is an example of a production system. Production systems are applied to
problem solving programs that must perform a wide-range of searches. Production systems
are symbolic AI systems. The difference between these two terms is only one of semantics.
A symbolic AI system may not be restricted to the very definition of production systems,
but they can't be much different either.

Production systems are composed of three parts, a ​global database​, ​production rules​ and a
control​ ​structure. ​Production rules (or simply productions) are conditional if-then branches.
In a​ p​ roduction system, whenever a or condition in the system is satisfied, the system is
allowed to execute or perform a specific action which may be specified under that rule. If the
rule is not fulfilled, it may perform another action. This can be simply paraphrased:

DATA (binded with initial global data base)

when ​DATA satisfies the halting condition​ do

begin

select ​some rule R that can be applied to DATA

return DATA (binded with the result of when R was applied to DATA)

end

For a scenario where a production system is attempting to find the best move in a Tic Tac
Toe, pattern matching is required to tell whether or not a condition is satisfied. If the current
state of a Tic Tac Toe matches the desired state (win state or the solution to game), then
anyone wins in game. However, if this case is not so, the system must attempt an action that
will contribute to manipulating the global database, under the production rules in such a way
that the Machine (i.e Computer) will eventually be winner.

CODE 1.
#include <bits/stdc++.h>
using namespace std;
int mark[10];
void printBoard()
{
cout<<"\n3: User 5: System 2: Empty\n";
 for(int i=1;i<=3;i++)
{
for(int j=1;j<=3;j++)
{
if(j!=3)
cout<<mark[3*(i-1)+j]<<" - ";

else

cout<<mark[3*(i-1)+j];

cout<<endl;
}

}
bool checkwin(int a)

{
int a1, a2, a3=1, a4=1;
for(int i=1;i<=3;i++)
{
a1=1;a2=1;
for(int j=1;j<=3;j++)

{
a1*=mark[(i-1)*3+j];

a2*=mark[i+3*(j-1)];

if(i==j)a3*=mark[(i-1)*3+j];

if(i+j==4)a4*=mark[(i-1)*3+j];

if(a1==a)return 1;
if(a2==a)return 1;
 f(a3==a)return 1;

if(a4==a)return 1; }

return 0;}
int poswin(int x)

int target=18;

if(x==5)target=50;

else if(x==2) target = 20;

else if(x ==1) target = 45;

if(!checkwin(target))return 0;

if(target != 45)

//check rows

int i;

for(int i=1;i<=3;i++)

int a1=1;int idx=-1, jdx=-1;

for(int j=1;j<=3;j++)

a1*=mark[(i-1)*3+j];

if(mark[(i-1)*3+j]==2){idx=i;jdx=j;}

if(a1==target) return (idx-1)*3+jdx;

}
//check columns

for(int i=1;i<=3;i++)

{
 int a1=1;int idx=-1, jdx=-1;

for(int j=1;j<=3;j++)

a1*=mark[i+(j-1)*3];

if(mark[i+(j-1)*3]==2){idx=i;jdx=j;}

if(a1==target) return idx+(jdx-1)*3;

if(mark[1]*mark[5]*mark[9]==target)

if(mark[1]==2)return 1;

if(mark[5]==2)return 5;

if(mark[9]==2)return 9;

if(mark[3]*mark[5]*mark[7]==target)

if(mark[3]==2)return 3;

if(mark[5]==2)return 5;

if(mark[7]==2)return 7;

return 0;
}

else

if(mark[1]*mark[5]*mark[9]==target)

if(mark[1]==5)return 1;
 if(mark[9]==5)return 9;

if(mark[3]*mark[5]*mark[7]==target)

if(mark[3]==2)return 3;

if(mark[7]==2)return 7;

return 0;
}

int main()
{

int i, x, o;

for(i=0;i<10;i++)mark[i]=2;

printBoard();

bool b=0;

for(i=0;i<8;i+=2)

//user takes first move

cout<<"Enter Location 1-9: ";


cin>>x;

while(mark[x]!=2)

cout<<"Location Taken, Choose Another: ";

cin>>x;

mark[x]=3;
 

if(checkwin(27))

cout<<"\nCongrats!\n\n";

b=1;

break;

printBoard();

cout<<endl;

//computer turn

cout<<"Current Board\n";

if(i==0)

{
if(mark[1]==2 && mark[3]==2 && mark[7]==2 &&
mark[9]==2)mark[1]=5; else mark[5]=5;

else if(i==2)

{
x=poswin(3);

int y = poswin(1);

if(x==0)

if(y>0)

{ if(y == 1 || y==9)

if(mark[3] == 2) mark[3]=5;
 
else if(mark[7] ==2) mark[7] = 5;

else

{
if(mark[1] == 2) mark[1]=5;

else if(mark[9] ==2) mark[9] = 5;

}
else

{
if(mark[2]==2)mark[2]=5;

else if(mark[4]==2)mark[4]=5;

else if(mark[6]==2)mark[6]=5;

else if(mark[8]==2)mark[8]=5;

else mark[x]=5;

}
else if(i==4)

x=poswin(5);

int y=poswin(3);

int potentialwin = poswin(2);

if(x>0)

{ mark[x]=5;
cout<<"\nUser
Lost\n"; b=1;
 break;

}
else if(y>0)

mark[y]=5; }

else if(potentialwin>0)

mark[potentialwin] = 5;

{}

else if(mark[7]==2)

mark[7]=5; }

else mark[3]=5;

else

x=poswin(5);

int y=poswin(3);
if(x>0)

mark[x]=5;

cout<<"\nUser Lost\n";

b=1;

break;

else if(y>0)

mark[y]=5; }
 else

//mark random

for(int zz=1;zz<=9;zz++)

if(!mark[zz])

{ mark[zz]=5;
break;

}} }

printBoard();

cout<<endl;}

if(b==0)

{ cout<<"Enter Location
1-9: "; cin>>x;
while(mark[x]!=2)

cout<<"Locaion Taken, Choose Another: ";


cin>>x; }
mark[x]=3;
if(checkwin(27))
{
cout<<"\nCongrats!\n\n";
} else

{
cout<<"\nMatch Drawn\n"; } }

printBoard();

return 0;
}

OUTPUT 1.
DISCUSSION.
I​ n order to take a closer look to control structures let us look at a problem involving the
game. The Tic Tac Toe contains Nine Blank squares laid in a three-by-three grid. Player fills
square with either "X" or "O" (according to his choice of mark). In next move computer
appears in some, obfuscated state. The goal of the production system is to reach some final
state (the win state). This can be obtained by successively moving squares into the empty
position. This system changes with every move of the square, thus, the global database
changes with time. The current state of the system is given as the position and enumeration of
the squares. This can be represented for example as a 9 dimensional vector with components
0, 1, 2, 3,..., 8, NULL, the NULL object being the empty space.

LEARNING.
In this Tic Tac Toe , the most general production rule can be simply summed up in one
sentence- First fill the empty square that makes the opponent winner, if isn't then fill the box
that help us to win the game.
CODE 2.
#include <iostream>

#include<vector>
using namespace std;
int m[10];
char board[10];
bool isTaken[10];
void setBoard()
{
m[1] = 8;
 m[2] = 3;
m[3] = 4;
m[4] = 1;
m[5] = 5;
m[6] = 9;
m[7] = 6;
m[8] = 7;
m[9] = 2;
for(int i=0; i<10; i++)
{
board[i] = '-';
}
}
void printBoard()
{
cout<<"X: User\t O: System\t -: Blank\n";
for(int i=1;i<=3;i++)
{
for(int j=1;j<=3;j++)
{
if(j!=3)
cout<<board[3*(i-1)+j]<<" ";
else
cout<<board[3*(i-1)+j];

cout<<endl
}
}
int search_pos(int D)
{
for(int i=1; i<10; i++)
{
if(m[i] == D)
{
return i;
}
}
}
int poswin(vector<int> &played_list)
{
int diff = 0;
for(int j=0 ; j<played_list.size() ; j++)
{
for(int k = 0; k<played_list.size() ; k++)
{
int D = 15 - (m[played_list[j]] + m[played_list[k]]);
if(D>0 && D<9)
{
int pos = search_pos(D);
if(isTaken[pos] == false)
 return pos
}
}
}
return 0;
}int getBlank()
{
for(int i=1; i<=9 ; i++)
{
if(isTaken[i] == false)
return i;
}
return 0;
}
int main()
{
setBoard();
int x,b=0;
vector<int> comp, player;
for(int i=0; i<8; i+=2)
 {
cout<<"Enter Location 1-9: ";
cin>>x ;
while(isTaken[x]!=false)
{
cout<<"Location Marked, Select Another Location: ";
cin>>x;
}
isTaken[x] = true;
board[x] = 'X';
player.push_back(x);
if(player.size() >= 3)
{
for(int j=0 ; j<player.size() ; j++)
{
for(int k = 0; k<player.size() ; k++)
{
for(int l = 0; l<player.size() ; l++)
{
if(j!=k && k!=l && j!=l)
{
if(m[player[j]] + m[player[k]] + m[player[l]] == 15)
{
cout<<"Victory";
b=1;
 break;}
}
}
}
}
}
printBoard();
cout<<endl;
int poswin_player = poswin(player);
if(poswin_player > 0)
{
isTaken[poswin_player] = true;
board[poswin_player] = 'O';
comp.push_back(poswin_player);
}
else
{int poswin_comp = poswin(comp);
if(poswin_comp > 0)
{
isTaken[poswin_comp] = true;
board[poswin_comp] = 'O';
comp.push_back(poswin_comp);
 
cout<<"\nDefeat";
b=1;
}
else
{
if(i==0)
{
if(isTaken[1]==false && isTaken[3]==false && isTaken[7]==false && isTaken[9]==false)
{
isTaken[1]=true;
board[1] = 'O';
comp.push_back(1);
}
else
{
isTaken[5]=true;
board[5] = 'O';
comp.push_back(5);
}
}else if(i==2 || i == 4)
{
if(isTaken[5] == false)
{
isTaken[5]=true;
 board[5] = 'O';
comp.push_back(5);
}
else if(isTaken[2]==false)
{
isTaken[2]=true;
board[2] = 'O';
comp.push_back(2);
}
else if(isTaken[4]==false)
{
isTaken[4]=true;
board[4] = 'O';
comp.push_back(4);
}
else if(isTaken[6]==false)
{
isTaken[6]=true;
board[6] = 'O';
comp.push_back(6);
}
else if(isTaken[8]==false)
{
isTaken[8]=true;
board[8] = 'O';
comp.push_back(8);
 }}
else if(i==6)
{
int blank = getBlank();
isTaken[blank] = true;
board[blank] = 'O';
comp.push_back(blank);
}
}
}
printBoard();
cout<<endl;
if(b==1)
{
break;
}
}
if(b == 0)
{
int flag = 0;
cout<<"Enter Location 1-9: ";
cin>>x;
while(isTaken[x]!=false)
{
cout<<"Location Marked, Select Another Location: ";
 cin>>x;
}
isTaken[x] = true;
board[x] = 'X';
player.push_back(x);
printBoard();
if(player.size() >= 3)
{
for(int j=0 ; j<player.size() ; j++)
{
for(int k = 0; k<player.size() ; k++)
{
for(int l = 0; l<player.size() ; l++)
{
if(j!=k && k!=l && j!=l)
{
if(m[player[j]] + m[player[k]] + m[player[l]] == 15)
{
cout<<"Victory";
flag = 1;
break;
}
}
}
 }}
}
if(comp.size() >= 3)
{
for(int j=0 ; j<comp.size() ; j++)
{
for(int k = 0; k<comp.size() ; k++)
{
for(int l = 0; l<comp.size() ; l++)
{
if(j!=k && k!=l && j!=l)
{
if(m[comp[j]] + m[comp[k]] + m[comp[l]] == 15)
{
cout<<"\nDefeat";
flag = 1;
}}}}
}
}
if( flag == 0) {
cout<<"\n Draw";}
}
}

OUTPUT 2.
LEARNING.
​ he magic square logic can be used for any size of grid because it allows fast checking of
T
winning possibilities and in making the next move, after successfully implementing both the
approaches.

EXPERIMENT 2

AIM. ​To implement water jug problem using BFS and DFS.
THEORY.
​You are at the side of a river. You are given a m litre jug and a n litre jug where 0 < m < n.
Both the jugs are initially empty. The jugs don’t have markings to allow measuring smaller
quantities. You have to use the jugs to measure d litres of water where d < n. Determine the
minimum no of operations to be performed to obtain d litres of water in one of jug.
The operations you can perform are-
1. Empty a Jug
2. Fill a Jug
3. Pour water from one jug to the other until one of the jugs is either empty or full.
The problem is solvable only when t is a multiple of gcd (a, b) and can be modelled as search
through a state space. The state space for this problem can be described as the set of ordered
pair of integers (x, y) such that x ​∈​ {0, 1, 2, …, a} and y ​∈​ {0, 1, 2, …, b}. The initial state is
(0, 0) and the goal states are (t, y) and (x, t) ​∀​ x, y.
Here, we will be using Recursion with Dynamic Programming (DP) (involving Stack Data
Structure) to solve the problem.

ALGORITHM 1.
​All we need now for a search procedure to work is a way to generate new states (successors)
from a given state. This is captured by production rules that specify how and when a new
state can be generated from a given state. For the water jug problem, the following
production rules are sufficient:

1. (x, y) -> (a, y) if x < a i.e., Fill the first jug if it is not already full

2. (x, y) -> (x, b) if y < b i.e., Fill the second jug if it is not already full

3. (x, y) -> (0, y) if x > 0 i.e., Empty the first jug

4. (x, y) -> (x, 0) if y > 0 i.e, Empty the second jug


5. (x, y) -> (min(x + y, a), max(0, x + y – a)) if y > 0 i.e., Pour from second jug into first
jug until the first jug is full or the second jug is empty

6. (x, y) -> (max(0, x + y – b), min(x + y, b)) if x > 0 i.e., Pour from first jug into second
jug until the second jug is full or the first jug is empty

CODE 1.
#include <bits/stdc++.h>
using namespace std;
vector<pair<int, int> >ans;
bool func(int trgt, int j1, int j2, int cap1, int cap2,vector<vector<bool>
>&vis) { if (vis[j1][j2])
return false;
vis[j1][j2] = true;
if (j1 == trgt && j2==0) {
ans.push_back(make_pair(j1,j2));
return true;
}
if (j2 == trgt && j1==0) {
ans.push_back(make_pair(j1, j2));
return true;}
 bool done = false
done = func(trgt, cap1, j2, cap1, cap2, vis);
if (done) {
ans.push_back(make_pair(j1, j2));
return done;}
done = func(trgt, j1, cap2, cap1, cap2, vis);
if (done) {
ans.push_back(make_pair(j1, j2));
return done;}
int transfer = min(cap1 - j1, j2);
done = func(trgt, j1 + transfer, j2 - transfer, cap1, cap2, vis); if
(done) {
ans.push_back(make_pair(j1, j2));
return done;
}
transfer = min(j1, cap2 - j2);
done = func(trgt, j1 - transfer, j2 + transfer, cap1, cap2, vis); if
(done) {
ans.push_back(make_pair(j1, j2));
return done;
}
done = func(trgt,0,j2,cap1,cap2,vis);
if (done) {
ans.push_back(make_pair(j1, j2));
return done;
}
done = func(trgt, j1,0 , cap1, cap2, vis);
 if(done) {
ans.push_back(make_pair(j1, j2));
return done; }
return false;}
int main()
{
int jug1, jug2;
cout<<"Enter capacity of jug 1: ";
cin >> jug1;
cout<<"Enter capacity of jug 2: ";
cin >> jug2;
int trgt;
cout<<"Enter volume to be measured: ";
cin >> trgt;
vector<bool> in(jug2 + 1, false);
vector<vector<bool> >vis(jug1+1,in);
bool poss=func(trgt,0,0, jug1, jug2,vis);
if (poss) {
cout<<"The states of jugs are-\n";
int lim = ans.size();
while (lim > 0) {
--lim;
cout << ans[lim].first << ", " << ans[lim].second << endl; }
} else {
cout << "Not possible";}
return 0;
}

OUTPUT 1.
DISCUSSION.
Strictly speaking, the conditions in the production rules are not required e.g., we could fill an
already full jug except that it won’t lead us anywhere and would be wasteful in a tree search
procedure where the visited states are not saved to prevent revisiting. Moreover, any search
procedure (even BFS) can be applied to systematically search from the initial state to one of
the goal states through the state space.

LEARNING.
We were able to find out the minimum number of steps and the associated sequence to obtain
the desired amount of measurement using DFS procedure.

ALGORITHM 2.
All we need now for a search procedure to work is a way to generate new states (successors)
from a given state. This is captured by production rules that specify how and when a new
state can be generated from a given state. For the water jug problem, the following
production rules are sufficient:

1. (x, y) -> (a, y) if x < a i.e., Fill the first jug if it is not already full
2. (x, y) -> (x, b) if y < b i.e., Fill the second jug if it is not already full
3. (x, y) -> (0, y) if x > 0 i.e., Empty the first jug
4. (x, y) -> (x, 0) if y > 0 i.e., Empty the second jug
5. (x, y) -> (min (x + y, a), max(0, x + y – a)) if y > 0 i.e., Pour from second jug into first
jug until the first jug is full or the second jug is empty
6. (x, y) -> (max (0, x + y – b), min(x + y, b)) if x > 0 i.e., Pour from first jug into
second jug until the second jug is full or the first jug is empty.

CODE 2.
#include <bits/stdc++.h>
using namespace std;
void waterJugBfs(int trgt, int cap1, int cap2, vector<vector<pair<int, int> >
>&parent) { queue<pair<int, int> >q;
pair<int, int> start = make_pair(0, 0), last;
q.push(start);
bool poss = false;
while (!q.empty() && !poss) {
pair<int, int> cur = q.front();
q.pop();
int j1 = cur.first, j2 = cur.second;
if (j1 == trgt && j2 == 0) {
last = cur;
poss = true; break; }
else if (j1 == 0 && j2 == trgt) {
last = cur;
poss = true; break; }
pair<int, int> child = make_pair(cap1, j2), unvis = make_pair(-1, -1); if
(parent[child.first][child.second] == unvis) {
parent[child.first][child.second] = cur;
 q.push(child); }
child = make_pair(j1, cap2);
if (parent[child.first][child.second] == unvis) {
parent[child.first][child.second] = cur;
q.push(child);
}
int transfer = min(cap1 - j1, j2);
child = make_pair(j1 + transfer, j2 - transfer);
if (parent[child.first][child.second] == unvis) {
parent[child.first][child.second] = cur;
q.push(child); }
transfer = min(j1, cap2 - j2);
child = make_pair(j1 - transfer, j2 + transfer);
if (parent[child.first][child.second] == unvis) {
parent[child.first][child.second] = cur;
q.push(child); }
child = make_pair(0, j2);
if (parent[child.first][child.second] == unvis) {
parent[child.first][child.second] = cur;
q.push(child); }
child = make_pair(j1, 0);
if (parent[child.first][child.second] == unvis) {
parent[child.first][child.second] = cur;
q.push(child); }}
if (poss) {
cout << "The states of jugs are-\n";
vector<pair<int, int> >ans;
while (last.first != 0 || last.second != 0) {
 ans.push_back(last);
last = parent[last.first][last.second];
}
ans.push_back(make_pair(0, 0));
reverse(ans.begin(), ans.end());
pair<int, int>p;
for (int i = 0; i<ans.size(); ++i) {
p = ans[i];
cout << p.first << ", " << p.second << endl;
}}
else {
cout << "Not possible";
}}
int main()
{
int jug1, jug2;
cout << "Enter capacity of jug 1: ";
cin >> jug1;
cout << "Enter capacity of jug 2: ";
cin >> jug2;
int trgt;
cout << "Enter volume to be measured: ";
cin >> trgt;
vector<pair<int, int> > in(jug2 + 1, make_pair(-1, -1));
vector<vector<pair<int, int> > > parent(jug1 + 1,
in); waterJugBfs(trgt, jug1, jug2, parent); return
0;}

OUTPUT 2.

DISCUSSION.
​Strictly speaking, the conditions in the production rules are not required e.g., we could fill an
already full jug except that it won’t lead us anywhere and would be wasteful in a tree search
procedure where the visited states are not saved to prevent revisiting.

Moreover, any search procedure (even DFS) can be applied to systematically search from the
initial state to one of the goal states through the state space.

LEARNING.
We were able to find out the minimum number of steps and the associated sequence to
obtain the desired amount of measurement using BFS procedure.
EXPERIMENT 3
AIM. ​Write down program to implement least cost search for 8 puzzle problem.
THEORY.
The 8 puzzle consists of eight numbered, movable tiles set in a 3x3 frame. One cell of the
frame is always empty thus making it possible to move an adjacent numbered tile into the
empty cell. The program is to change the initial configuration into the goal configuration. To
solve a problem using a production system, we must specify the global database the rules,
and the control strategy. For the 8 puzzle problem that correspond to the problem states,
moves and goal. In this problem each tile configuration is a state. The set of all configuration
in the space of problem states or the problem space, there are only 3, 62, 880 different
configurations. For the 8-puzzle, a straight forward description is a 3X3 array of matrix of
numbers. The initial global database is this description of the initial problem state.

ALGORITHM.

algorithm LCSearch(list_node *t)

if (*t is an answer node)

print(*t);

return;

E = t;

while (true)

for each child x of E

if x is an answer node

print the path from x to t;

return;

} Add (x);
x->parent = E; }

if there are no more live nodes

print ("No answer node");

return;

E = Least(); }

CODE.
#include <bits/stdc++.h>

using namespace std;

#define N 3

struct Node

{ Node* parent;

int mat[N][N];

int x, y;

int cost;

int level;

};

int printMatrix(int mat[N][N])

for (int i = 0; i < N; i++)

for (int j = 0; j < N; j++)

printf("%d ", mat[i][j]);

printf("\n");

}
Node* newNode(int mat[N][N], int x, int y, int newX,

int newY, int level, Node* parent)

Node* node = new Node;

node->parent = parent;

memcpy(node->mat, mat, sizeof node->mat);

swap(node->mat[x][y], node->mat[newX][newY]);

node->cost = INT_MAX;

node->level = level;

node->x = newX;

node->y = newY;

return node;

int row[] = { 1, 0, -1, 0 };

int col[] = { 0, -1, 0, 1 };

int calculateCost(int initial[N][N], int final[N][N])

int count = 0;

for (int i = 0; i < N; i++)

for (int j = 0; j < N; j++)

if (initial[i][j] && initial[i][j] != final[i][j])

count++;

return count;

int isSafe(int x, int y)

return (x >= 0 && x < N && y >= 0 && y < N);


}void printPath(Node* root)

if (root == NULL)

return;

printPath(root->parent);

printMatrix(root->mat);

printf("\n");

}struct comp

bool operator()(const Node* lhs, const Node* rhs) const

return (lhs->cost + lhs->level) > (rhs->cost + rhs->level);

};

void solve(int initial[N][N], int x, int y,

int final[N][N])

priority_queue<Nde*, std::vector<Node*>, comp> pq;

Node* root = newNode(initial, x, y, x, y, 0, NULL);

root->cost = calculateCost(initial, final);

pq.push(root);

while (!pq.empty())

Node* min = pq.top();

pq.pop();

if (min->cost == 0)

{
printPath(min);

return;

for (int i = 0; i < 4; i++)

if (isSafe(min->x + row[i], min->y + col[i]))

Node* child = newNode(min->mat, min->x,

min->y, min->x + row[i],

min->y + col[i],

min->level + 1, min);

child->cost = calculateCost(child->mat, final);

pq.push(child);

int main()

int initial[N][N] =

{1, 2, 3},

{5, 6, 0},

{7, 8, 4}

};

int final[N][N] =

{ {1, 2, 3}, {5, 8, 6}, {0, 7, 4} };


int x = 1, y = 2;

solve(initial, x, y, final);

return 0;

OUTPUT,

RESULT.
The code is successfully implemented and the output is shown here.

DISCUSSION.
Branch and bound or least cost search for an answer node speeds up the search by using an
“intelligent” ranking function, also called an approximate cost function to avoid searching in
sub-trees that do not contain an answer node. It is similar to backtracking technique but uses
BFS-like search.

LEARNING.
The least cost search algorithm can be used to solve 8-Puzzle problem efficiently.
EXPERIMENT 4

AIM. ​Write down program to implement steps of A* for 8 puzzle problem.


THEORY.
The 8 puzzle consists of eight numbered, movable tiles set in a 3x3 frame. One cell of the
frame is always empty thus making it possible to move an adjacent numbered tile into the
empty cell. The program is to change the initial configuration into the goal configuration. To
solve a problem using a production system, we must specify the global database the rules,
and the control strategy. For the 8 puzzle problem that correspond to the problem states,
moves and goal. In this problem each tile configuration is a state. The set of all configuration
in the space of problem states or the problem space, there are only 3, 62, 880 different
configurations. For the 8-puzzle, a straight forward description is a 3X3 array of matrix of
numbers. The initial global database is this description of the initial problem state.

ALGORITHM.

1. Get the first state, which is your start state.


2. Get all the possible states in which puzzle can be.
3. Add these new states in open state list
4. Add processed sate in the closed list
5. Pick the next state which has the lowest cost in the open list
6. Start repeating the steps from 1 to 6 unless you are into the final state, or no more
states to examine (in this case, no solution exists).
7. Once you have the final state, backtrack to the start node and that would be the
optimal path to find the solution.

CODE.
#include <bits/stdc++.h>
using namespace std;
typedef vector<vector<int>> vvi;
int calc(vvi & bo, vvi & best) {
int ret = 0;
vector<pair<int, int>> use(9);
for(int i = 0; i < 3; i ++) {
for(int j = 0; j < 3; j ++) {
use[bo[i][j]] = {i, j};
}
}
for(int i = 0; i < 3; i ++) {
for(int j = 0; j < 3; j ++) {
ret += abs(use[best[i][j]].first - i) + abs(use[best[i][j]].second - j);
}
}
return ret;
}
void print(vvi & bo) {
for(int i = 0; i < 3; i ++) {
for(int j = 0; j < 3; j ++) {
cout << (bo[i][j] ? to_string(bo[i][j]) : " ") << " \n"[j == 2];
}
}
cout << '\n';
}
int dx[] = {0, 1, -1, 0};
int dy[] = {1, 0, 0, -1};
vector<vvi> gen(vvi & bo) {
vector<vvi> ret;
for(int i = 0; i < 3; i ++) {
for(int j = 0; j < 3; j ++) if(not bo[i][j]) {
for(int k = 0; k < 4; k ++) {
int ni = i + dx[k], nj = j + dy[k];
if(ni >= 0 and nj >= 0 and ni < 3 and nj < 3) {
swap(bo[ni][nj], bo[i][j]);
ret.emplace_back(bo);
swap(bo[ni][nj], bo[i][j]);
}
}
}
}
return ret;
}

struct state {
int c = 0, s = 0;
vvi p, cur;
};
struct cmp {
bool operator() (state & l, state & r) {
return ((l.c == r.c) ? l.s > r.s : l.c > r.c);
}
};

int main() {
priority_queue<state, vector<state>, cmp> pq;
map<vvi, int> vis;
map<vvi, vvi> from;
map<vvi, int> timecost;
vvi bo(3, vector<int>(3));
vvi best = bo;

for(int i = 0; i < 3; i ++) {


for(int j = 0; j < 3; j ++) {
cin >> bo[i][j];
best[i][j] = (1 + i * 3 + j) % 9;
}
}

state og;
og.c = calc(bo, best);
og.cur = bo;
pq.emplace(og);

while(!pq.empty()) {
auto ele = pq.top(); pq.pop();
if(not vis[ele.cur]) {
vis[ele.cur] = 1;
from[ele.cur] = ele.p;
timecost[ele.cur] = ele.s;
if(not ele.c) {
break;
}
auto children = gen(ele.cur);
state te;
te.p = ele.cur;
te.s = ele.s + 1;
for(auto child : children) {
te.c = calc(child, best);
te.cur = child;
pq.emplace(te);
}
}
}

if(vis[best]) {
stack<vvi> use;
vvi te = best;
while(te.size()) {
use.emplace(te);
te = from[te];
}
while(not use.empty()) {
print(use.top());
use.pop();
}
} else {
cout << "Not Possible";
}
}
OUTPUT.
RESULT.
The code is successfully implemented and the output is shown here.

DISCUSSION.​
A* ​is a computer algorithm that is widely used in pathfinding and graph traversal, the process
of plotting an efficiently directed path between multiple points, called nodes. It enjoys
widespread use due to its ​performance and accuracy. It is found that A* to be superior to
other approaches.

LEARNING.
The A* algorithm can be used to solve 8-Puzzle problem efficiently
EXPERIMENT 5
AIM. ​To solve cryptarithmetic problem, eg-
SEND

+MORE

-----------

MONEY

THEORY.
Cryptarithmetic problems are where numbers are replaced with alphabets. By using standard
arithmetic rules, we need to decipher the alphabet.
General Rules:
1. Each alphabet takes only one number from 0 to 9 uniquely.
2. Two single digit numbers sum can be maximum 19 with carryover. So carry over in
problems of two number addition is always 1.
3. Try to solve left most digit in the given problem.
4. If a × b = kb, then the following are the possibilities
(3 × 5 = 15; 7 × 5 = 35; 9 × 5 = 45) or (2 × 6 = 12; 4 × 6 = 24; 8 × 6 = 48)
Here is a sample problem:
SEND + MORE = MONEY
A solution to the puzzle is
S = 9, R = 8, O = 0, M = 1, Y = 2, E = 5, N = 6, D= 7
That is,
9567+
1085
------------
10652

ALGORITHM.
1. Set distinct values for all variables(S, E,N,D,M,O,R,Y). The values should be
between 0 and 9.
2. Seeing the problem M and S can’t be Zero as they are first digit of the numbers.
3. For each set of values do
● Calculate send=send=S*1000+E*100+N*10+D,
more=M*1000+O*100+R*10+E and
money=M*10000+O*1000+N*100+E*10+Y;
● Check if send+more=money if yes return solution else continue
4. If no solution found, return no solution.

CODE.
#include<iostream>

using namespace std;


int main(){
bool arr[10]={0};
int S,E,N,D,M,O,R,Y,send,more,money,c;
for(M=1;M<=9;M++)
{
arr[M]=1;
for(O=0;O<=9;O++)
{
if(arr[O]==0)
{
arr[O]=1;
for(S=1;S<=9;S++)
{
if(arr[S]==0)
{
arr[S]=1;
for(E=0;E<=9;E++)
{
if(arr[E]==0)
{
arr[E]=1;
for(N=0;N<=9;N++)
{
if(arr[N]==0)
{
arr[N]=1;
for(R=0;R<=9;R++)
{
if(arr[R]==0)
{
arr[R]=1;
for(Y=0;Y<=9;Y++)
{
if(arr[Y]==0)
{
arr[Y]=1;
for(D=0;D<=9;D++)
{
if(arr[D]==0)
{
arr[D]=1;
send=S*1000+E*100+N*10+D;
more=M*1000+O*100+R*10+E;
money=M*10000+O*1000+N*100+E*10+Y;
if((send+more)==money)
{
cout<<"Values are as follows \n"<<send<<"+"<<more<<"="<<money<<"\n";
cout<<"S="<<S<<endl;
cout<<"E="<<E<<endl;
cout<<"N="<<N<<endl;
cout<<"D="<<D<<endl;
cout<<"M="<<M<<endl;
cout<<"O="<<O<<endl;
cout<<"R="<<R<<endl;
cout<<"Y="<<Y<<endl;
cin>>c;
return 0;
}
arr[D]=0;
}
}
arr[Y]=0;
}
}
arr[R]=0;
}
}
arr[N]=0;
}
}
arr[E]=0;
}
}
arr[S]=0;
}
}
arr[O]=0;
}
}
arr[M]=0;
}
return 0;
}
OUTPUT.

DISCUSSION.
Cryptarithmetic is the science and art of creating and solving cryptarithms. A
Cryptarithmetic is a genre of mathematical puzzle in which the digits are replaced by letters
of the alphabet or other symbols.

LEARNING.
Solving problems like these involves understanding some basic principles and rules of
addition and a lot of trial and error.
EXPERIMENT 6

AIM. ​To implement AO* algorithm.


THEORY.
​A solution in an AND-OR tree is a ​sub tree​ whose ​leafs​ are included in the goal set. Cost
function:sum of costs in AND node

f(n) = f(n1) + f(n2) + …. + f(nk)

The Depth first search and Breadth first search given earlier for OR trees or graphs can be
easily adopted by AND-OR graph. The main difference lies in the way termination
conditions are determined, since all goals following an AND nodes must be realized; where
as a single goal node following an OR, node will do. So, for this purpose we are using AO*
algorithm. Like A* algorithm here we will use two arrays and one heuristic function.

OPEN:

It contains the nodes that has been traversed but yet not been marked solvable or
unsolvable.

CLOSE​:

It contains the nodes that have already been processed.

PROCESS:

▪ Traverse the graph (from the initial node) following the best current path.
▪ Pick one of the unexpanded nodes on that path and expand it. Add its successors to
the graph and compute ​f​ for each of them
▪ Change the expanded node’s ​f​ value to reflect its successors. Propagate the
change up the graph.
▪ Reconsider the current best solution and repeat until a solution is found.

ALGORITHM.
1. ​Place the starting node into OPEN.
2. ​Compute the most promising solution tree say T0.
3. ​Select a node n that is both on OPEN and a member of T0. Remove it from OPEN
and place it in CLOSE
4. ​If n is the terminal goal node then leveled n as solved and leveled all the ancestors​ ​of
n as solved. If the starting node is marked as solved then success and exit.
5. ​If n is not a solvable node, then mark n as unsolvable. If starting node is marked as
unsolvable, then return failure and exit.
6. ​Expand n. Find all its successors and find their h (n) value, push them into OPEN.
7. ​Return to Step 2.
8. ​Exit
CODE.
#include <bits/stdc++.h>
using namespace std;
struct Node {
char id;
int cost;
vector < pair < Node *, int > > or_list;
vector < pair < Node *, int > > and_list;
Node *par;
Node(char x, int c) {
id = x;
cost = c;
par = NULL;
}
};
Node* input_graph(){
queue<Node*> q;
char id;
int tempCost;
cout<<"Enter the node id: "<<endl;
cin>>id;
cout<<"Enter the cost: "<<endl;
cin>>tempCost;
Node* root = new Node(id,tempCost);
 
// root.par =
NULL;
q.push(root);
int pathCost;
while(q.empty()!=tru
e){
Node* temp =q.front();
q.pop();
int andnum;
cout<<"Enter number of nodes in AND list of node
"<<temp->id<<endl; cin>>andnum;
for(int i=0;i<andnum;i++){
cout<<"Enter the node id:
"<<endl; cin>>id;
cout<<"Enter the cost: "<<endl;
cin>>tempCost;

cout<<"Enter the path cost: "<<endl;


cin>>pathCost;
Node* newNode = new Node(id,tempCost);
newNode->par = temp;
temp->and_list.push_back(make_pair(newNode,pathC
ost)); q.push(newNode);
}
int ornum;
cout<<"Enter number of nodes in OR list of node
"<<temp->id<<endl; cin>>ornum;
for(int i=0;i<ornum;i++){
cout<<"Enter the node id:
 "<<endl; cin>>id;
cout<<"Enter the cost: "<<endl;
cin>>tempCost;
cout<<"Enter the path cost: "<<endl;
cin>>pathCost;
Node* newNode = new Node(id,tempCost);
newNode->par = temp;
temp->or_list.push_back(make_pair(newNode,pathCost));
q.push(newNode);
}
}
return root;
}
bool isLeaf(Node *cur)
{
return (cur->and_list.size() == 0 && cur->or_list.size() == 0);
}
void propogate_cost(Node * cur, int dif)
{
if(dif == 0)
return;
while(cur->par!=NULL)
{
cur = cur->par;
 cur->cost += dif;
}
}
int main()
{
Node * root = input_graph();
list < pair < int, Node * > > cur;
list < pair < int, Node * > >::iterator it;
cout<<"\nThe Graph is: "<<endl;
cur.push_back({root->cost, root});
//queue<Node> q
while(!cur.empty())
{
it = cur.begin();
Node *tmp = (*it).second;
int c = (*it).first;
cur.erase(it);
cout<<tmp->id<<" "<<c<<endl;
if(tmp->and_list.size()>0)
{
int s = tmp->and_list.size();
int new_c = 0;
for(int i=0;i<s;i++)
{Node *nxt = tmp->and_list[i].first;
int ed_wt = tmp->and_list[i].second;
 new_c += ed_wt;
new_c += nxt->cost;
cur.push_back({nxt->cost, nxt});}
propogate_cost(tmp, new_c - tmp->cost);
tmp->cost = new_c;
}
if(tmp->or_list.size()>0)
{int s = tmp->or_list.size(); int
new_c = INT_MAX;
Node * toPush;
for(int i=0;i<s;i++)
{ Node *nxt = tmp->or_list[i].first;
int ed_wt = tmp->or_list[i].second;
int tmp_c = ed_wt;
tmp_c += nxt->cost;
if(tmp_c<new_c)
{
new_c = tmp_c;
toPush = nxt;
}
}
cur.push_back({toPush->cost, toPush});
propogate_cost(tmp, new_c - tmp->cost);
tmp->cost = new_c;}
cur.sort();}
cout<<"Final Total Cost: "<<root->cost<<endl;
return 0;
}
OUTPUT.
DISCUSSION.

Advantages​:
It is an optimal algorithm.
If traverse according to the ordering of nodes. It can be used for both OR and AND
graph.
Disadvantages​:
Sometimes for unsolvable nodes, it can’t find the optimal path. Its complexity is than
other algorithms.

LEARNING.
AO* is useful for searching game trees, problem solving etc. but in most cases, more
domain specific search algorithms (e.g. alpha-beta pruning for game trees, general or
domain specific planning algorithms) are used instead.

In particular, AI uses knowledge-intensive approaches and in practical applications heavy


use is made of domain specific knowledge or problem conditions to produce better (faster
or more optimal solutions).
EXPERIMENT 7

AIM. ​Compare the different search algorithms on suitable data set.


THEORY.
Hill Climbing Algorithm​:
Hill Climbing is a technique to solve certain optimization problems. In this technique, we
start with a sub-optimal solution and the solution is improved repeatedly until some condition
is maximized. The idea of starting with a sub-optimal solution is compared to starting from
the base of the hill, improving the solution is compared to walking up the hill, and finally
maximizing some condition is compared to reaching the top of the hill. Hence, the hill
climbing technique can be considered as the following phases −
● Constructing a sub-optimal solution obeying the constraints of the problem
● Improving the solution step-by-step
● Improving the solution until no more improvement is possible
Hill Climbing technique is mainly used for solving computationally hard problems. It looks
only at the current state and immediate future state. Hence, this technique is memory efficient
as it does not maintain a search tree.

Best First Search​:


Best-first search is a search algorithm which explores a graph by expanding the most
promising node chosen according to a specified rule. Best-first search estimates the promise
of node n by a heuristic evaluation function f(n) which, in general, may depend on the
description of n, the description of the goal, the information gathered by the search up to that
point, and most important, on any extra knowledge about the problem domain.

A* Algorithm​:
A* is a search algorithm which explores a graph by expanding the most promising node
chosen according to a specified rule. Best-first search estimates the promise of node n by a
heuristic evaluation function f(n) which, in general, may depend on the description of n, the
description of the goal, the information gathered by the search up to that point, and most
important, on any extra
knowledge about the problem domain and also on g(n) which depends on the distance of the
current state from the initial state. This g(n) term is not present in Best First Search.

ALGORITHM.

Hill Climbing Algorithm​:


currentNode = startNode;
loop do
L = NEIGHBORS(currentNode);
nextEval = -INF;
nextNode = NULL;
for all x in L
if (EVAL(x) > nextEval)
nextNode = x;
nextEval = EVAL(x);
if nextEval <= EVAL(currentNode)
//Return current node since no better neighbors exist
return currentNode;
currentNode = nextNode;

Best First Search​:


Using a greedy algorithm, expand the first successor of the parent. After a successor is
generated:
1. If the successor's heuristic is better than its parent, the successor is set at the front of the
queue (with the parent reinserted directly behind it), and the loop restarts.
2. Else, the successor is inserted into the queue (in a location determined by its
heuristic value). The procedure will evaluate the remaining successors (if any) of the parent.

A* Algorithm​:
function A*(start, goal)
closedSet := {}
openSet := {start}
cameFrom := an empty map
gScore := map with default value of Infinity
gScore[start] := 0
fScore := map with default value of Infinity
fScore[start] := heuristic_cost_estimate(start, goal)
while openSet is not empty
current := the node in openSet having the lowest fScore[] value
if current = goal
return reconstruct_path(cameFrom, current)
openSet.Remove(current)
closedSet.Add(current)
for each neighbor of current
if neighbor in closedSet
continue
if neighbor not in openSet
openSet.Add(neighbor)
tentative_gScore := gScore[current] + dist_between(current, neighbor)
if tentative_gScore >= gScore[neighbor]
continue // This is not a better path.
cameFrom[neighbor] := current
gScore[neighbor] := tentative_gScore
fScore[neighbor] := gScore[neighbor] + heuristic_cost_estimate(neighbor, goal)
return failure
function reconstruct_path(cameFrom, current)
total_path := [current]
while current in cameFrom.Keys:
current := cameFrom[current]
total_path.append(current)
return total_path
DISCUSSION.
Hill Climbing Best First Algorithm A* Algorithm
Visited nodes are not Visited Nodes are stored Visited Nodes are stored
stored here. here. here.
Only compares the Compares the neighbours of Compares the neighbours of
neighbours of the current all open nodes. all open nodes.
node.
Compares Heuristic Compares Heuristic Value. Compares Heuristic Value
Value. + Value of effort to reach
that node
F(n) = f(n) F(n) = f(n) F(n) = f(n) + g(n)
It may or may not find a It will always find a solution, It will always find a
solution. if there exists one. solution, if there exists
one.
It is a fast algorithm. Does not take into account Does not work for And-Or
the effort to reach that node. graphs.
EXPERIMENT 8(a)

AIM. ​To write prolog program to compute factorial of a number.

THEORY.
In mathematics, the factorial of a non-negative integer n, denoted by n! is the product of all
positive integers less than or equal to n. For example,

4! =4*3*2*1=24 The value of 0! is 1, according to the convention for an empty product

ALGORITHM.
1. ​Start
2. Read: Take input N
3. If N == 0 then Print: Fact = 1 and Exit
4. Call FACTORIAL (Fact, N – 1)
5. Set_fact = N*Fact
6. Return

CODE.
factorial(0, 1).

factorial(N, F) :-

N > 0,

N2 is N -1,

factorial(N2, R),

F is R * N.

OUTPUT.
DISCUSSION.
This program consists of two ​clauses​. The first clause is a unit clause, having no body. The
second is a rule, because it does have a ​body​. The body of the second clause is on the right
side of the ‘: -' which can be read as "if". The body consists of literals separated by commas
',' each of which can be read as "and". The head of a clause is the whole clause if the clause
is a unit clause, otherwise the ​head​ of a clause is the part appearing to the left of the colon
in ‘: -'.

LEARNING.
The factorial operation is encountered in many areas of mathematics, notably in
combinatorics, algebra, and mathematical analysis. Its most basic occurrence is the fact
that there is n! ways to arrange n distinct objects into a sequence (i.e., permutations of
the set of objects).
EXPERIMENT 8(b)

AIM. ​To write prolog program to compute area and circumference of a circle

ALGORITHM.
1. Start
2. Read: Take input radius R
3. If R<0, Exit.
4. Set A = 3.14*R*R
5. Set C = 6.28*R
6. Display Area and Circumference of the Circle

CODE.
func(R):-

R>0,

A is 3.14*R*R,

C is 6.28*R,

write('The Area is '),

write(A),

nl,

write('The circumference is '),

write(C).

OUTPUT.
DISCUSSION.
Program logic is expressed in terms of relations, and a computation is initiated by
running a query over these relations. Relations and queries are constructed using Prolog's
single data type, the term. Relations are defined by clauses. Given a query, the Prolog
engine attempts to find a resolution refutation of the negated query.

LEARNING.
The circumference of a circle is the edge or rim of a circle itself. It is the equivalent of
'perimeter' for a circle.

Potrebbero piacerti anche