Download as txt, pdf, or txt
Download as txt, pdf, or txt
You are on page 1of 26

EXERCISE NO: 1A IMPLEMENT BFS AND DFS FOR WATER JUG PROBLEM

AIM: To implement BFS and DFS For Water Jug Problem


WATER JUG PROBLEM:
EXPLANATION:
You are given an m liter jug and a n liter jug. 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 liters of water where d is less than n.
(X, Y) corresponds to a state where X refers to the amount of water in Jug1 and Y
refers to the amount of water in Jug2 Determine the path from the initial
state (xi, yi) to the final state (xf, yf), where (xi, yi) is (0, 0) which
indicates both Jugs are initially empty and (xf, yf) indicates a state which could
be (0, d) or (d, 0).
The operations you can perform are:
1. Empty a Jug, (X, Y)->(0, Y) Empty Jug 1
2. Fill a Jug, (0, 0)->(X, 0) Fill Jug 1
3. Pour water from one jug to the other until one of the jugs is either empty or
full, (X, Y) -> (X-d, Y+d)
Examples:
Input : 4 3 2
Output : {(0, 0), (0, 3), (3, 0), (3, 3), (4, 2), (0, 2)}
We have discussed the optimal solution in The Two Water Jug Puzzle. In this a BFS
based solution is discussed.
Here, we keep exploring all the different valid cases of the states of water in the
jug simultaneously until and unless we reach the required target water.
As provided in the problem statement, at any given state we can do either of the
following operations:
1. Fill a jug
2. Empty a jug
3. Transfer water from one jug to another until either of them gets completely
filled or empty.

Running of the algorithm:


We start at an initial state in the queue where both the jugs are empty. We then
continue to explore all the possible intermediate states derived from the current
jug state using the operations provided.
We also, maintain a visited matrix of states so that we avoid revisiting the same
state of jugs again and again.

Cases Jug 1 Jug 2 Is Valid

Case 1
Fill it
Empty it

Case 2
Empty it
Fill it
Case 3 Fill it Fill it Redundant case

Case 4
Empty it
Empty it Already visited (Initial State)

Case 5
Unchanged
Fill it
Case 6
Fill it
Unchanged

Case 7
Unchanged
Empty

Case 8
Empty
Unchanged

Case 9 Transfer water from this Transfer water into this


Case 10 Transfer water into this Transfer water from this

From the table above, we can observe that the state where both the jugs are filled
is redundant as we won’t be able to continue ahead / do anything with this state in
any possible way.
So, we proceed, keeping in mind all the valid state cases (as shown in the table
above) and we do a BFS on them.
In the BFS, we firstly skip the states which was already visited or if the amount
of water in either of the jugs exceeded the jug quantity.
If we continue further, then we firstly mark the current state as visited and check
if in this state, if we have obtained the target quantity of water in either of the
jugs, we can empty the other jug and return the current state’s entire path.

But, if we have not yet found the target quantity, we then derive the intermediate
states from the current state of jugs i.e. we derive the valid cases, mentioned in
the table above (go through the code once if you have some confusion).
We keep repeating all the above steps until we have found our target or there are
no more states left to proceed with.
PROGRAM FOR IMPLEMENTING BFS:

from collections import deque

def BFS(a, b, target):

# Map is used to store the states, every # state is hashed to binary value to
# indicate either that state is visited # before or not
m = {}
isSolvable = False path = []

# Queue to maintain states q = deque()

# Initialing with initial state q.append((0, 0))

while (len(q) > 0):

# Current state u = q.popleft()

#q.pop() #pop off used state

# If this state is already visited


if ((u[0], u[1]) in m):
continue

# Doesn't met jug constraints


if ((u[0] > a or u[1] > b or
u[0] < 0 or u[1] < 0)):
continue

# Filling the vector for constructing # the solution path path.append([u[0], u[1]])

# Marking current state as visited m[(u[0], u[1])] = 1


# If we reach solution state, put ans=1
if (u[0] == target or u[1] == target): isSolvable = True
if (u[0] == target):
if (u[1] != 0):
# Fill final state path.append([u[0], 0])
else:
if (u[0] != 0):
# Fill final state path.append([0, u[1]])

# Print the solution path sz = len(path)


for i in range(sz): print("(", path[i][0], ",",
path[i][1], ")")
break

# If we have not reached final state # then, start developing intermediate # states
to reach solution state q.append([u[0], b]) # Fill Jug2 q.append([a, u[1]]) # Fill
Jug1

for ap in range(max(a, b) + 1):

# Pour amount ap from Jug2 to Jug1 c = u[0] + ap


d = u[1] - ap

# Check if this state is possible or not


if (c == a or (d == 0 and d >= 0)): q.append([c, d])

# Pour amount ap from Jug 1 to Jug2 c = u[0] - ap


d = u[1] + ap

# Check if this state is possible or not


if ((c == 0 and c >= 0) or d == b): q.append([c, d])

# Empty Jug2 q.append([a, 0])

# Empty Jug1 q.append([0, b])

# No, solution exists if ans=0


if (not isSolvable):
print ("No solution") # Driver code
if name == ' main ': Jug1, Jug2, target = 4, 3, 2 print("Path from initial
state "
"to solution state ::") BFS(Jug1, Jug2, target)
OUTPUT:
Path of states of jugs followed is :
0 , 0
0 , 3
3 , 0
3 , 3
4 , 2
0 , 2
TIME COMPLEXITY: O(n*m).
SPACE COMPLEXITY: O(n*m). Where n and m are the quantity of jug1 and jug2
respectively.

b) DEPTH FIRST SEARCH OR DFS FOR A GRAPH

Depth First Traversal (or Search) for a graph is similar to Depth First Traversal
of a tree. The only catch here is, unlike trees, graphs may contain cycles (a node
may be visited twice
). To avoid processing a node more than once, use a boolean visited array.
Example:
Input: n = 4, e = 6
0 -> 1, 0 -> 2, 1 -> 2, 2 -> 0, 2 ->
3, 3 -> 3
Output: DFS from vertex 1 : 1 2 0
3

Explanation:
DFS :

Input: n = 4, e = 6
2 -> 0, 0 -> 2, 1 -> 2, 0 -> 1, 3 ->
3, 1 -> 3
Output: DFS from vertex 2 : 2 0 1 3
Explanation:

Prerequisites: for all applications of Depth First Traversal


PROGRAM:

# Python3 program to print DFS traversal # from a given given graph


from collections import defaultdict
# This class represents a directed graph using # adjacency list representation
class Graph:
# Constructor
def init (self):
# default dictionary to store graph self.graph = defaultdict(list)

# function to add an edge to graph


def addEdge(self, u, v): self.graph[u].append(v)

# A function used by DFS


def DFSUtil(self, v, visited):
# Mark the current node as visited # and print it
visited.add(v)
print(v, end=' ')
# Recur for all the vertices # adjacent to this vertex
for neighbour in self.graph[v]:
if neighbour not in visited: self.DFSUtil(neighbour, visited)

# The function to do DFS traversal. It uses # recursive DFSUtil()


def DFS(self, v):
# Create a set to store visited vertices visited = set()
# Call the recursive helper function # to print DFS traversal self.DFSUtil(v,
visited)
# Driver code
# Create a graph given # in the above diagram g = Graph() g.addEdge(0, 1)
g.addEdge(0, 2)
g.addEdge(1, 2)
g.addEdge(2, 0)
g.addEdge(2, 3)
g.addEdge(3, 3)
print("Following is DFS from (starting from vertex 2)") g.DFS(2)
Output: Following is Depth First Traversal (starting from vertex 2)
2 0 1 3
Complexity Analysis:

• Time complexity: O(V + E), where V is the number of vertices and E is the
number of edges in the graph.
• Space Complexity: O(V), since an extra visited array of size V is required.

Handling A Disconnected Graph:


• Solution:
This will happen by handling a corner case. The above code
traverses only the vertices reachable from a given source vertex. All the vertices
may not be reachable from a given vertex, as in a Disconnected graph. To do a
complete DFS traversal of such graphs, run DFS from all unvisited nodes after a
DFS.
The recursive function remains the same.
Algorithm:
0. Create a recursive function that takes the index of the node and a visited
array.
1. Mark the current node as visited and print the node.
2. Traverse all the adjacent and unmarked nodes and call the recursive function
with the index of the adjacent node.
3. Run a loop from 0 to the number of vertices and check if the node is
unvisited in the previous DFS, call the recursive function with the current node.

PROGRAM:
'''Python program to print DFS traversal for complete graph'''
from collections import defaultdict
# this class represents a directed graph using adjacency list representation
class Graph:
# Constructor
def init (self):
# default dictionary to store graph self.graph = defaultdict(list)
# Function to add an edge to graph
def addEdge(self, u, v): self.graph[u].append(v)
# A function used by DFS
def DFSUtil(self, v, visited):
# Mark the current node as visited and print it visited.add(v)
print(v,end=" ")
# recur for all the vertices adjacent to this vertex
for neighbour in self.graph[v]:
if neighbour not in visited: self.DFSUtil(neighbour, visited)
# The function to do DFS traversal. It uses recursive DFSUtil
def DFS(self):
# create a set to store all visited vertices visited = set()
# call the recursive helper function to print DFS traversal starting from all #
vertices one by one

for vertex in self.graph:


if vertex not in visited: self.DFSUtil(vertex, visited)
# Driver code
# create a graph given in the above diagram print("Following is Depth First
Traversal \n") g = Graph()
g.addEdge(0, 1)
g.addEdge(0, 2)
g.addEdge(1, 2)
g.addEdge(2, 0)
g.addEdge(2, 3)
g.addEdge(3, 3) g.DFS()

OUTPUT:
Following is Depth First Traversal 0 1 2 3 9

COMPLEXITY ANALYSIS:
• TIME COMPLEXITY: O(V + E), where V is the number of vertices and E is the
number of edges in the graph.
• SPACE COMPLEXITY: O(V), since an extra visited array of size V is required.

RESULT: Hence BFS and DFS algorithm is implemented to solve water jug problem

EXERCISE: 1 B IMPLEMENT A* AND MEMORY BOUNDED A* SEARCH

AIM: To Implement A* and Memory Bounded A* Search in python A Star Search Algorithm
with a solved numerical

Numbers written on edges represent the distance between nodes. Numbers written on
nodes represent the heuristic value.

PSEUDOCODE:

Given the graph, find the cost-effective path from A to G. That is A is the source
node and G is the goal node.

Now from A, we can go to point B or E, so we compute f(x) for each of them, A → B =


g(B) + h(B) = 2 + 6 = 8
A → E = g(E) + h(E) = 3 + 7 = 10

Since the cost for A → B is less, we move forward with this path and compute the
f(x) for the children nodes of B.

Now from B, we can go to point C or G, so we compute f(x) for each of them, A → B →


C = (2 + 1) + 99= 102
A → B → G = (2 + 9 ) + 0 = 11

Here the path A → B → G has the least cost but it is still more than the cost of A
→ E, thus we explore this path further.

Now from E, we can go to point D, so we compute f(x), A → E → D = (3 + 6) + 1 = 10


Comparing the cost of A → E → D with all the paths we got so far and as this cost
is least of all we move forward with this path.

Now compute the f(x) for the children of D A → E → D → G = (3 + 6 + 1) +0 = 10


Now comparing all the paths that lead us to the goal, we conclude that A → E → D →
G is the most cost-effective path to get from A to G.

Implementation of A Star Search Algorithm in python

PROGRAM
def aStarAlgo(start_node, stop_node):

open_set = set(start_node)

closed_set = set()
g = {} #store distance from starting node

parents = {} # parents contains an adjacency map of all nodes

#distance of starting node from itself is zero

g[start_node] = 0

#start_node is root node i.e it has no parent nodes

#so start_node is set to its own parent node

parents[start_node] = start_node

while len(open_set) > 0:

n = None

#node with lowest f() is found

for v in open_set:

if n == None or g[v] + heuristic(v) < g[n] + heuristic(n):

n = v

if n == stop_node or Graph_nodes[n] == None:

pass

else:

for (m, weight) in get_neighbors(n):

#nodes 'm' not in first and last set are added to first

#n is set its parent

if m not in open_set and m not in closed_set:

open_set.add(m)

parents[m] = n

g[m] = g[n] + weight

#for each node m,compare its distance from start i.e g(m) to the

#from start through n node

else:

if g[m] > g[n] + weight:

#update g(m)

g[m] = g[n] + weight


#change parent of m to n

parents[m] = n

#if m in closed set,remove and add to open

if m in closed_set:

closed_set.remove(m)

open_set.add(m)

if n == None:

print('Path does not exist!')

return None

# if the current node is the stop_node

# then we begin reconstructin the path from it to the start_node

if n == stop_node:

path = []

while parents[n] != n:

path.append(n)

n = parents[n]

path.append(start_node)

path.reverse()

print('Path found: {}'.format(path))

return path

# remove n from the open_list, and add it to closed_list

# because all of his neighbors were inspected

open_set.remove(n)

closed_set.add(n)

print('Path does not exist!')

return None

#define fuction to return neighbor and its distance

#from the passed node

def get_neighbors(v):
'I': 1,

'J': 0

return H_dist[n]

#Describe your graph here

Graph_nodes = {

'A': [('B', 6), ('F', 3)],

'B': [('A', 6), ('C', 3), ('D', 2)],

'C': [('B', 3), ('D', 1), ('E', 5)],

'D': [('B', 2), ('C', 1), ('E', 8)],

'E': [('C', 5), ('D', 8), ('I', 5), ('J', 5)],

'F': [('A', 3), ('G', 1), ('H', 7)],

'G': [('F', 1), ('I', 3)],

'H': [('F', 7), ('I', 2)],

'I': [('E', 5), ('G', 3), ('H', 2), ('J', 3)],

aStarAlgo('A', 'J')

Output:

Path found: ['A', 'F', 'G', 'I', 'J']

#for simplicity we ll consider heuristic distances given #and this function returns
heuristic distance for all nodes def heuristic(n):
H_dist = { 'A': 11,
'B': 6,

'C': 99,

'D': 1,

'E': 7,

'G': 0,
}

return H_dist[n]

#Describe your graph here Graph_nodes = {


'A': [('B', 2), ('E', 3)],
B': [('A', 2), ('C', 1), ('G', 9)],

'C': [('B', 1)],

'D': [('E', 6), ('G', 1)],

'E': [('A', 3), ('D', 6)],

'G': [('B', 9), ('D', 1)]

aStarAlgo('A', 'G')

OUTPUT:

Path found: ['A', 'E', 'D', 'G']

RESULT: Hence A* and memory bounded A* algorithm is implemented successfully

EXP NO 2A: IMPLEMENT MIN-MAX ALGORITHM FOR GAME PLAYING

AIM: To Implement Min-Max Algorithm for Game Playing in Python

Minimax is a kind of backtracking algorithm that is used in decision making and


game theory to find the optimal move for a player, assuming that your opponent also
plays optimally. It is widely used in two player turn-based games such as Tic-Tac-
Toe, Backgammon, Mancala, Chess, etc.

In Minimax the two players are called maximizer and minimizer. The maximizer tries
to get the highest score possible while the minimizer tries to do the opposite and
get the lowest score possible.

Every board state has a value associated with it. In a given state if the maximizer
has upper hand then, the score of the board will tend to be some positive value. If
the minimizer has the upper hand in that board state then it will tend to be some
negative value. The values of the board are calculated by some heuristics which are
unique for every type of game.
Example:
Consider a game which has 4 final states and paths to reach final state are from
root to 4 leaves of a perfect binary tree as shown below. Assume you are the
maximizing player and you get the first chance to move, i.e., you are at the root
and your opponent at next level. Which move you would make as a maximizing player
considering that your opponent also plays optimally?

Since this is a backtracking based algorithm, it tries all possible moves, then
backtracks and makes a decision.
• Maximizer goes LEFT: It is now the minimizers turn. The minimizer now has a
choice between 3 and 5. Being the minimizer it will definitely choose the least
among both, that is 3
• Maximizer goes RIGHT: It is now the minimizers turn. The minimizer now has a
choice between 2 and 9. He will choose 2 as it is the least among the two values.
Being the maximizer you would choose the larger value that is 3. Hence the optimal
move for the maximizer is to go LEFT and the optimal value is 3.

Note: Even though there is a value of 9 on the right subtree, the minimizer will
never pick that. We must always assume that our opponent plays optimally.
Below is the implementation for the same.
PROGRAM:

# A simple Python3 program to find # maximum score that


# maximizing player can get
import math

def minimax (curDepth, nodeIndex, maxTurn, scores, targetDepth):

# base case : targetDepth reached


if (curDepth == targetDepth):
return scores[nodeIndex]

if (maxTurn):
return max(minimax(curDepth + 1, nodeIndex * 2, False, scores, targetDepth),
minimax(curDepth + 1, nodeIndex * 2 + 1, False, scores, targetDepth))

else:
return min(minimax(curDepth + 1, nodeIndex * 2, True, scores, targetDepth),
minimax(curDepth + 1, nodeIndex * 2 + 1, True, scores, targetDepth))

# Driver code
scores = [3, 5, 2, 9, 12, 5, 23, 23]

treeDepth = math.log(len(scores), 2)

print("The optimal value is : ", end = "") print(minimax(0, 0, True, scores,


treeDepth))

# This code is contributed # by rootshadow

OUTPUT:
The optimal value is: 12
a) ALPHA-BETA PRUNING

Alpha-Beta pruning is not actually a new algorithm, rather an optimization


technique for minimax algorithm. It reduces the computation time by a huge factor.
This allows us to search much faster and even go into deeper levels in the game
tree. It cuts off branches in the game tree which need not be searched because
there already exists a better move available. It is called Alpha-Beta pruning
because it passes 2 extra parameters in the minimax function, namely
alpha and beta. Let’s define the parameters alpha
and beta. Alpha is the best value that the maximizer currently can
guarantee at that level or above. Beta is the best value that the minimizer
currently can guarantee at that level or above. PSEUDOCODE :

function minimax(node, depth, isMaximizingPlayer, alpha, beta):

if node is a leaf node :


return value of the node

if isMaximizingPlayer : bestVal = -INFINITY for each child node :


value = minimax(node, depth+1, false, alpha, beta) bestVal = max( bestVal, value)
alpha = max( alpha, bestVal)
if beta <= alpha:
break return bestVal
else :
bestVal = +INFINITY
for each child node :

value = minimax(node, depth+1, true, alpha, beta) bestVal = min( bestVal, value)
beta = min( beta, bestVal)
if beta <= alpha:
break return bestVal

// Calling the function for the first time.


minimax(0, 0, true, -INFINITY, +INFINITY)
Let’s make above algorithm clear with an example.

• The initial call starts from A. The value of alpha here is -INFINITY and the
value of beta is +INFINITY. These values are passed down to subsequent nodes in the
tree. At A the maximizer must choose max of B and C, so A calls B first
• At B it the minimizer must choose min of D and E and hence calls D first.
• At D, it looks at its left child which is a leaf node. This node returns a
value of 3. Now the value of alpha at D is max( -INF, 3) which is 3.
• To decide whether its worth looking at its right node or not, it checks the
condition beta<=alpha. This is false since beta = +INF and alpha = 3. So it
continues the search.
• D now looks at its right child which returns a value of 5.At D, alpha =
max(3, 5) which is 5. Now the value of node D is 5
• D returns a value of 5 to B. At B, beta = min( +INF, 5) which is 5. The
minimizer is now guaranteed a value of 5 or lesser. B now calls E to see if he can
get a lower value than 5.
• At E the values of alpha and beta is not -INF and +INF but instead -INF and 5
respectively, because the value of beta was changed at B and that is what B passed
down to E
• Now E looks at its left child which is 6. At E, alpha = max(-INF, 6) which is
6. Here the condition becomes true. beta is 5 and alpha is 6. So beta<=alpha is
true. Hence it breaks and E returns 6 to B
• Note how it did not matter what the value of E‘s right child is. It could
have been
+INF or -INF, it still wouldn’t matter, We never even had to look at it because the
minimizer was guaranteed a value of 5 or lesser. So as soon as the maximizer saw
the 6 he knew the minimizer would never come this way because he can get a 5 on the
left side of B. This way we dint have to look at that 9 and hence saved computation
time.
• E returns a value of 6 to B. At B, beta = min( 5, 6) which is 5.The value of
node B is also 5
So far this is how our game tree looks. The 9 is crossed out because it was never
computed.

• B returns 5 to A. At A, alpha = max( -INF, 5) which is 5. Now the maximizer


is guaranteed a value of 5 or greater. A now calls C to see if it can get a higher
value than 5.
• At C, alpha = 5 and beta = +INF. C calls F
• At F, alpha = 5 and beta = +INF. F looks at its left child which is a 1.
alpha = max( 5, 1) which is still 5.
• F looks at its right child which is a 2. Hence the best value of this node is
2. Alpha still remains 5
• F returns a value of 2 to C. At C, beta = min( +INF, 2). The condition beta
<= alpha becomes true as beta = 2 and alpha = 5. So it breaks and it does not even
have to compute the entire sub-tree of G.
• The intuition behind this break off is that, at C the minimizer was
guaranteed a value of 2 or lesser. But the maximizer was already guaranteed a value
of 5 if he choose B. So why would the maximizer ever choose C and get a value less
than 2 ? Again you can see that it did not matter what those last 2 values were. We
also saved a lot of computation by skipping a whole sub tree.
• C now returns a value of 2 to A. Therefore the best value at A is max( 5, 2)
which is a 5.
• Hence the optimal value that the maximizer can get is 5

This is how our final game tree looks like. As you can see G has been crossed out
as it was never computed.

PROGRAM:

# Python3 program to demonstrate # working of Alpha-Beta Pruning

# Initial values of Alpha and Beta MAX, MIN = 1000, -1000


# Returns optimal value for current player #(Initially called for root and
maximizer)
def minimax(depth, nodeIndex, maximizingPlayer, values, alpha, beta):
# Terminating condition. i.e # leaf node is reached
if depth == 3:
return values[nodeIndex]
if maximizingPlayer: best = MIN
# Recur for left and right children
for i in range(0, 2):
val = minimax(depth + 1, nodeIndex * 2 + i, False, values, alpha, beta)
best = max(best, val) alpha = max(alpha, best) # Alpha Beta Pruning
if beta <= alpha:
break return best
else:
best = MAX
# Recur for left and # right children
for i in range(0, 2):
val = minimax(depth + 1, nodeIndex * 2 + i, True, values, alpha, beta)
best = min(best, val) beta = min(beta, best) # Alpha Beta Pruning if beta <= alpha:
break return best
# Driver Code
if name == " main ":
values = [3, 5, 6, 9, 1, 2, 0, -1]
print("The optimal value is :", minimax(0, 0, True, values, MIN, MAX))

OUTPUT :
The optimal value is : 5

RESULT: Hence Minimax algorithm for game playing (Alpha-Beta pruning) is


implemented successfully.

EXERCISE 3: K NEAREST NEIGHBOUR ALGORITHM


AIM: To perform K nearest neighbourhood algorithm

Supervised Learning :
It is the learning where the value or result that we want to predict is within the
training data (labeled data) and the value which is in data that we want to study
is known as Target or Dependent Variable or Response Variable.
All the other columns in the dataset are known as the Feature or Predictor
Variable or Independent Variable.
Supervised Learning is classified into two categories:
1. Classification: Here our target variable consists of the categories.
2. Regression: Here our target variable is continuous and we usually try to find
out the line of the curve.
As we have understood that to carry out supervised learning we need labeled data.
How we can get labeled data? There are various ways to get labeled data:
1. Historical labeled Data
2. Experiment to get data: We can perform experiments to generate labeled data
like A/B Testing.
3. Crowd-sourcing
Now it’s time to understand algorithms that can be used to solve supervised machine
learning problem. In this post, we will be using popular scikit-learn package.

This algorithm is used to solve the classification model problems. K-nearest


neighbor or K-NN algorithm basically creates an imaginary boundary to classify the
data. When new data points come in, the algorithm will try to predict that to the
nearest of the boundary line.

Therefore, larger k value means smother curves of separation resulting in less


complex models. Whereas, smaller k value tends to overfit the data and resulting in
complex models.

Note: It’s very important to have the right k-value when analyzing the dataset to
avoid overfitting and underfitting of the dataset.

# Import necessary modules


from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_iris
# Loading data
irisData = load_iris()
# Create feature and target arrays
X = irisData.data
y = irisData.target
# Split into training and test set
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size = 0.2, random_state=42)
knn = KNeighborsClassifier(n_neighbors=7)
knn.fit(X_train, y_train)
# Predict on dataset which model has not seen before
print(knn.predict(X_test))
In the example shown above following steps are performed:
1. The k-nearest neighbor algorithm is imported from the scikit-learn package.
2. Create feature and target variables.
3. Split data into training and test data.
4. Generate a k-NN model using neighbors value.
5. Train or fit the data into the model.
6. Predict the future.
We have seen how we can use K-NN algorithm to solve the supervised machine learning
problem. But how to measure the accuracy of the model?

Import necessary modules


from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_iris
# Loading data
irisData = load_iris()
# Create feature and target arrays
X = irisData.data
y = irisData.target
# Split into training and test set
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size = 0.2, random_state=42)
knn = KNeighborsClassifier(n_neighbors=7)
knn.fit(X_train, y_train)
# Calculate the accuracy of the model
print(knn.score(X_test, y_test))
Model Accuracy:
So far so good. But how to decide the right k-value for the dataset? Obviously, we
need to be familiar to data to get the range of expected k-value, but to get the
exact k-value we need to test the model for each and every expected k-value. Refer
to the example shown below.
# Import necessary modules
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_iris
import numpy as np
import matplotlib.pyplot as plt
irisData = load_iris()
# Create feature and target arrays
X = irisData.data
y = irisData.target
# Split into training and test set
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size = 0.2, random_state=42)
neighbors = np.arange(1, 9)
train_accuracy = np.empty(len(neighbors))
test_accuracy = np.empty(len(neighbors))
# Loop over K values
for i, k in enumerate(neighbors):
knn = KNeighborsClassifier(n_neighbors=k)
knn.fit(X_train, y_train)
# Compute training and test data accuracy
train_accuracy[i] = knn.score(X_train, y_train)
test_accuracy[i] = knn.score(X_test, y_test)
# Generate plot
plt.plot(neighbors, test_accuracy, label = 'Testing dataset Accuracy')
plt.plot(neighbors, train_accuracy, label = 'Training dataset Accuracy')
plt.legend()
plt.xlabel('n_neighbors')
plt.ylabel('Accuracy')
plt.show()

OUTPUT:

Result:
k-nearest neighbor algorithm which is used to solve supervised machine learning
problems. We also explored measuring the accuracy of the model.
Using the k-nearest neighbor algorithm we fit the historical data (or train the
model) and predict the future.

EXERCISE 4: PROGRAM TO FIND NUMBER OF STEPS TO SOLVE 8-PUZZLE IN PYTHON

AIM: To Write a Program to Find Number of Steps To Solve 8-Puzzle In Python

We have a 3x3 board of where all numbers are in range 0 to 8 and no repeating
numbers are there. Now, we can swap the 0 with one of its 4 neighbors, and we are
trying to solve it to get all arranged sequence, we have to find minimum number of
steps required to reach the goal.
So, if the input is like

3 1 2
4 7 5
6 8 0

then the output will be 4

PSEUDOCODE

• Define a function find_next() . This will take node


• moves := a map defining moves as a list corresponding to each value {0: [1,
3],1: [0, 2, 4],2: [1, 5],3: [0, 4, 6],4: [1, 3, 5, 7],5: [2, 4, 8],6: [3, 7],7:
[4, 6, 8],8: [5, 7],}
• results := a new list
• pos_0 := first value of node
• for each move in moves[pos_0], do
o new_node := a new list from node
o swap new_node[move] and new_node[pos_0]
o insert a new tuple from new_node at the end of results
• return results
• Define a function get_paths() . This will take dict
• cnt := 0
• Do the following infinitely, do
o current_nodes := a list where value is same as cnt
o if size of current_nodes is same as 0, then
 return -1

o for each node in current_nodes, do


 next_moves := find_next(node)
 for each move in next_moves, do
 if move is not present in dict, then
 dict[move] := cnt + 1
 if move is same as (0, 1, 2, 3, 4, 5, 6, 7, 8) , then
 return cnt + 1
 cnt := cnt + 1
• From the main method do the following:
• dict := a new map, flatten := a new list
• for i in range 0 to row count of board, do
o flatten := flatten + board[i]
• flatten := a copy of flatten
• dict[flatten] := 0
• if flatten is same as (0, 1, 2, 3, 4, 5, 6, 7, 8) , then
o return 0
• return get_paths(dict)
Let us see the following implementation to get better understanding PROGRAM:

def get_paths(self, dict):


cnt = 0 while True:
current_nodes = [x for x in dict if dict[x] == cnt] if len(current_nodes) == 0:
return -1

for node in current_nodes: next_moves = self.find_next(node) for move in


next_moves:
if move not in dict: dict[move] = cnt + 1
if move == (0, 1, 2, 3, 4, 5, 6, 7, 8):
return cnt + 1 cnt += 1

def find_next(self, node): moves = {


0: [1, 3],
1: [0, 2, 4],
2: [1, 5],
3: [0, 4, 6],
4: [1, 3, 5, 7],
5: [2, 4, 8],
6: [3, 7],
7: [4, 6, 8],
8: [5, 7],
}

results = []
pos_0 = node.index(0)
for move in moves[pos_0]:
new_node = list(node)
new_node[move], new_node[pos_0] = new_node[pos_0], new_node[move]
results.append(tuple(new_node))

return results ob = Solution() matrix = [


[3, 1, 2],
[4, 7, 5],
[6, 8, 0]

Input

matrix = [ [3, 1, 2],


[4, 7, 5],
[6, 8, 0] ]

Ouput

b) 8-QUEENS PROBLEM

AIM: To Write a program to solve 8 queens problem

The 8 queens problem is a problem in swhich we figure out a way to put 8 queens on
an 8×8 chessboard in such a way that no queen should attack the other. For basic
info about the queen in a chess game, you should know that a queen can move in any
direction ( vertically, horizontally, and diagonally) and to any number of places.
In the figure below you can see how to place 4 queens on a 4×4 chessboard.

Similarly, we have to place 8 queens on an 8×8 chessboard. We will use backtracking


to solve this interesting problem(puzzle).

What is Backtracking?

Backtracking the solution of the problem depends on the previous steps taken. We
take a step and then analyze it that whether it will give the correct answer or
not? and if not, then we move back and change the previous step.
##TAKING NUMBER OF QUEENS AS INPUT FROM USER
PROGRAM:
print ("Enter the number of queens") N = int(input())
# here we create a chessboard
# NxN matrix with all elements set to 0 board = [[0]*N for _ in range(N)]
def attack(i, j):
#checking vertically and horizontally for k in range(0,N):
if board[i][k]==1 or board[k][j]==1: return True
#checking diagonally for k in range(0,N):
for l in range(0,N):
if (k+l==i+j) or (k-l==i-j): if board[k][l]==1:
return True return False
def N_queens(n): if n==0:
return True
for i in range(0,N): for j in range(0,N):
if (not(attack(i,j))) and (board[i][j]!=1): board[i][j] = 1
if N_queens(n-1)==True: return True
board[i][j] = 0 return False
N_queens(N) for i in board: print (i)

Result:

Enter the number of queens 8

Output

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

Python program to solve N Queen # Problem using backtracking

global N N = 4

def printSolution(board):
for i in range(N):
for j in range(N):
print (board[i][j],end=' ')
print()
# A utility function to check if a queen can
# be placed on board[row][col]. Note that this # function is called when "col"
queens are
# already placed in columns from 0 to col -1. # So we need to check only left side
for
# attacking queens
def isSafe(board, row, col):

# Check this row on left side


for i in range(col):
if board[row][i] == 1:
return False

# Check upper diagonal on left side


for i, j in zip(range(row, -1, -1), range(col, -1, -1)): if board[i][j] == 1:
return False

# Check lower diagonal on left side


for i, j in zip(range(row, N, 1), range(col, -1, -1)): if board[i][j] == 1:
return False

return True

def solveNQUtil(board, col):


# base case: If all queens are placed # then return true
if col >= N:
return True

# Consider this column and try placing # this queen in all rows one by one
for i in range(N):

if isSafe(board, i, col):
# Place this queen in board[i][col] board[i][col] = 1

# recur to place rest of the queens


if solveNQUtil(board, col + 1) == True:
return True

# If placing queen in board[i][col # doesn't lead to a solution, then # queen from


board[i][col]
board[i][col] = 0
# if the queen can not be placed in any row in # this column col then return false
return False

# This function solves the N Queen problem using # Backtracking. It mainly uses
solveNQUtil() to
# solve the problem. It returns false if queens # cannot be placed, otherwise
return true and # placement of queens in the form of 1s.
# note that there may be more than one
# solutions, this function prints one of the # feasible solutions.
def solveNQ():
board = [ [0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]
]

if solveNQUtil(board, 0) == False: print ("Solution does not exist") return False

printSolution(board)
return True

# driver program to test above function solveNQ()


Output:
0 0 1 0
1 0 0 0
0 0 0 1
0 1 0 0
C) CRYPTARITHMETIC
Given an array of strings, arr[] of size N and a string S, the task is to find if
it is possible to map integers value in the range [0, 9] to every alphabet that
occurs in the strings, such that the sum obtained after summing the numbers formed
by encoding all strings in the array is equal to the number formed by the string S.
Examples:
Input: arr[][] = {“SEND”, “MORE”}, S = “MONEY”
Output: Yes
PSEUDOCODE:
One of the possible ways is:
1. Map the characters as the following, ‘S’→ 9, ‘E’→5, ‘N’→6, ‘D’→7, ‘M’→1,
‘O’→0, ‘R’→8, ‘Y’→2.
2. Now, after encoding the strings “SEND”, “MORE”, and “MONEY”, modifies to
9567, 1085 and 10652 respectively.
3. Thus, the sum of the values of “SEND” and “MORE” is equal to (9567+1085 =
10652), which is equal to the value of the string “MONEY”.
Therefore, print “Yes”.
Input: arr[][] = {“SIX”, “SEVEN”, “SEVEN”}, S = “TWENTY”
Output: Yes
PSEUDOCODE:
One of the possible ways is:
1. Map the characters as the following, ‘S’→ 6, ‘I’→5, ‘X’→0, ‘E’→8, ‘V’→7,
‘N’→2, ‘T’→1, ‘W’→’3’, ‘Y’→4.
2. Now, after encoding the strings “SIX”, “SEVEN”, and “TWENTY”, modifies to
650, 68782 and 138214 respectively.
3. Thus, the sum of the values of “SIX”, “SEVEN”, and “SEVEN” is equal to (650+
68782+ 68782 = 138214), which is equal to the value of the string “TWENTY”.
Therefore, print “Yes”.
Set 1 of this article has been discussed here in which the array of strings is of
size 2. Approach: The given problem can be solved using Backtracking. Follow the
steps below to solve the problem:
• Initialize three, arrays say mp[26], Hash[26], and CharAtfront[26] to store
the mapped value of the alphabet, the sum of the position values of an alphabet in
every string, and if a character is at the starting index of any string.
• Initialize an array used[10] to store if a number is mapped to any alphabet
or not.
• Initialize a StringBuffer say unique to store the string with every occurred
alphabet once.
• Assign -1 to every array element of mp.
• Iterate over the array arr[] using the variable i and perform the following
operations:
• Store the length of the string, arr[i] in a variable M.
• Iterate over the string, arr[i] using the variable j and perform the
following operations:
• If mp[arr[i][j] – ‘A’] is -1, then append
the arr[i][j] in uniq and assign 0 to mp[arr[i][j]-‘A].
• Now increment the value of Hash[arr[i][j] -‘A’] by 10(M-j-1).
• If arr[i].length() > 1 and j is 0 then mark true at arr[i][j] – ‘A’ in
CharAtfront[].
• Iterate over the string, S, and perform the same tasks as performed with
every array Strings.
• Fill -1 to every array element of mp.
• Define a recursive function say solve(String word, int i, int S, int[] mp,
int[] used) for backtracking:

• If i is equal to word.length() then return true if S is 0. Otherwise,


return false.
• If mp[word[i]-‘A’] is not equal to -1 then recursively call the
function solve(word, i+1, S+mp[word[i]-‘A’]*Hash[word[i]-‘A], mp, used) and then
return the value returned by it.
• Else, Initialize a variable X as false and iterate over the range [0, 9]
using a variable j and perform the following operations:
• Continue to the next iteration in the loop if any of the conditions are
satisfied:
• If used[j] is true.
• If CharAtfront[word[i]-‘A’] is 1 and j is 0.
• Now mark used[j] as true and assign j to mp[word[i]-‘A’].
• After the above steps call the function solve(word, i+1, S + j *
Hash[word[i]-‘A’], mp, used) and then perform bitwise OR of X with value
returned by it.
• Now mark used[j] as false and assign -1 to mp[word[i] – ‘A’] for
backtracking.
• Return the value of X.
• Finally, after completing the above steps, if the value returned by
solve(uniq, 0, 0, mp, used) is true, then print “Yes“. Otherwise, print “No“.
Below is the implementation of the above approach:
PROGRAM
# Python3 program for the above approach # Function to check if the
# assignment of digits to # characters is possible
def isSolvable(words, result): # Stores the value
# assigned to alphabets mp = [-1]*(26)

# Stores if a number # is assigned to any # character or not used = [0]*(10)

# Stores the sum of position # value of a character


# in every string Hash = [0]*(26)

# Stores if a character # is at index 0 of any # string


CharAtfront = [0]*(26)

# Stores the string formed # by concatenating every # occured character only # once
uniq = ""

# Iterator over the array, # words


for word in range(len(words)): # Iterate over the string,
# word
for i in range(len(words[word])): # Stores the character
# at ith position
ch = words[word][i]

# Update Hash[ch-'A]
Hash[ord(ch) - ord('A')] += pow(10, len(words[word]) - i - 1)

# If mp[ch-'A'] is -1
if mp[ord(ch) - ord('A')] == -1:
mp[ord(ch) - ord('A')] = 0 uniq += str(ch)

# If i is 0 and word # length is greater # than 1


if i == 0 and len(words[word]) > 1:
CharAtfront[ord(ch) - ord('A')] = 1
# Iterate over the string result
for i in range(len(result)): ch = result[i]
Hash[ord(ch) - ord('A')] -= pow(10, len(result) -i - 1) # If mp[ch-'A] is -1
if mp[ord(ch) - ord('A')] == -1:
mp[ord(ch) - ord('A')] = 0 uniq += str(ch)

# If i is 0 and length of # result is greater than 1


if i == 0 and len(result) > 1:
CharAtfront[ord(ch) - ord('A')] = 1 mp = [-1]*(26)
# Recursive call of the function
return True

# Auxiliary Recursive function # to perform backtracking


def solve(words, i, S, mp, used, Hash, CharAtfront): # If i is word.length
if i == len(words):
# Return true if S is 0
return S == 0

# Stores the character at # index i


ch = words[i]

# Stores the mapped value # of ch


val = mp[ord(words[i]) - ord('A')]

# If val is -1
if val != -1:
# Recursion
return solve(words, i + 1, S + val * Hash[ord(ch) - ord('A')], mp, used, Hash,
CharAtfront)

# Stores if there is any # possible solution


x = False

# Iterate over the range


for l in range(10):
# If CharAtfront[ch-'A'] # is true and l is 0
if CharAtfront[ord(ch) - ord('A')] == 1 and l == 0:
continue

# If used[l] is true
if used[l] == 1:
continue

# Assign l to ch mp[ord(ch) - ord('A')] = l

# Marked l as used used[l] = 1

# Recursive function call


x |= solve(words, i + 1, S + l * Hash[ord(ch) - ord('A')], mp, used, Hash,
CharAtfront)

# Backtrack
mp[ord(ch) - ord('A')] = -1

# Unset used[l] used[l] = 0

# Return the value of x;


return x
arr = [ "SIX", "SEVEN", "SEVEN" ] S = "TWENTY"

# Function Call
if isSolvable(arr, S): print("Yes")
else:
print("No")

OUTPUT
Yes
TIME COMPLEXITY: O(N*M+10!), where M is the length of the largest string.

RESULT:
Hence 8-PUZZLE,8-QUEENS, and CRYPTARITHMETIC problem has been solved using python

EXERCISE NO:5 SOLVE TIC-TAC-TOE USING PYTHON

AIM: To Solve TIC-TAC-TOE Using Python


Tic-tac-toe is a very popular game, so let’s implement an automatic Tic-tac-toe
game using Python.
The game is automatically played by the program and hence, no user input is needed.
Still, developing a automatic game will be lots of fun. Let’s see how to do this.
numpy and random Python libraries are used to build this game. Instead of asking
the user to put a mark on the board, code randomly chooses a place on the board and
put the mark. It will display the board after each turn unless a player wins. If
the game gets draw, then it returns -1.

Explanation:
play_game() is the main function, which performs following tasks :
• Calls create_board() to create a 9×9 board and initializes with 0.
• For each player (1 or 2), calls the random_place() function to randomly
choose a location on board and mark that location with the player number,
alternatively.
• Print the board after each move.
• Evaluate the board after each move to check whether a row or column or a
diagonal has the same player number. If so, displays the winner name. If after 9
moves, there are no winner then displays -1.

PROGRAM:

# Tic-Tac-Toe Program using # random number in Python

# importing all necessary libraries


import numpy as np
import random
from time import sleep

# Creates an empty board


def create_board():
return(np.array([[0, 0, 0],
[0, 0, 0],
[0, 0, 0]]))

# Check for empty places on board


def possibilities(board): l = []

for i in range(len(board)):
for j in range(len(board)):

if board[i][j] == 0:
l.append((i, j))
return(l)

# Select a random place for the player


def random_place(board, player): selection = possibilities(board) current_loc =
random.choice(selection) board[current_loc] = player return(board)

# Checks whether the player has three # of their marks in a horizontal row def
row_win(board, player):
for x in range(len(board)): win = True

for y in range(len(board)):
if board[x, y] != player: win = False continue

if win == True:
return(win) return(win)

# Checks whether the player has three # of their marks in a vertical row
def col_win(board, player):
for x in range(len(board)): win = True
for y in range(len(board)):
if board[y][x] != player: win = False
continue if win == True:
return(win) return(win)
# Checks whether the player has three # of their marks in a diagonal row
def diag_win(board, player): win = True
y = 0
for x in range(len(board)):
if board[x, x] != player: win = False
if win:
return win win = True
if win:
for x in range(len(board)): y = len(board) - 1 - x
if board[x, y] != player: win = False
return win
# Evaluates whether there is # a winner or a tie
def evaluate(board): winner = 0

for player in [1, 2]:


if (row_win(board, player) or col_win(board,player) or diag_win(board,player)):
winner = player
if np.all(board != 0) and winner == 0: winner = -1
return winner
# Main function to start the game
def play_game():
board, winner, counter = create_board(), 0, 1 print(board)
sleep(2)
while winner == 0:
for player in [1, 2]:
board = random_place(board, player) print("Board after " + str(counter) + " move")
print(board)
sleep(2) counter += 1
winner = evaluate(board)
if winner != 0:
break return(winner)
# Driver Code
print("Winner is: " + str(play_game()))
OUTPUT :
[[0 0 0]
[0 0 0]
[0 0 0]]
Board after 1 move [[0 0 0]
[0 0 0]
[1 0 0]]
Board after 2 move [[0 0 0]
[0 2 0]
[1 0 0]]
Board after 3 move [[0 1 0]
[0 2 0]
[1 0 0]]
Board after 4 move [[0 1 0]
[2 2 0]
[1 0 0]]
Board after 5 move [[1 1 0]
[2 2 0]
[1 0 0]]
Board after 6 move [[1 1 0]
[2 2 0]
[1 2 0]]
Board after 7 move [[1 1 0]
[2 2 0]
[1 2 1]]
Board after 8 move [[1 1 0]
[2 2 2]
[1 2 1]]

Winner is: 2

RESULT: Hence TIC-TAC-TOE is solved using python.

EXERCISE NO 6: Mini Project

SU DO KU

M = 9
def puzzle(a):
for i in range(M):
for j in range(M):
print(a[i][j],end = " ")
print()
def solve(grid, row, col, num):
for x in range(9):
if grid[row][x] == num:
return False

for x in range(9):
if grid[x][col] == num:
return False

startRow = row - row % 3


startCol = col - col % 3
for i in range(3):
for j in range(3):
if grid[i + startRow][j + startCol] == num:
return False
return True

def Suduko(grid, row, col):

if (row == M - 1 and col == M):


return True
if col == M:
row += 1
col = 0
if grid[row][col] > 0:
return Suduko(grid, row, col + 1)
for num in range(1, M + 1, 1):

if solve(grid, row, col, num):

grid[row][col] = num
if Suduko(grid, row, col + 1):
return True
grid[row][col] = 0
return False

'''0 means the cells where no value is assigned'''


grid = [[2, 5, 0, 0, 3, 0, 9, 0, 1],
[0, 1, 0, 0, 0, 4, 0, 0, 0],
[4, 0, 7, 0, 0, 0, 2, 0, 8],
[0, 0, 5, 2, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 9, 8, 1, 0, 0],
[0, 4, 0, 0, 0, 3, 0, 0, 0],
[0, 0, 0, 3, 6, 0, 0, 7, 2],
[0, 7, 0, 0, 0, 0, 0, 0, 3],
[9, 0, 3, 0, 0, 0, 6, 0, 4]]

if (Suduko(grid, 0, 0)):
puzzle(grid)
else:
print("Solution does not exist:(")

OUTPUT:

2 5 8 7 3 6 9 4 1
6 1 9 8 2 4 3 5 7
4 3 7 9 1 5 2 6 8
3 9 5 2 7 1 4 8 6
7 6 2 4 9 8 1 3 5
8 4 1 6 5 3 7 2 9
1 8 4 3 6 9 5 7 2
5 7 6 1 4 2 8 9 3
9 2 3 5 8 7 6 1 4

You might also like