All - Data Structures Laboratory (Pattern 2024)
All - Data Structures Laboratory (Pattern 2024)
Second Year of Computer Engineering and Computer Science and Engineering (2024 Course)
Python Program
from collections import defaultdict, Counter
import statistics
books_borrow_count = {
'Book A': 12,
'Book B': 3,
'Book C': 7,
'Book D': 3,
'Book E': 12,
'Book F': 0
}
def average_books_borrowed(members_dict):
total = sum(members_dict.values())
count = len(members_dict)
return total / count if count > 0 else 0
def book_with_highest_and_lowest_borrowings(books_dict):
if not books_dict:
return None, None
max_book = max(books_dict.items(), key=lambda x: x[1])
min_book = min(books_dict.items(), key=lambda x: x[1])
return max_book, min_book
def count_members_with_zero_borrowings(members_dict):
return sum(1 for borrow_count in members_dict.values() if borrow_count == 0)
def most_frequent_borrow_count_book(books_dict):
borrow_counts = list(books_dict.values())
try:
mode_value = statistics.mode(borrow_counts)
books = [book for book, count in books_dict.items() if count == mode_value]
return mode_value, books
except statistics.StatisticsError:
return None, []
Sample Output
Average books borrowed by members: 2.33
Book with highest borrowings: ('Book A', 12)
Book with lowest borrowings: ('Book F', 0)
Number of members with 0 borrowings: 2
Most frequent borrow count: 12, Books: ['Book A', 'Book E']
2 In an e-commerce system, customer account IDs are stored in a list, and you are tasked with
writing a program that implements the following:
• Linear Search: Check if a particular customer account ID exists in the list.
• Binary Search: Implement Binary search to find if a customer account ID exists,
improving the search efficiency over the basic linear
# Sample Data
customer_account_ids = [1023, 1001, 1045, 1012, 1078, 1033]
# Linear Search
print("\n--- Linear Search ---")
pos = linear_search(customer_account_ids, target)
if pos != -1:
print(f"Customer account ID {target} found at position {pos} (unsorted list).")
else:
print(f"Customer account ID {target} NOT found.")
# Binary Search (requires sorted list)
sorted_ids = sorted(customer_account_ids)
print("\n--- Binary Search ---")
pos = binary_search(sorted_ids, target)
if pos != -1:
print(f"Customer account ID {target} found at sorted position {pos}.")
else:
print(f"Customer account ID {target} NOT found in sorted list.")
Explanation:
• Linear Search: Scans each item one by one. No sorting required.
• Binary Search: Requires the list to be sorted. Much faster for large datasets (O(log n) vs O(n)).
Sample Run:
Enter customer account ID to search: 1045
def bubble_sort(salaries):
"""Sort the salaries using Bubble Sort (ascending)"""
n = len(salaries)
for i in range(n):
for j in range(0, n - i - 1):
if salaries[j] > salaries[j + 1]:
salaries[j], salaries[j + 1] = salaries[j + 1], salaries[j]
return salaries
# Selection Sort
selection_sorted = selection_sort(employee_salaries.copy())
print("Salaries sorted using Selection Sort:")
print(selection_sorted)
print("Top 5 highest salaries (Selection Sort):")
print(selection_sorted[-5:][::-1]) # Top 5 from end, reversed to descending order
# Bubble Sort
bubble_sorted = bubble_sort(employee_salaries.copy())
print("\nSalaries sorted using Bubble Sort:")
print(bubble_sorted)
print("Top 5 highest salaries (Bubble Sort):")
print(bubble_sorted[-5:][::-1])
Sample Output:
Salaries sorted using Selection Sort:
[32000.75, 45000.5, 54000.0, 60000.2, 78000.0, 88000.75, 91000.9, 120000.0]
Top 5 highest salaries (Selection Sort):
[120000.0, 91000.9, 88000.75, 78000.0, 60000.2]
Summary:
• Both sorts produce the same result.
• Top 5 salaries are extracted using sorted_list[-5:][::-1].
Sr. Group B Stacks Queues and Linked List
1 Implementing a real-time undo/redo system for a text editing application using a Stack data
structure. The system should support the following operations:
• Make a Change: A new change to the document is made.
• Undo Action: Revert the most recent change and store it for potential redo.
• Redo Action: Reapply the most recently undone action.
• Display Document State: Show the current state of the document after undoing or
redoing an action
Features Implemented:
1. Make a Change ➜ Push change to the undo stack.
2. Undo ➜ Pop from undo stack and push to redo stack.
3. Redo ➜ Pop from redo stack and push back to undo stack.
4. Display ➜ Show current document state (top of undo stack).
Python Code:
class TextEditor:
def __init__(self):
self.undo_stack = [] # Holds applied changes
self.redo_stack = [] # Holds undone changes
def undo(self):
if len(self.undo_stack) > 1:
state = self.undo_stack.pop()
self.redo_stack.append(state)
print("Undo successful.")
else:
print("Nothing to undo.")
def redo(self):
if self.redo_stack:
state = self.redo_stack.pop()
self.undo_stack.append(state)
print("Redo successful.")
else:
print("Nothing to redo.")
def display(self):
if self.undo_stack:
print("Current Document State:", self.undo_stack[-1])
else:
print("Document is empty.")
# Initial document
editor.make_change("Hello")
editor.make_change("Hello, world!")
editor.make_change("Hello, world! How are you?")
editor.undo()
editor.display()
editor.redo()
editor.display()
Sample Output:
Change applied: 'Hello'
Change applied: 'Hello, world!'
Change applied: 'Hello, world! How are you?'
Current Document State: Hello, world! How are you?
Undo successful.
Current Document State: Hello, world!
Undo successful.
Current Document State: Hello
Redo successful.
Current Document State: Hello, world!
Change applied: 'New sentence after redo.'
Current Document State: New sentence after redo.
Nothing to redo.
How It Works:
• Undo stack holds the history of document states.
• Redo stack holds states that can be re-applied after an undo.
• On new change, the redo stack is cleared to prevent inconsistent redo.
2 Implement a real-time event processing system using a Queue data structure. The system
should support the following features:
• Add an Event: When a new event occurs, it should be added to the event queue.
• Process the Next Event: The system should process and remove the event that has
been in the queue the longest.
• Display Pending Events: Show all the events currently waiting to be processed.
• Cancel an Event: An event can be canceled if it has not been processed.
Features Implemented
1. Add an Event → Append to the queue.
2. Process Next Event → Remove from the front (FIFO).
3. Display Pending Events → Print all unprocessed events.
4. Cancel an Event → Remove a specific event if not processed yet.
Data Structure Used
• Queue (implemented using Python's collections.deque) for efficient FIFO behavior.
Python Code
from collections import deque
class EventProcessingSystem:
def __init__(self):
self.event_queue = deque()
def process_event(self):
if self.event_queue:
event = self.event_queue.popleft()
print(f"Processing event: {event}")
else:
print("No events to process.")
def display_pending_events(self):
if self.event_queue:
print("Pending events:")
for idx, event in enumerate(self.event_queue, start=1):
print(f"{idx}. {event}")
else:
print("No pending events.")
eps.add_event("User login")
eps.add_event("File upload")
eps.add_event("Send notification")
eps.display_pending_events()
eps.process_event()
eps.display_pending_events()
eps.cancel_event("File upload")
eps.display_pending_events()
eps.process_event()
eps.process_event()
eps.process_event() # Should print "No events to process."
Sample Output:
Event 'User login' added to the queue.
Event 'File upload' added to the queue.
Event 'Send notification' added to the queue.
Pending events:
1. User login
2. File upload
3. Send notification
Processing event: User login
Pending events:
1. File upload
2. Send notification
Event 'File upload' has been canceled.
Pending events:
1. Send notification
Processing event: Send notification
No events to process.
3 A call center receives incoming calls, and each call is assigned a unique customer ID. The
calls are answered in the order they are received. Your task is to simulate the call queue of a
call center using a queue data structure.
• addCall(customerID, callTime): Add a call to the queue with the customer ID and the
call time (in minutes).
• answerCall(): Answer and remove the first call from the queue.
• viewQueue(): View all calls currently in the queue without removing them.
• isQueueEmpty(): Check if the queue is empty.
class CallCenterQueue:
def __init__(self):
self.queue = deque()
def answerCall(self):
"""Answer (process) the first call in the queue."""
if self.isQueueEmpty():
print("No calls to answer. Queue is empty.")
else:
call = self.queue.popleft()
print(f"Answered call from Customer ID = {call['customerID']} ({call['callTime']} min)")
def viewQueue(self):
"""View all pending calls in the queue."""
if self.isQueueEmpty():
print("Call queue is empty.")
else:
print("Current calls in queue:")
for i, call in enumerate(self.queue, start=1):
print(f"{i}. Customer ID = {call['customerID']}, Call Time = {call['callTime']} min")
def isQueueEmpty(self):
"""Check if the queue is empty."""
return len(self.queue) == 0
# Example usage
if __name__ == "__main__":
callCenter = CallCenterQueue()
# Sample calls
callCenter.addCall("CUST001", 5)
callCenter.addCall("CUST002", 3)
callCenter.addCall("CUST003", 7)
callCenter.viewQueue()
callCenter.answerCall()
callCenter.viewQueue()
callCenter.answerCall()
callCenter.answerCall()
callCenter.answerCall() # Extra call to test empty case
Sample Output:
Call added: Customer ID = CUST001, Call Time = 5 min
Call added: Customer ID = CUST002, Call Time = 3 min
Call added: Customer ID = CUST003, Call Time = 7 min
Current calls in queue:
1. Customer ID = CUST001, Call Time = 5 min
2. Customer ID = CUST002, Call Time = 3 min
3. Customer ID = CUST003, Call Time = 7 min
Answered call from Customer ID = CUST001 (5 min)
Current calls in queue:
1. Customer ID = CUST002, Call Time = 3 min
2. Customer ID = CUST003, Call Time = 7 min
Answered call from Customer ID = CUST002 (3 min)
Answered call from Customer ID = CUST003 (7 min)
No calls to answer. Queue is empty.
4 Create a Student Record Management System using linked list
• Use a singly/doubly linked list to store student data (Roll No, Name, Marks).
• Perform operations: Add, Delete, Update, Search, and Sort.
• Display records in ascending/descending order based on marks or roll number.
class StudentRecordSystem:
def __init__(self):
self.head = None
if sort_by == "roll":
students.sort(key=lambda x: x[0], reverse=not ascending)
elif sort_by == "marks":
students.sort(key=lambda x: x[2], reverse=not ascending)
# Sample Usage
if __name__ == "__main__":
system = StudentRecordSystem()
# Add students
system.add_student(101, "Alice", 89)
system.add_student(102, "Bob", 75)
system.add_student(103, "Charlie", 95)
# Display unsorted
system.display_students()
# Update
system.update_student(102, marks=82)
# Search
system.search_student(103)
# Delete
system.delete_student(101)
Output Example
Student added: 101, Alice, 89
Student added: 102, Bob, 75
Student added: 103, Charlie, 95
def display(self):
print("\n--- Hash Table ---")
for i, chain in enumerate(self.table):
print(f"Index {i}: {chain}")
print("------------------")
# Example usage
if __name__ == "__main__":
ht = HashTable()
# Display
ht.display()
# Search
ht.search(15)
ht.search(99) # not found
# Delete
ht.delete(25)
ht.delete(99) # not found
Output Sample
Inserted key 10 with value 'Alice' at index 0
Inserted key 20 with value 'Bob' at index 0
Inserted key 15 with value 'Charlie' at index 5
Inserted key 25 with value 'David' at index 5
Inserted key 35 with value 'Eve' at index 5
2 Design and implement a hash table of fixed size. Use the division method for the hash
function and resolve collisions using linear probing. Allow the user to perform the following
operations:
• Insert a key
• Search for a key
• Delete a key
• Display the table
def display(self):
print("\n--- Hash Table ---")
for i, val in enumerate(self.table):
if val is None:
print(f"Index {i}: Empty")
elif val is self.deleted:
print(f"Index {i}: Deleted")
else:
print(f"Index {i}: {val}")
print("------------------")
# Sample usage
if __name__ == "__main__":
ht = LinearProbingHashTable(10)
# Insert keys
ht.insert(10)
ht.insert(20)
ht.insert(30) # Collision with 10, 20
ht.insert(1)
ht.insert(11) # Collision with 1
# Display
ht.display()
# Search
ht.search(30)
ht.search(99)
# Delete
ht.delete(20)
ht.delete(99)
Sample Output
Inserted key 10 at index 0
Inserted key 20 at index 0
Inserted key 30 at index 1
Inserted key 1 at index 1
Inserted key 11 at index 2
3 Implement a hash table with extendible hashing. The hash table should dynamically expand
when the number of keys in a bucket exceeds a certain threshold.
Perform the following operations:
• Insert(key): Insert key-value pairs into the hash table
• Search(key): Search for the value associated with a given key
• Delete(key): Delete a key-value pair from the hash table
def is_full(self):
return len(self.items) >= self.size
class ExtendibleHashTable:
def __init__(self, bucket_size=2):
self.global_depth = 1
self.bucket_size = bucket_size
# Start with 2 buckets (2^1)
self.directory = [Bucket(local_depth=1, size=bucket_size) for _ in range(2)]
# Handle overflow
print(f"Bucket {index} is full. Splitting...")
self._split_bucket(index)
# Retry insertion
self.insert(key, value)
def _double_directory(self):
self.global_depth += 1
new_directory = self.directory * 2
self.directory = new_directory
print(f"Directory doubled. New global depth: {self.global_depth}")
def display(self):
print(f"\nDirectory (Global Depth: {self.global_depth}):")
seen = set()
for i, bucket in enumerate(self.directory):
if id(bucket) not in seen:
seen.add(id(bucket))
print(f"Bucket {i:02b} (Local Depth: {bucket.local_depth}) -> {bucket.items}")
print()
Example Usage
if __name__ == "__main__":
eht = ExtendibleHashTable(bucket_size=2)
eht.insert(10, "Alice")
eht.insert(22, "Bob")
eht.insert(31, "Charlie")
eht.insert(4, "David")
eht.insert(15, "Eve")
eht.display()
eht.search(22)
eht.search(99)
eht.delete(22)
eht.delete(100)
eht.display()
Sample Output
Inserted (10, Alice) into bucket 0
Inserted (22, Bob) into bucket 1
Inserted (31, Charlie) into bucket 1
Bucket 1 is full. Splitting...
Directory doubled. New global depth: 2
Inserted (4, David) into bucket 0
Inserted (15, Eve) into bucket 3
Sample Graph:
Let’s assume the following routes exist:
• A connects to B and C
• B connects to D
• C connects to E
• D connects to F
• E connects to F
That gives us:
Graph:
A—B—D—F
| \
C \
\ \
E --------
Python Implementation
from collections import deque
while queue:
current = queue.popleft()
print(current, end=" ")
add_edge_matrix('A', 'B')
add_edge_matrix('A', 'C')
add_edge_matrix('B', 'D')
add_edge_matrix('C', 'E')
add_edge_matrix('D', 'F')
add_edge_matrix('E', 'F')
Output
DFS Traversal (Adjacency Matrix) from A:
ABDFEC
Explanation
• DFS goes deep first: A → B → D → F → E → C
• BFS goes level-by-level: A → B → C → D → E → F
2 A pizza shop receives multiple orders from several locations. Assume that one pizza boy is
tasked with delivering pizzas in nearby locations, which is represented using a graph. The
time required to reach from one location to another represents node connections. Solve the
problem of delivering a pizza to all customers in the minimum time. Use appropriate data
structures.
Problem Summary:
This is a Minimum Spanning Tree (MST) problem – it ensures all nodes (locations) are visited with minimum total
edge weight (travel time).
We can use either:
• Prim’s Algorithm (greedy and good for dense graphs)
• Kruskal’s Algorithm (greedy and good for sparse graphs)
Here, we’ll use Prim’s Algorithm with a priority queue (min-heap) for efficient performance.
Python Implementation
import heapq
class Graph:
def __init__(self):
self.graph = {}
if len(visited) != len(self.graph):
print("Graph is not connected. Some customers cannot be reached.")
else:
print("Pizza delivery route (minimum total time):")
for u, v, t in mst_edges:
print(f"{u} -> {v} : {t} mins")
print(f"Total Minimum Delivery Time: {total_time} mins")
Sample Output
Pizza delivery route (minimum total time):
A -> B : 5 mins
B -> D : 3 mins
D -> E : 2 mins
D -> C : 4 mins
Total Minimum Delivery Time: 14 mins
Notes
• This does not visit every path, just ensures minimum total time to reach all customers.
• Works even if customers are spread out in complex ways.
3 Implement various operations on a Binary Search Tree, such as insertion, deletion, display, and search
class BST:
def __init__(self):
self.root = None
bst.display("inorder")
bst.display("preorder")
bst.display("postorder")
# Search
result = bst.search(bst.root, 60)
print("Search for 60:", "Found" if result else "Not found")
# Delete
bst.root = bst.delete(bst.root, 70)
print("After deleting 70:")
bst.display("inorder")
Sample Output
Inorder traversal:
20 30 40 50 60 70 80
Preorder traversal:
50 30 20 40 70 60 80
Postorder traversal:
20 40 30 60 80 70 50
Key Concepts
• Inorder traversal gives sorted order
• Search uses BST property (O(log n) avg)
• Delete handles:
o Leaf node
o Node with 1 child
o Node with 2 children (replace with in-order successor)
4 Construct an expression tree from the given prefix expression, e.g., +--a*bc/def, traverse it using post-order traversal
(non-recursive), and then delete the entire tree.
class ExpressionTree:
def __init__(self):
self.root = None
stack1 = [self.root]
stack2 = []
while stack1:
node = stack1.pop()
stack2.append(node)
if node.left:
stack1.append(node.left)
if node.right:
stack1.append(node.right)
while stack2:
print(stack2.pop().value, end=" ")
print()
_delete_postorder(self.root)
self.root = None
print("Expression tree deleted.")
Example Usage
if __name__ == "__main__":
expr = "+--a*bc/def"
tree = ExpressionTree()
tree.construct_from_prefix(expr)
tree.postorder_non_recursive()
tree.delete_tree()
Given Input:
Prefix Expression: +--a*bc/def
Equivalent Infix:
((a - (b * c)) + (d / e - f))
Post-order Output:
abc*-de/f-+
Output
Post-order traversal (non-recursive):
abc*-de/f-+
Expression tree deleted.
5 A list stores city names and their populations. Use a Binary Search Tree for implementation.
Provide a facility for adding new cities, deleting a city, and updating population values.
Provide a facility to display all the city names in ascending/descending order. Also, find how
many maximum comparisons are required to search for a particular city.
Python Code for City BST Management
class CityNode:
def __init__(self, name, population):
self.name = name
self.population = population
self.left = None
self.right = None
class CityBST:
def __init__(self):
self.root = None
# Update population
def update_population(self, name, new_population):
node = self._search(self.root, name)
if node:
node.population = new_population
print(f"Updated population for {name} to {new_population}")
else:
print(f"City '{name}' not found.")
# Delete city
def delete(self, name):
def _delete(root, name):
if root is None:
return None
if name < root.name:
root.left = _delete(root.left, name)
elif name > root.name:
root.right = _delete(root.right, name)
else:
if root.left is None:
return root.right
elif root.right is None:
return root.left
def inorder(root):
if root:
inorder(root.left)
result.append((root.name, root.population))
inorder(root.right)
def reverse_inorder(root):
if root:
reverse_inorder(root.right)
result.append((root.name, root.population))
reverse_inorder(root.left)
if order == "asc":
inorder(self.root)
else:
reverse_inorder(self.root)
# Add cities
bst.insert("Mumbai", 20000000)
bst.insert("Delhi", 18000000)
bst.insert("Kolkata", 14000000)
bst.insert("Chennai", 10000000)
# Display cities
print("\nCities in Ascending Order:")
bst.display("asc")
# Update population
bst.update_population("Kolkata", 14500000)
# Delete city
bst.delete("Delhi")
# Search city
print("\nSearch for 'Mumbai':")
bst.search_with_comparisons("Mumbai")
Explanation:
• Insert: Places cities based on alphabetical order of name.
• Delete: Uses in-order successor if the node has two children.
• Update: Searches and modifies population.
• Display: Uses in-order traversal for ascending and reverse in-order for descending.
• Search with comparisons: Reports number of steps taken.
6 Read the formulas in propositional calculus. Write a function that reads such a formula and
creates its binary tree representation. What is the complexity of your function?
def parse_formula(formula):
tokens = tokenize(formula)
tree, _ = build_tree(tokens, 0)
return tree
def tokenize(formula):
# Split by characters for parsing
tokens = []
i=0
while i < len(formula):
ch = formula[i]
if ch in '()¬∧∨→↔':
tokens.append(ch)
i += 1
elif ch.isalpha():
tokens.append(ch)
i += 1
elif ch == ' ':
i += 1
else:
raise ValueError(f"Invalid character '{ch}'")
return tokens
else:
# operand (like P, Q, R)
return Node(tokens[index]), index + 1
Example Usage:
formula = '((P∧Q)→(¬R∨S))'
tree = parse_formula(formula)
print("Postorder traversal of formula tree:")
print_postorder(tree)
# Output: P Q ∧ R ¬ S ∨ →
3. Time Complexity
Let n = length of formula (number of tokens).
• Tokenization: O(n)
• Tree construction: Each token is visited once in recursion → O(n)
Overall Time Complexity: O(n)
Also, space complexity = O(n) due to recursion stack and tree storage.
Concept:
• Stack (LIFO): Last-in-first-out → used to reverse the string.
• Queue (FIFO): First-in-first-out → keeps original order.
• If both structures return the same sequence when traversed → palindrome.
Python Implementation
from collections import deque
class PalindromeChecker:
def __init__(self):
self.stack = []
self.queue = deque()
if __name__ == "__main__":
checker = PalindromeChecker()
test_strings = [
"Madam",
"racecar",
"A man a plan a canal Panama",
"OpenAI",
"Was it a car or a cat I saw?"
]
for s in test_strings:
result = checker.is_palindrome(s)
print(f"'{s}' → {'Palindrome' if result else 'Not Palindrome'}")
Sample Output:
'Madam' → Palindrome
'racecar' → Palindrome
'A man a plan a canal Panama' → Palindrome
'OpenAI' → Not Palindrome
'Was it a car or a cat I saw?' → Palindrome