0% found this document useful (0 votes)
19 views14 pages

Ai Lab Manual

The document explains the differences between lists and tuples in Python, highlighting that lists are mutable while tuples are immutable. It includes various Python programming tasks such as creating dictionaries, handling user input, and implementing algorithms like BFS, DFS, and A* search with heuristics. Additionally, it discusses the implications of using A* search in real-world applications, emphasizing potential drawbacks such as high memory consumption and reliance on heuristic quality.

Uploaded by

streetvibes137
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)
19 views14 pages

Ai Lab Manual

The document explains the differences between lists and tuples in Python, highlighting that lists are mutable while tuples are immutable. It includes various Python programming tasks such as creating dictionaries, handling user input, and implementing algorithms like BFS, DFS, and A* search with heuristics. Additionally, it discusses the implications of using A* search in real-world applications, emphasizing potential drawbacks such as high memory consumption and reliance on heuristic quality.

Uploaded by

streetvibes137
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/ 14

Lab No 1 - 4

What is the difference between a list and a tuple? Provide an example.


List:
 A list is an ordered collection of items that can hold a mix of data types.
 Lists are mutable, meaning they can be modified after creation.
Tuples:
A tuple is similar to a list but is immutable (cannot be changed after creation).
 Used when data should remain constant.
Write a Python script that creates a dictionary of 3 students with their names and grades,
then prints the dictionary.
# Create a dictionary with student names as keys and their grades as values
students = {
"Alice": 85,
"Bob": 92,
"Charlie": 78
}
# Print the dictionary
print(students)
Implement a Python program that takes user input for 5 favorite fruits and stores them in
a list. Display the list after input.
# Initialize an empty list to store favorite fruits
favorite_fruits = []
# Loop to take input for 5 favorite fruits
for i in range (5):
fruit = input(f"Enter favorite fruit #{i+1}: ")
favorite fruits. append(fruit)
# Display the list of favorite fruits
print ("Your favorite fruits are:", favorite_fruits)
Create a tuple with 5 city names and try to change one element. What happens? Explain
with output.
# Create a tuple with 5 city names
cities = ("New York", "London", "Paris", "Tokyo", "Sydney")
print ("Original tuple:", cities)
# Try to change one element of the tuple
try:
cities [2] = "Berlin” # Attempt to change "Paris" to "Berlin"
except TypeError as e:
print ("Error:", e)
Output:
Original tuple: ('New York', 'London', 'Paris', 'Tokyo', 'Sydney')
Error: 'tuple' object does not support item assignment
Implement a program where a dictionary stores employee details (name, age, department).
Allow the user to update an employee’s department and print the updated dictionary.
# Initialize dictionary with employee details
employee = {
"name": "John Doe",
"age": 30,
"department": "Sales"
}
print ("Current employee details:")
print(employee)
# Ask user to update the department
new_department = input ("Enter the new department for the employee: "
# Update the department
employee["department"] = new_department
print ("\nUpdated employee details:")
print(employee)
What is the difference between propositional logic and rule-based reasoning? Provide
examples.
Propositional Logic
Propositional logic (also called propositional calculus) is a branch of logic that deals with
propositions (statements that are either true or false) and their combinations using logical
connectives such as AND, OR, NOT, IMPLIES, etc. It focuses on the truth values of propositions
and how they relate.
Key Characteristics:
 Deals with declarative statements (propositions).
 Uses logical connectives to form compound statements.
 Truth values are assigned to propositions (True or False).
 Reasoning involves applying logical inference rules to derive conclusions.
Example:
 Propositions:
 P: "It is raining."
 Q: "The ground is wet."
 Logical statement:
 If it is raining, then the ground is wet: (P \rightarrow Q)
 Given (P = \text {True}), we can infer (Q = \text {True} ).

Rule-Based Reasoning
Rule-based reasoning is a method of reasoning that uses a set of "if-then" rules (production rules)
to derive conclusions or take actions. It is commonly used in expert systems and AI for decision
making.
Key Characteristics:
 Uses rules in the form: IF condition(s) THEN action/conclusion.
 Conditions are often based on facts or data.
 Can handle chaining of rules (forward or backward chaining).
 More procedural and operational compared to propositional logic.
Example:
 Rules:
 IF it is raining THEN the ground is wet.
 IF the ground is wet THEN carry an umbrella.
 Facts:
 It is raining.
 Reasoning:
 From the first rule and fact, conclude the ground is wet.
 From the second rule and conclusion, decide to carry an umbrella.

Implement a Python script that uses SymPy to verify logical expressions.


from sympy import symbols
from sympy. logic.boolalg import Implies, Equivalent, simplify_logic
P, Q = symbols ('P Q')
expr1 = Implies (P, Q) # P -> Q

expr2 = (~P) | Q # ¬P ∨ Q
# Check equivalence
print ("Equivalent:", Equivalent (expr1, expr2). simplify())
# Simplify expression
expr = (P | Q) & (~P | Q)
print ("Simplified:", simplify_logic(expr))

# Check tautology
tautology = P | ~P
print ("Is tautology:", simplify_logic(tautology) == True)
Extend the rule-based system to include additional conditions for diagnosing diseases.
symptoms = {
"fever": True,
"cough": True,
"shortness_of_breath": True, new symptom
"loss_of_taste": False # new symptom
}
def diagnose(symptoms):
if symptoms["fever"] and symptoms["cough"] and symptoms["shortness_of_breath"]:
return "Possible COVID-19"
elif symptoms["fever"] and symptoms["loss_of_taste"]:
return "Possible COVID-19 Variant"
else:
return "Diagnosis unclear"
print(diagnose(symptoms))
Write a Prolog program that defines family relationships and infers parent-child
relationships.
% Define some family facts: parent (Parent, Child).
parent (john, mary).
parent (jane, mary).
parent (mary, susan).
parent (mary, tom).
parent (susan, alice).
% Define father and mother for clarity (optional)
father (john, mary).
mother (jane, mary).
mother (mary, susan).
mother (mary, tom).
mother (susan, alice).
% Define child relationship (child is inverse of parent)
child (Child, Parent): - parent (Parent, Child).
% Example queries you can run:
%? - parent (john, mary).
%? - child (mary, john).
%? - child (alice, susan).
Implement a Python script that dynamically takes user input to evaluate logical
expressions.
from sympy import symbols, sympify, to_cnf
expr_str = input ("Enter logical expression (use & |, ~, >>): ")
# Extract symbols from input
symbols_in_expr = set (filter (str.isalpha, expr_str.replace(' ', '')))
symbs = symbols (. join(symbols_in_expr))
sym_dict = {str(s): s for s in symbs}

try:
expr = sympify (expr_str, locals=sym_dict)
print ("Original:", expr)
print ("Simplified:", expr.simplify())
print ("CNF:", to_cnf (expr, simplify=True))
except Exception as e:
print ("Error:", e)
Explain the difference between BFS and DFS with examples.
BFS (Breadth-First Search)
 Approach: Explores all neighbors of a node before moving to the next level.
 Data structure: Uses a queue (FIFO).
 Traversal order: Level by level.
 Use case: Finding the shortest path in unweighted graphs.
Example:
A
/\
B C
/ \
D E
BFS starting at A:
Visit order → A → B, C → D, E
DFS (Depth-First Search)
 Approach: Explores as far as possible along one branch before backtracking.
 Data structure: Uses a stack (LIFO) or recursion.
 Traversal order: Deep along a path before exploring siblings.
 Use case: Topological sorting, cycle detection.
Example:
Same graph, DFS starting at A:
Possible visit order → A → B → D → C → E
Modify the BFS and DFS implementations to find the shortest path in an
unweighted graph.
from collections import deque
def bfs_shortest_path (graph, start, goal):
queue = deque ([(start, [start]])
visited = {start}
while queue:
node, path = queue. popleft ()
if node == goal:
return path
for neighbor in graph.get (node, []):
if neighbor not in visited:
visited.add(neighbor)
queue.append((neighbor, path + [neighbor]))
return None
Compare the execution times of BFS and DFS on a large dataset.

Aspect BFS DFS

Time Complexity O (V + E) O (V + E)

Space Complexity O(V) (queue can be large) O(V) (stack/recursion depth)

Higher due to queue and


Memory Usage Lower, especially in deep graphs
visited set

Can suffer due to random


Cache Efficiency Often better due to sequential access
memory access

Execution Time on Can be slower without


Often faster in sparse/deep graphs
Large Graphs optimization

High (loop unrolling, sorting,


Optimization Potential Moderate
mmap)

Implement an iterative version of DFS (without recursion) using a stack.


def iterative_dfs (graph, start):
stack = [start] Initialize stack with the starting node
visited = set () Set to track visited nodes
result = [] # List to store the traversal order
while stack:
node = stack.pop () Pop the top node
if node not in visited:
visited.add(node) Mark as visited
result. append(node) Add to result
# Add neighbors in reverse order
for neighbor in reversed (graph.get (node, [])):
if neighbor not in visited:
stack. append(neighbor)
return result
# Example usage:
graph = {
'A': ['B', 'C'],
'B': ['D', 'E'],
'C': ['F'],
'D': [],
'E': [],
'F': []
}
print (iterative_dfs (graph, 'A')) Output: ['A', 'C', 'F', 'B', 'E', 'D']
Discuss a real-world application where BFS would be more suitable than DFS and vice
versa.
BFS and DFS are widely used in various real-world applications. BFS is ideal for finding the
shortest path in unweighted graphs, such as in social networks or web crawling, while DFS is
useful for tasks like maze solving, topological sorting, and exploring game trees.
Explain the role of heuristics in A* search and how it affects performance.
Heuristics in A* search are critical for guiding the algorithm by providing estimates of the cost to
reach the goal from a given node. Their role and impact on performance can be summarized as
follows:
Role of Heuristics:
 Guidance: Heuristics prioritize which nodes to explore, focusing on the most promising
paths.
 Quality Matters: A good heuristic (admissible and consistent) ensures optimal solutions,
while a poor one can led to suboptimal paths and longer search times.
 Trade-offs: Simpler heuristics may speed up the search but can compromise accuracy.
Impact on Performance:
 Reduced Search Space: Effective heuristics decrease the number of nodes evaluated,
leading to faster solutions.
 Faster Convergence: A* with a good heuristic finds optimal solutions more quickly than
uninformed methods like BFS or DFS.
 Applications: Widely used in pathfinding for video games, GPS navigation, and robotics.
Modify the A* algorithm to work in a 2D grid environment with obstacles.
import heapq
def heuristic (a, b):
return abs (ab [0] [0]) + abs(a[1]-b [1])
def a_star (grid, start, goal):
rows, cols = len(grid), len (grid [0])
open_set = [(heuristic (start, goal), 0, start)]
came_from = {}
g_score = {start: 0}
while open_set:
_, cost, current = heapq.heappop(open_set)
if current == goal:
path = []
while current in came_from:
path. append(current)
current = came_from[current]
return [start] + path [: -1]
for dx, dy in [(-1,0), (1,0), (0, -1), (0,1)]:
nx, ny = current [0] +dx, current [1] +dy
neighbor = (nx, ny)
if 0 <= nx < rows and 0 <= ny < cols and grid[nx][ny] == 0:
new_cost = cost + 1
if neighbor not in g_score or new_cost < g_score[neighbor]:
g_score[neighbor] = new_cost
heapq. heappush (open_set, (new_cost + heuristic (neighbor, goal),
new_cost, neighbor))
came_from[neighbor] = current
return None
Implement A* with both Manhattan and Euclidean distance heuristics and compare
their results.
import heapq
import math
def manhattan_heuristic (a, b):
return abs (a [0] - b [0]) + abs(a[1] - b[1]) Manhattan distance

def euclidean_heuristic (a, b):


return math. sqrt(a[0] - b[0]) ** 2 + (a [1] - b[1]) ** 2) # Euclidean distance
def a_star (grid, start, goal, heuristic):
rows, cols = len(grid), len (grid [0])
open_set = []
heapq. heappush (open_set, (0, start))
came_from = {}
g_score = {start: 0}
f_score = {start: heuristic (start, goal)
while open_set:
current = heapq. heappop(open_set) [1]
if current == goal:
return reconstruct_path (came_from, current)
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
neighbor = (current [0] + dx, current [1] + dy)
if 0 <= neighbor [0] < rows and 0 <= neighbor [1] < cols and grid[neighbor [0]]
[neighbor [1]] == 0:
tentative_g_score = g_score[current] + 1

if neighbor not in g_score or tentative_g_score < g_score[neighbor]:


came_from[neighbor] = current
g_score[neighbor] = tentative_g_score
f_score[neighbor] = tentative_g_score + heuristic (neighbor, goal)
if neighbor not in [i[1] for i in open_set]:
heapq. heappush (open_set, (f_score[neighbor], neighbor))
return None # No path found
def reconstruct_path (came_from, current):
total_path = [current]
while current in came_from:
current = came_from[current]
total_path. append(current)
return total_path[::-1] # Return reversed path
# Example grid
grid = [
[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 1, 0],
[0, 1, 0, 0, 0],
[0, 0, 0, 1, 0]
]
start = (0, 0) # Starting position
goal = (4, 4) Goal position
# Run A* with both heuristics
manhattan_path = a_star (grid, start, goal, manhattan_heuristic)
euclidean_path = a_star (grid, start, goal, euclidean_heuristic)
print ("Path using Manhattan heuristic:", manhattan_path)
print ("Path using Euclidean heuristic:", euclidean_path)
What are the potential drawbacks of using A* search in real-world applications?
Here are the potential drawbacks of using the A* search algorithm in real-world
applications, summarized:
Potential Drawbacks of A* Search
1. High Memory Consumption: Requires significant memory to store all generated nodes,
which can be problematic in large search spaces.
2. Time Complexity: Can exhibit exponential time complexity in the worst case, especially
with a poorly designed heuristic.
3. Heuristic Dependency: Performance heavily relies on the quality of the heuristic; a poor
heuristic can lead to inefficient searches.
4. Dynamic Environments: Struggles in environments that change during search, requiring
additional logic to adapt.
5. Balancing Optimality and Speed: In real-time applications, prioritizing speed may
compromise the search for the optimal path.
6. Handling Ties: Multiple paths with the same cost can lead to longer search times based
on the choice of which path to explore first.
7. Admissibility Challenges: Designing an admissible heuristic that never overestimates
costs can be difficult, affecting optimality guarantees.
Design a custom heuristic function for a specific pathfinding problem and analyze
its effectiveness.
Custom Heuristic Function for Pathfinding
Heuristic Design:
 Create a heuristic that combines distance metrics, such as Manhattan and diagonal
distance, to suit the environment.
Example Implementation:
def custom_heuristic(current, goal):
dx = abs(goal[0] - current[0])
dy = abs(goal[1] - current[1])
diagonal_cost = (2 ** 0.5) # Cost for diagonal movement
return (dx + dy) + (diagonal_cost - 2) * min(dx, dy)
Effectiveness Analysis
1. Speed Comparison:
 Measure pathfinding time using custom heuristic versus standard heuristics like
A*.
2. Path Accuracy:
 Compare paths generated by the custom heuristic to optimal paths to ensure
admissibility.
3. Scalability:
 Test performance in larger environments to assess memory usage and efficiency.
4. Dynamic Environment Handling:
 Evaluate how well the heuristic adapts to changing obstacles during pathfinding.
5. User Feedback:
 Gather feedback on path efficiency and adjust the heuristic based on practical
observations.

You might also like