AI Labfile
AI Labfile
AI Labfile
Bachelor of Technology
in
Information Technology
IT306-Artificial Intelligence
2 1) Write down two intelligent programs for the TIC-TAC-TOE problem. 2) 23/01/23
Write down a program to implement Breadth-first search and depth-first
search for the water jug problem
3 1) Implement Best first search and least cost search for 8-Puzzle problem. 2) 29/01/23
Implement the steps of A* Algorithms for 8-Puzzle problem
NumPy is a powerful Python library used for scientific computing and data analysis. It provides support
for arrays and matrices, which are the basic data structures used for numerical calculations. Here are some
basic characteristics of NumPy:
1. Array Creation: NumPy provides a convenient way to create arrays of various sizes and dimensions.
You can create arrays from Python lists, tuples, and other iterable objects using the `numpy.array()`
function.
2. Array Manipulation: NumPy allows you to manipulate arrays in various ways. You can reshape, slice,
and concatenate arrays using the built-in functions provided by NumPy.
3. Mathematical Operations: NumPy provides a wide range of mathematical functions for performing
basic arithmetic, trigonometric, logarithmic, and statistical operations on arrays.
4. Broadcasting: NumPy allows you to perform operations on arrays of different shapes and sizes using a
technique called broadcasting.
5. Indexing and Slicing: NumPy provides powerful indexing and slicing capabilities for accessing and
manipulating elements of arrays.
6. Universal Functions: NumPy provides a large number of universal functions that operate on entire
arrays rather than individual elements.
7. Linear Algebra: NumPy provides a comprehensive set of linear algebra functions for solving linear
equations, eigenvalue problems, and other matrix operations.
8. Random Number Generation: NumPy provides functions for generating random numbers and random
arrays using various distributions.
Overall, NumPy is a powerful library for numerical computing and data analysis in Python, and it has
become a standard tool for scientific computing in Python.
1/29/23, 6:08 PM IT_154_AIES_1
In [1]:
import numpy as np
In [2]:
arr = np.array([1, 2, 3, 4, 5])
print(arr)
print(type(arr))
[1 2 3 4 5]
<class 'numpy.ndarray'>
In [3]:
print(arr[2] + arr[3])
In [4]:
print(np.add(1, arr[:]))
[2 3 4 5 6]
In [5]:
print(np.subtract(arr[:], 2))
[-1 0 1 2 3]
In [6]:
print(arr.dtype)
int32
In [7]:
a = np.array(['apple', 'banana', 'cherry'])
print(a.dtype)
<U6
In [8]:
b = np.array([True,True,False])
print(b.dtype)
bool
In [9]:
a = [1, 2, 2, 4, 3, 6, 4, 8]
c = np.unique(a)
print(c)
[1 2 3 4 6 8]
In [10]:
dt = np.dtype('>i4')
print(dt)
print("Byte order is:",dt.byteorder)
print("Size is:",dt.itemsize)
localhost:8890/nbconvert/html/IT_154_AIES_1.ipynb?download=false 1/3
1/29/23, 6:08 PM IT_154_AIES_1
In [11]:
x = arr.copy()
arr[0] = 42
print(arr)
print(x)
[42 2 3 4 5]
[1 2 3 4 5]
In [12]:
x = arr.view()
arr[0] = 32
print(arr)
print(x)
[32 2 3 4 5]
[32 2 3 4 5]
In [13]:
arr = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
print(arr.shape)
(2, 4)
In [14]:
arr = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
newarr = arr.reshape(4, 3)
print(arr)
print(newarr)
[ 1 2 3 4 5 6 7 8 9 10 11 12]
[[ 1 2 3]
[ 4 5 6]
[ 7 8 9]
[10 11 12]]
In [15]:
for x in arr:
print(x)
1
2
3
4
5
6
7
8
9
10
11
12
localhost:8890/nbconvert/html/IT_154_AIES_1.ipynb?download=false 2/3
1/29/23, 6:08 PM IT_154_AIES_1
In [16]:
arr1 = np.array([1, 2, 3])
print(arr)
[1 2 3 4 5 6]
In [17]:
arr = np.array([1, 2, 3, 4, 5, 6])
newarr = np.array_split(arr, 3)
print(newarr)
In [18]:
x = np.where(arr == 4)
print(x)
(array([3], dtype=int64),)
In [19]:
arr = np.array([3, 2, 0, 1])
print(np.sort(arr))
[0 1 2 3]
In [20]:
arr = np.array([41, 42, 43, 44])
newarr = arr[x]
print(newarr)
[41 43]
In [ ]:
In [ ]:
localhost:8890/nbconvert/html/IT_154_AIES_1.ipynb?download=false 3/3
2K20/IT/154
Assignment 2
Artificial intelligence (or AI) is a computer program that can intelligently respond to the player’s moves.
This game doesn’t introduce any complicated new concepts. The artificial intelligence that plays Tic Tac
Toe is really just a few lines of code.
Two people play Tic Tac Toe with paper and pencil. One player is X and the other player is O. Players
take turns placing their X or O. If a player gets three of their marks on the board in a row, column, or one
of the two diagonals, they win. The game ends in a draw when the board fills up with neither player
winning.
The game is automatically played by the program and hence, no user input is needed. Still, developing an
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, the code randomly chooses a
place on the board and puts the mark. It will display the board after each turn unless a player wins. If the
game gets drawn, then it returns -1.
In [1]:
import numpy as np
import random
from time import sleep
In [2]:
def create_board():
return(np.array([[0, 0, 0],[0, 0, 0],[0, 0, 0]]))
In [3]:
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)
In [4]:
def random_place(board, player):
selection = possibilities(board)
current_loc = random.choice(selection)
board[current_loc] = player
return(board)
In [5]:
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)
In [6]:
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)
localhost:8890/nbconvert/html/IT_154_AIES_2.ipynb?download=false 1/3
1/29/23, 6:08 PM IT_154_AIES_2
In [7]:
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
In [8]:
def evaluate(board):
winner = 0
winner = player
In [9]:
def play_game():
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)
In [10]:
print("Winner is:" + str(play_game()))
localhost:8890/nbconvert/html/IT_154_AIES_2.ipynb?download=false 2/3
1/29/23, 6:08 PM IT_154_AIES_2
[[0 0 0]
[0 0 0]
[0 0 0]]
Board After1move
[[0 0 0]
[0 1 0]
[0 0 0]]
Board After2move
[[0 0 0]
[2 1 0]
[0 0 0]]
Board After3move
[[0 0 0]
[2 1 0]
[1 0 0]]
Board After4move
[[0 0 0]
[2 1 2]
[1 0 0]]
Board After5move
[[1 0 0]
[2 1 2]
[1 0 0]]
Board After6move
[[1 0 2]
[2 1 2]
[1 0 0]]
Board After7move
[[1 0 2]
[2 1 2]
[1 0 1]]
Winner is:1
In [ ]:
localhost:8890/nbconvert/html/IT_154_AIES_2.ipynb?download=false 3/3
2. Write down a program to implement Breadth-first search and depth-first search for the
water jug problem.
You are given an m litter jug and a n litter 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 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).
Examples:
Input : 4 3 2
Output : {(0, 0), (0, 3), (3, 0), (3, 3), (4, 2), (0, 2)}
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
Code:
Output:
{(0, 0), (0, 3), (3, 0), (3, 3), (2, 2), (2, 0), (4, 0), (4, 4)}
2K20/IT/154
Assignment 3
1. Implement Best first search and least cost search for 8-Puzzle problem.
Bfs
Output
Output
AI Assignment 4/5
2K20/IT/154
Depth first Search or Depth first traversal is a recursive algorithm for searching all the vertices of a graph
or tree data structure. Traversal means visiting all the nodes of a graph.A standard DFS implementation
puts each vertex of the graph into one of two categories:
● Visited
● Not Visited
The purpose of the algorithm is to mark each vertex as visited while avoiding cycles.The DFS algorithm
works as follows:
1. Start by putting any one of the graph's vertices on top of a stack.
2. Take the top item of the stack and add it to the visited list.
3. Create a list of that vertex's adjacent nodes. Add the ones which aren't in the visited list to the top
of the stack.
4. Keep repeating steps 2 and 3 until the stack is empty.
graph = {
'5' : ['3','7'],
'3' : ['2', '4'],
'7' : ['8'],
'2' : [],
'4' : ['8'],
'8' : []
}
visited = set()
Output
● Experiment 5: Implement the hill climbing algorithm in python
The hill-climbing algorithm is a local search algorithm used in mathematical optimization. An important
property of local search algorithms is that the path to the goal does not matter, only the goal itself matters.
Because of this, we do not need to worry about which path we took in order to reach a certain goal state,
all that matters is that we reached it.
The basic principle behind the algorithm is moving across neighboring states according to elevation or
increase in value. This working principle also causes the algorithm to be susceptible to local maximums.
Solving the traveling salesman problem using the hill climbing algorithm in python
In the Travelling salesman problem, we have a salesman who needs to visit a number of cities exactly
once, after which he returns to the first city. The distances between each pair of cities are known, and we
need to find the shortest route. As you can imagine, there is (often) a large number of possible solutions
(routes) to a specific Travelling salesman problem; the goal is to find the best (i.e. the shortest) solution.
Hill climbing tries to find the best solution to this problem by starting out with a random solution, and
then generate neighbors: solutions that only slightly differ from the current one. If the best of those
neighbors is better (i.e. shorter) than the current one, it replaces the current solution with this better
solution. It then repeats the pattern by again creating neighbors. If at some point no neighbor is better than
the current solution, it returns the then current solution. That’s it! The algorithm is quite simple, but it
needs to be said that it doesn’t always find the best solution. It can get stuck in a local maximum: a place
where the current solution isn’t the best solution to the problem, but where none of the direct neighbors of
the current solution are better than the current solution. As described, the algorithm will stop at such a
point, unfortunately without returning the best solution. More complicated algorithms exist that have a
higher chance of finding the best solution, but they often take more computational resources
Code
import random
def randomSolution(tsp):
cities = list(range(len(tsp)))
solution = []
for i in range(len(tsp)):
randomCity = cities[random.randint(0, len(cities) - 1)]
solution.append(randomCity)
cities.remove(randomCity)
return solution
def routeLength(tsp, solution):
routeLength = 0
for i in range(len(solution)):
routeLength += tsp[solution[i - 1]][solution[i]]
return routeLength
def getNeighbours(solution):
neighbours = []
for i in range(len(solution)):
for j in range(i + 1, len(solution)):
neighbour = solution.copy()
neighbour[i] = solution[j]
neighbour[j] = solution[i]
neighbours.append(neighbour)
return neighbours
def hillClimbing(tsp):
currentSolution = randomSolution(tsp)
currentRouteLength = routeLength(tsp, currentSolution)
neighbours = getNeighbours(currentSolution)
bestNeighbour, bestNeighbourRouteLength = getBestNeighbour(tsp, neighbours)
def main():
tsp = [
[0, 400, 500, 300],
[400, 0, 300, 500],
[500, 300, 0, 400],
[300, 500, 400, 0]
]
print(hillClimbing(tsp))
if __name__ == "__main__":
main()
main()
Output
2K20/IT/154
Assignment 6
Code:
import numpy as np
from keras.models import Sequential
from keras.layers import Dense
# Define the training data
X_train = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y_train = np.array([0, 1, 1, 0])
# Define the neural network architecture
model = Sequential()
model.add(Dense(units=2, input_dim=2, activation='relu'))
model.add(Dense(units=1, activation='sigmoid'))
# Compile the model
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
# Train the model
model.fit(X_train, y_train, epochs=1000)
# Test the model
X_test = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y_test = np.array([0, 1, 1, 0])
_, accuracy = model.evaluate(X_test, y_test)
print("Accuracy: %.2f%%" % (accuracy * 100))
Output:
AI Assignment 7
2K20/IT/154
Numerous search algorithms are available, each with unique strengths and limitations. Here is a
comparison of some of the commonly used search algorithms:
Linear Search:
This algorithm examines every element in a list or array until it discovers the target value. Although it is
simple to implement and performs well on small datasets, it can be sluggish on larger datasets due to the
need to scan every element. Its time complexity is O(n).
Binary Search:
This algorithm is more efficient and is used on sorted lists. It begins by examining the middle element,
and if the target value is less than the middle element, it searches the left half of the list; otherwise, it
searches the right half. This procedure is repeated until the target value is discovered or the list is
exhausted. Binary search has a time complexity of O(log n), making it faster than linear search on larger
datasets.
A* Search:
This heuristic search algorithm employs an estimate of the distance to the goal to guide its search. It
combines the efficiency of greedy best-first search with the completeness of uniform-cost search. A* is
frequently used in pathfinding problems, such as finding the shortest route between two points on a map.
Its time complexity is determined by the heuristic function used, but generally, it is O(b^d), where b is the
branching factor and d is the depth of the solution.
Dijkstra's Algorithm:
This greedy algorithm finds the shortest path between two vertices in a graph with non-negative weights.
It begins at the source vertex and iteratively adds the closest unvisited vertex to the shortest path tree.
Dijkstra's algorithm has a time complexity of O(V^2), but with the use of a priority queue, it can be
reduced to O(E + V log V), where E is the number of edges and V is the number of vertices.
Overall, the selection of a search algorithm is determined by the specific problem and the data
characteristics. Some algorithms work better on smaller datasets, while others scale more effectively on
larger datasets. The existence or lack of a sorted order, the data structure (graphs or arrays), and the need
for heuristic information are also crucial factors to consider when selecting a search algorithm.
● Experiment 8:
fact(0,1).
fact(N,F):-
(
N>0 ->
(
N1 is N-1,
fact(N1,F1),
F is N*F1
)
;
N<0 ->
(
N1 is N+1,
fact(N1,F1),
F is N*F1
)
).
Output:
Fibonacci number prolog program
fib(0, 1) :- !.
fib(1, 1) :- !.
fib(N, F) :-
N > 1,
N1 is N-1,
N2 is N-2,
fib(N1, F1),
fib(N2, F2),
F is F1+F2.
Output:
Output:
2K20/IT/154
Assignment 9
Code
queens(N, Queens) :-
length(Queens, N),
board(Queens, Board, 0, N, _, _),
queens(Board, 0, Queens).
board([], [], N, N, _, _).
board([_|Queens], [Col-Vars|Board], Col0, N, [_|VR], VC) :-
Col is Col0+1,
functor(Vars, f, N),
constraints(N, Vars, VR, VC),
board(Queens, Board, Col, N, VR, [_|VC]).
constraints(0, _, _, _) :- !.
constraints(N, Row, [R|Rs], [C|Cs]) :-
arg(N, Row, R-C),
M is N-1,
constraints(M, Row, Rs, Cs).
queens([], _, []).
queens([C|Cs], Row0, [Col|Solution]) :-
Row is Row0+1,
select(Col-Vars, [C|Cs], Board),
arg(Row, Vars, Row-Row),
queens(Board, Row, Solution).
?- queens(4, Queens).
Output