0% found this document useful (0 votes)
41 views16 pages

AI Lab QB

The document discusses various search algorithms including breadth-first search, depth-first search, and best-first search. It provides Python code examples to implement these algorithms on graphs represented as dictionaries. The code traverses the graphs and performs the specified search.

Uploaded by

Iffat
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
41 views16 pages

AI Lab QB

The document discusses various search algorithms including breadth-first search, depth-first search, and best-first search. It provides Python code examples to implement these algorithms on graphs represented as dictionaries. The code traverses the graphs and performs the specified search.

Uploaded by

Iffat
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 16

AI Lab

1. Relationships: John likes X if X is a hobby and not a sport.


2. Sentence: "John likes X if X is a hobby and not a sport."

1. Relationships: X is an ancestor of Y if X is a parent of Y.


2. Sentence: "X is an ancestor of Y if X is a parent of Y."

1. sum(X, Y, Z): This states that something is the sum of X and Y if it satisfies
the conditions mentioned in the Body.
2. Read the Body:
Z is X + Y: This condition states that Z is the result of adding X and Y.
3. Putting it together:
The rule can be read as: "Something is the sum of X and Y if Z is the result of
adding X and Y."
1. Read the Head:
 father(Father, Child): This states that something is a father if it
satisfies the conditions mentioned in the Body.
2. Read the Body:
 parent(Father, Child): This condition states that Father is a parent of
Child.
 male(Father): This condition states that Father is male.
3. Putting it together:
 The rule can be read as: "Someone is a father of a child if they are the
parent of that child and they are male."

1. Read the Head:


 is_even(X): This states that something is even if it satisfies the
conditions mentioned in the Body.
2. Read the Body:
 0 is X mod 2: This condition states that X is divisible by 2 with no
remainder (i.e., the remainder of X divided by 2 is 0).
3. Putting it together:
 The rule can be read as: "Something is even if the remainder of
dividing it by 2 is 0."
Question Bank
1. Write a Prolog program to define an ancestor relation. If A is the parent of
B and B is the parent of C, then A is an ancestor of C. Test it with queries
like ancestor(A, C).

Example program:
parent(john, jim).
parent(john, ann).
parent(jim, pat).
parent(pat, kate).

ancestor(A, C) :- parent(A, C).


ancestor(A, C) :- parent(A, B), ancestor(B, C).
1. The first clause, ancestor(A, C) :- parent(A, C)., states that someone (A) is
an ancestor of someone else (C) if they are directly the parent of that
person. This serves as the base case for the recursion.
2. The second clause, ancestor(A, C) :- parent(A, B), ancestor(B, C)., states
that someone (A) is an ancestor of someone else (C) if they are the parent
of some intermediate person (B) and B is an ancestor of C. This recursive
rule allows us to find ancestors at multiple levels.

2. Write a Prolog program to define a sibling relation. Two people are


siblings if they have at least one parent in common. Test it with queries
like `sibling(X, Y).

parent(john, jim).
parent(john, ann).
parent(jim, pat).
parent(sarah, jim).

sibling(X, Y) :- parent(Z, X), parent(Z, Y), X \= Y.


3. Write a Prolog program to generate the Fibonacci sequence up to a given
number N. The predicate `Fibonacci (N, Sequence)` should give the Fibonacci
sequence up to N in `Sequence`.

fibonacci(0, 0).
fibonacci(1, 1).
fibonacci(N, Sequence) :-
N > 1,
N1 is N - 1,
N2 is N - 2,
fibonacci(N1,Sequence1),
fibonacci(N2, Sequence2),
Sequence is Sequence1 + Sequence2.
4. Write a Prolog program to solve the Tower of Hanoi problem. The predicate
`hanoi(N)` should give the steps to solve a Tower of Hanoi puzzle with N
disks.

5. Write a Python function `reverse_string(input_str)` that takes a string


as input and returns the string in reverse order.

def reverse(input_str):
return input_str[::-1]

def main():
my_string = input("Enter a string: ")
rev = reverse(my_string)
print("Reversed string:", rev)

if __name__ == "__main__":
main()
6. Write a Python function `caesar_cipher(input_str, shift)` that
implements a simple Caesar cipher, which is a type of substitution cipher
in which each letter in the plaintext is 'shifted' a certain number of places
down the alphabet.

def caesar_cipher(input_str, shift):

output_str = ""
for char in input_str:
if char.isalpha():
output_str += chr((ord(char) + shift - ord('a')) % 26+ord('a'))
else:
output_str += char

return output_str

if __name__ == "__main__":
input_str = "Hello, world!"
shift = 3
encrypted_str = caesar_cipher(input_str, shift)
print(encrypted_str)

   Write a Python function `find_duplicate(lst)` that takes a list of integers where


each integer appears once except for one which appears twice. The function should
find and return the integer that appears twice.
   Write a Python function `tic_tac_toe(board)` that checks a Tic Tac Toe board
represented as a list of lists and returns whether 'X', 'O', or neither player has won
the game yet.

def tic_tac_toe(board):
# Check rows
for row in board:
if row.count('X') == 3:
return 'X'
elif row.count('O') == 3:
return 'O'

# Check columns
for col in range(3):
if board[0][col] == board[1][col] == board[2][col] == 'X':
return 'X'
elif board[0][col] == board[1][col] == board[2][col] == 'O':
return 'O'

# Check diagonals
if board[0][0] == board[1][1] == board[2][2] == 'X' or board[0][2] ==
board[1][1] == board[2][0] == 'X':
return 'X'
elif board[0][0] == board[1][1] == board[2][2] == 'O' or board[0][2]
== board[1][1] == board[2][0] == 'O':
return 'O'
# No winner
return 'Neither'

# Example board
board = [
['X', 'O', 'O'],
['X', 'X', 'O'],
['O', 'X', 'X']
]

winner = tic_tac_toe(board)
print("The winner is", winner)

1. **Problem 5: Breadth-First Search (BFS)**


   Write a Python function `bfs(graph, start_node)` that performs a breadth-first
search on a given graph, starting from a given node. The graph will be
represented as a dictionary where the keys are node identifiers and the values
are lists of neighboring nodes.

from collections import deque

def bfs(graph, start_node):


    visited = set()  # Set to track visited nodes
    queue = deque([start_node])  # Queue to store nodes for BFS traversal
   
    while queue:
        node = queue.popleft()  # Get the next node from the queue
        if node not in visited:
            visited.add(node)  # Mark node as visited
            print(node)  # You can modify this line to perform any desired
operation on the node
           
            neighbors = graph.get(node, [])  # Get neighbors of the
current node from the graph
            for neighbor in neighbors:
                if neighbor not in visited:
                    queue.append(neighbor)  # Add neighbors to the queue
for further traversal

def main():
    # Create a graph represented as a dictionary
    graph = {
        'A': ['B', 'C'],
        'B': ['A', 'D', 'E'],
        'C': ['A', 'F'],
        'D': ['B'],
        'E': ['B'],
        'F': ['C']
    }

    start_node = 'A'

    print("BFS Traversal:")
    bfs(graph, start_node)

if __name__ == '__main__':
    main()

Depth-First Search (DFS)**


   Write a Python function `dfs(graph, start_node)` that performs a depth-first
search on a given graph, starting from a given node. As with the previous
problem, the graph will be represented as a dictionary.
def dfs(graph, start_node):
    visited = set()  # Set to track visited nodes
    stack = [start_node]  # Stack to store nodes for DFS traversal

    while stack:
        node = stack.pop()  # Get the next node from the stack
        if node not in visited:
            visited.add(node)  # Mark node as visited
            print(node)  # You can modify this line to perform any desired
operation on the node

            neighbors = graph.get(node, [])  # Get neighbors of the


current node from the graph
            for neighbor in neighbors[::-1]:
                if neighbor not in visited:
                    stack.append(neighbor)  # Add neighbors to the stack
for further traversal

def main():
    # Create a graph represented as a dictionary
    graph = {
        'A': ['B', 'C'],
        'B': ['D', 'E'],
        'C': ['F'],
        'D': [],
        'E': ['F'],
        'F': []
    }

    start_node = 'A'

    print("DFS Traversal:")
    dfs(graph, start_node)

if __name__ == '__main__':
    main()
Write a Python function `best_first_search(graph, start_node, goal_node, heuristic)`
that performs a best-first search on a given graph, starting from a given node and
aiming for a goal node. The heuristic function will be provided and takes two nodes
as input, returning an estimate of the cost to reach the goal from the first node.

from queue import PriorityQueue

def best_first_search(graph, start_node, goal_node, heuristic):

visited = set() # Set to track visited nodes


queue = PriorityQueue() # Priority queue to store nodes for best-first search
queue.put((0, start_node)) # Put the start node with priority 0 into the
queue

while not queue.empty():


# Get the node with the lowest priority (based on the heuristic value)
_, current_node = queue.get()

# If the current node is the goal node, return the path


if current_node == goal_node:
return reconstruct_path(graph, start_node, goal_node, visited)

# Mark the current node as visited


visited.add(current_node)

# Expand the neighbors of the current node


neighbors = graph[current_node]
for neighbor in neighbors:
# If the neighbor has not been visited, add it to the queue with its heuristic
value as priority
if neighbor not in visited:
priority = heuristic(neighbor, goal_node)
queue.put((priority, neighbor))

# No path exists
return None

def reconstruct_path(graph, start_node, goal_node, visited):


"""
Reconstructs the path from the start node to the goal node based on the
visited nodes.

Args:
graph: A graph represented as a dictionary where the keys are nodes and
the values are lists of neighbors.
start_node: The start node of the path.
goal_node: The goal node of the path.
visited: A set containing the visited nodes during the search.

Returns:
A list of nodes representing the path from the start node to the goal
node.
"""
path = [goal_node]
current_node = goal_node

while current_node != start_node:


neighbors = graph[current_node]
for neighbor in neighbors:
if neighbor in visited:
path.insert(0, neighbor)
current_node = neighbor
break

return path

# Example usage
def heuristic(node1, node2):
# Example heuristic function (returning a constant value for demonstration
purposes)
return 1

def main():
graph = {
'A': ['B', 'C'],
'B': ['A', 'D', 'E'],
'C': ['A', 'F'],
'D': ['B'],
'E': ['B'],
'F': ['C']
}

start_node = 'A'
goal_node = 'F'

result = best_first_search(graph, start_node, goal_node, heuristic)


if result:
print("Path found:", result)
else:
print("No path exists")

if __name__ == '__main__':
main()

   

Write a Python function `a_star_search(graph, start_node, goal_node, heuristic)`


that performs an A* search on a given graph, starting from a given node and aiming
for a goal node. The heuristic function will be provided, just like in the best-first
search problem.

import heapq

def a_star_search(graph, start_node, goal_node, heuristic):


# Create a priority queue to store nodes to be visited
queue = [(0, start_node)] # (f-value, node)

# Create dictionaries to store the cost and parent information


cost = {start_node: 0}
parent = {start_node: None}

while queue:
# Get the node with the lowest f-value from the priority queue
f_value, current_node = heapq.heappop(queue)

# If the goal node is reached, reconstruct the path and return it


if current_node == goal_node:
path = []
while current_node:
path.append(current_node)
current_node = parent[current_node]
return path[::-1] # Reverse the path to get it in the correct order

# Explore the neighbors of the current node


for neighbor in graph[current_node]:
# Calculate the g-value (cost from start to neighbor)
g_value = cost[current_node] + graph[current_node][neighbor]

# Calculate the h-value (heuristic estimate from neighbor to goal)


h_value = heuristic(neighbor, goal_node)

# Calculate the f-value (sum of g-value and h-value)


f_value = g_value + h_value

# If the neighbor has not been visited or the new path has a lower cost,
# update the cost and parent information, and add the neighbor to the queue
if neighbor not in cost or g_value < cost[neighbor]:
cost[neighbor] = g_value
parent[neighbor] = current_node
heapq.heappush(queue, (f_value, neighbor))

# If the goal node cannot be reached, return an empty path


return []

# Example usage:
# Define the graph as a dictionary of dictionaries
graph = {
'A': {'B': 5, 'C': 3},
'B': {'D': 2},
'C': {'D': 8, 'E': 10},
'D': {'E': 2, 'F': 6},
'E': {'F': 1},
'F': {}
}

# Define a heuristic function (e.g., Manhattan distance)


def heuristic(node, goal_node):
return abs(ord(node) - ord(goal_node))

start_node = 'A'
goal_node = 'F'

path = a_star_search(graph, start_node, goal_node, heuristic)


print(path) # Output: ['A', 'B', 'D', 'F']

   Write a Python function `dijkstra(graph, start_node, end_node)` that finds the


shortest path between two nodes in a graph using Dijkstra's algorithm. The graph
will be represented as a dictionary of dictionaries: the outer dictionary's keys are
node identifiers, and the inner dictionaries' keys are neighboring nodes with the
associated values being the distance to that neighbor.

import heapq

def dijkstra(graph, start_node, end_node):


# Create a priority queue to store nodes to be visited
queue = [(0, start_node)] # (distance, node)

# Create a dictionary to store the shortest distances from the start node to
each node
distances = {node: float('inf') for node in graph}
distances[start_node] = 0

# Create a dictionary to store the previous node in the shortest path


previous = {node: None for node in graph}
while queue:
# Get the node with the smallest distance from the priority queue
distance, current_node = heapq.heappop(queue)

# If the end node is reached, reconstruct the path and return it


if current_node == end_node:
path = []
while current_node:
path.append(current_node)
current_node = previous[current_node]
return path[::-1] # Reverse the path to get it in the correct order

# Skip this node if its distance is already greater than the distance from the
start node
if distance > distances[current_node]:
continue

# Explore the neighbors of the current node


for neighbor, edge_distance in graph[current_node].items():
# Calculate the total distance from the start node to the neighbor
total_distance = distances[current_node] + edge_distance

# If the new distance is shorter, update the distance and previous information
if total_distance < distances[neighbor]:
distances[neighbor] = total_distance
previous[neighbor] = current_node
heapq.heappush(queue, (total_distance, neighbor))

# If the end node cannot be reached, return an empty path


return []

# Example usage:
# Define the graph as a dictionary of dictionaries
graph = {
'A': {'B': 5, 'C': 3},
'B': {'D': 2},
'C': {'D': 8, 'E': 10},
'D': {'E': 2, 'F': 6},
'E': {'F': 1},
'F': {}
}

start_node = 'A'
end_node = 'F'

path = dijkstra(graph, start_node, end_node)


print(path) # Output: ['A', 'B', 'D', 'F']

You might also like