Anda di halaman 1dari 45

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 production 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
In 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.

Finding and 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-

Findings and learning

The magic square logic can be used for any size of grid because it allows fast
checking of winning possibilities and in making the next move, after successfully
implementing both the approaches.
EXPERIMENT 2
Aim- To implement water jig 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.

Finding and 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.
Finding and 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;

Result/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.
Finding and 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:

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

Step 2: Get all the possible states in which puzzle can be.

Step 3: Add these new states in open state list

Step 4: Add processed sate in the closed list

Step 5: Pick the next state which has the lowest cost in the open list

Step 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).

Step 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";
}
}
Result/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.

Finding and Learning: The A* algorithm can be used to solve 8-Puzzle problem
efficiently
EXPERIMENT 5
Aim- To solve cryptarithmatic 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.

Findings and 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-
Step 1: Place the starting node into OPEN.
Step 2: Compute the most promising solution tree say T0.
Step 3: Select a node n that is both on OPEN and a member of T0. Remove it from
OPEN and place it in CLOSE
Step 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.
Step 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.
Step 6: Expand n. Find all its successors and find their h (n) value, push them into
OPEN.
Step 7: Return to Step 2.
Step 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()!=true){
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,pathCost)); 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.
Findings and 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 Visited Nodes are Visited Nodes are
not stored here. stored here. stored here.
Only compares the Compares the Compares the
neighbours of the neighbours of all open neighbours of all open
current nodes. nodes.
node.
Compares Compares Heuristic Value. Compares Heuristic
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 It will always find a It will always find a
a solution. solution, if there exists solution, if there
one. exists one.
It is a fast algorithm. Does not take into Does not work for And-
account the effort to Or graphs.
reach that node.
EXPERIMENT 8(a)
Aim: To write prolog program to compute factorial of a no.
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=24The value of 0! is 1, according to the convention for an empty product
Algorithm-
Step 1: Start
Step 2: Read: Take input N
Step 3: If N == 0 then Print: Fact = 1 and Exit
Step 4: Call FACTORIAL (Fact, N – 1)
Step 5: Set Fact = N*Fact
Step 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 ‘: -'.
Findings and 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-
Step 1: Start
Step 2: Read: Take input radius R.
Step 3: If R<0, Exit.
Step 4: Set A = 3.14*R*R
Step 5: Set C = 6.28*R
Step 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.
Findings and learning- The circumference of a circle is the edge or rim of a circle
itself. It is the equivalent of 'perimeter' for a circle.

Anda mungkin juga menyukai