0% found this document useful (0 votes)
3 views32 pages

Python Assignment One PDF

The document is an Alternate Assessment Tool Report for the Python Programming course submitted by Anand Raj at Dayananda Sagar College of Engineering. It includes a certificate of authenticity, a declaration of ethical commitment, and detailed programming assignments covering topics such as palindrome extraction, string operations, list statistics, and student record management. The report emphasizes the importance of built-in methods for efficiency and discusses the performance implications of using lists versus dictionaries in Python.

Uploaded by

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

Python Assignment One PDF

The document is an Alternate Assessment Tool Report for the Python Programming course submitted by Anand Raj at Dayananda Sagar College of Engineering. It includes a certificate of authenticity, a declaration of ethical commitment, and detailed programming assignments covering topics such as palindrome extraction, string operations, list statistics, and student record management. The report emphasizes the importance of built-in methods for efficiency and discusses the performance implications of using lists versus dictionaries in Python.

Uploaded by

wifacad817
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

DAYANANDA SAGAR COLLEGE OF ENGINEERING

(An Autonomous Institute affiliated to Visvesvaraya Technological University (VTU), Belagavi,


Approved by AICTE and UGC, Accredited by NAAC with ‘A’ grade & ISO 9001 – 2015 Certified Institution)
Shavige Malleshwara Hills, Kumaraswamy Layout, Bengaluru-560111

DEPARTMENT OF INFORMATION SCIENCE & ENGINEERING

Alternate Assessment Tool Report submitted for the subject

Python Programming – ESC22IS362

Submitted by

ANAND RAJ(1DS24IS020)

Under the Guidance of


Ms. Spoorthi M
Assistant Professor
Department of Information Science and Engineering
DSCE, Bengaluru

VISVESVARAYA TECHNOLOGICAL UNIVERSITY


JNANASANGAMA, BELAGAVI-590018, KARNATKA
2025-26
DAYANANDA SAGAR COLLEGE OF ENGINEERING
(An Autonomous Institute affiliated to Visvesvaraya Technological University (VTU), Belagavi,
Approved by AICTE and UGC, Accredited by NAAC with ‘A’ grade & ISO 9001 – 2015 Certified Institution)
Shavige Malleshwara Hills, Kumaraswamy Layout, Bengaluru-560111

DEPARTMENT OF INFORMATION SCIENCE & ENGINEERING

CERTIFICATE
This is to certify that the Alternate Assessment Tool (AAT) submitted as part of Python
Programming (ESC22IS362) is a bonafide work carried out by Anand Raj (1DS24IS020) as 10-mark
component in partial fulfillment for the 3rd semester of Bachelor of Engineering in Information Science
& Engineering of the Visvesvaraya Technological University, Belgaum during the year 2025-26. The
AAT report has been approved as it satisfies the academic requirements prescribed for the Bachelor of
Engineering degree.

Signature of Faculty Signature of HOD


[Ms. Spoorthi M] [[Link] P Patil]
DAYANANDA SAGAR COLLEGE OF ENGINEERING
(An Autonomous Institute affiliated to Visvesvaraya Technological University (VTU), Belagavi,
Approved by AICTE and UGC, Accredited by NAAC with ‘A’ grade & ISO 9001 – 2015 Certified Institution)
Shavige Malleshwara Hills, Kumaraswamy Layout, Bengaluru-560111

DEPARTMENT OF INFORMATION SCIENCE & ENGINEERING

DECLARATION

I declare that I abide by the ethical principles and commit to professional ethics and
responsibilities and norms of the engineering practice. The work submitted in this report of Python
Programming (ESC22IS362) III Semester BE, ISE has been compiled by referring to the relevant online
and offline resources to the best of my understanding and in partial fulfillment of the requirement for the
award of the degree of Bachelor of Engineering in Information Science & Engineering, at Dayananda
Sagar College of Engineering, an autonomous institution affiliated to VTU, Belagavi during the academic
year 2025-26.
I hereby declare that the same has not been submitted in part or full for other academic purposes.

ANAND RAJ (1DS2424IS020)

Place: Bengaluru

Date: 10 Dec 2025


Contents

PART A

1. Assignment 1..................................................... Page No 01


2. Assignment 2..................................................... Page No 06
3. Assignment 3..................................................... Page No 11
4. Assignment 4..................................................... Page No 17
5. Assignment 5..................................................... Page No 23
Module-1 (Python Basics, Data Types, Strings & Control Statements)

Q1 )Palindrome Extractor & Slicing Analysis

a) Program

def get_palindromes(text, min_length=5):


n = len(text)
palindromes = []
for i in range(n):
for j in range(i + min_length, n + 1):
substring = text[i:j]
if substring == substring[::-1]:
[Link](substring)
return palindromes

def main():
filename = "input_data.txt"
with open(filename, "w") as f:
[Link]("The racecar driver saw a level radar.\n")
[Link]("Madam, the rotor is broken.\n")
[Link]("A man, a plan, a canal, Panama? No, just a malayalam word.")

all_palindromes = []

try:
with open(filename, "r") as file:
lines = [Link]()
for line in lines:
found = get_palindromes([Link](), min_length=5)
all_palindromes.extend(found)

sorted_palindromes = sorted(all_palindromes, key=len, reverse=True)

print("--- Palindromes (Length >= 5) ---")


for p in sorted_palindromes:
print(f"{p} (Len: {len(p)})")

except FileNotFoundError:
print("Error: The file was not found.")

if __name__ == "__main__":
main()

DAA (IPCC22IS43) AY 2025-26 Page 1


b) Output:-

c) Detailed Explanation & Analysis


How the Code Works (Step-by-Step):
1. Input Handling: We read the whole file into one variable, text. We remove newlines so palindromes that might
span across lines (though rare) wouldn't break, and to keep the indexing simple.
2. The Search Strategy (Nested Loops): Imagine a sliding window.
o The outer loop (i) places the start of the window.
o The inner loop (j) expands the end of the window.
o The condition range(i + 5, n + 1) forces the window to be at least 5 characters wide immediately.
3. The Palindrome Trick: sub[::-1] is a Python shortcut. The -1 is the "step", meaning "step backwards through
the string." If the backward version matches the forward version, it's a palindrome.
The question asks "how slicing impacts performance." Here is the deep dive:
• 1. The "Copying" Penalty (Memory)
o Concept: Python strings are immutable (they cannot be changed). When you write sub = text[i:j], Python
does not just give you a "view" or a pointer to that part of the original text.
o Reality: Python allocates a completely new block of memory and copies every character from the
original text into this new block.
o Consequence: If you have a 10MB file, and you iterate through it, your program is constantly allocating
millions of tiny new strings and then throwing them away. This floods the computer's RAM and forces
the "Garbage Collector" to work overtime, pausing your program repeatedly to clean up memory.
• 2. The Speed Penalty (Time Complexity)
o The Loops ($O(N^2)$): Checking every start and end position naturally takes $N^2$ steps.
o The Slice ($O(N)$): Because slicing copies data, it takes time proportional to the length of the slice.
o The Result ($O(N^3)$): You are doing an $O(N)$ copy operation inside an $O(N^2)$ loop.

DAA (IPCC22IS43) AY 2025-26 Page 2


Module-1 Python Basics, Data Types, Strings & Control Statements

Q2 ) Menu-Driven String Operations

a) Program

import sys

def main():
while True:
print("\n--- String Operations Menu ---")
print("1. Convert to Uppercase")
print("2. Count Character Occurrences")
print("3. Check Prefix")
print("4. Exit")

choice = input("Enter your choice (1-4): ")

if choice == '1':
text = input("Enter a string: ")
print("Result:", [Link]())

elif choice == '2':


text = input("Enter a string: ")
char = input("Enter character to count: ")
print(f"Count: {[Link](char)}")

elif choice == '3':


text = input("Enter a string: ")
prefix = input("Enter prefix to check: ")
print(f"Starts with '{prefix}': {[Link](prefix)}")

elif choice == '4':


print("Exiting program.")
[Link]()

else:
print("Invalid choice, please try again.")

if __name__ == "__main__":
main()

DAA (IPCC22IS43) AY 2025-26 Page 3


b) Output:-

c) Detailed Explanation & Analysis

Program Logic: The program uses an infinite while loop to continuously display the menu until the user explicitly
chooses to exit. It utilizes input() to capture the user's string and choice. Depending on the selection, it calls the
standard string methods directly on the input variable.
Justification for Built-in Methods: Using built-in methods like upper(), count(), and startswith() is significantly
more efficient than writing manual for loops in Python for three key reasons:
1. C Implementation: Python's built-in string methods are implemented in C. When you call [Link](), the
operation runs at C-speed, which is much faster than processing a Python loop that executes bytecode
instruction by instruction.

DAA (IPCC22IS43) AY 2025-26 Page 4


2. Optimization: These methods are highly optimized for specific tasks. For example, count() utilizes
algorithms that are faster than checking every single character index-by-index in a Python loop.
3. Readability: Built-in methods reduce the code complexity. A manual loop requires initialization, iteration,
conditional checking, and incrementing logic, whereas a built-in method achieves the same result in a
single, readable line.

DAA (IPCC22IS43) AY 2025-26 Page 5


Module-2 (Functions Lists, Tuples, Exception Handling)

Q1 ) List Statistics & Mutability Analysis

a) Program

def process_stats(data_list):
try:
if not isinstance(data_list, list):
raise TypeError("Input provided is not a list.")

unique_list = []
for item in data_list:
if not isinstance(item, int):
raise ValueError(f"Non-integer value '{item}' found.")

if item not in unique_list:


unique_list.append(item)

unique_list.sort()

n = len(unique_list)
if n == 0:
print("List is empty.")
return

total = sum(unique_list)
mean_val = total / n

if n % 2 == 1:
median_val = unique_list[n // 2]
else:
mid1 = unique_list[n // 2 - 1]
mid2 = unique_list[n // 2]
median_val = (mid1 + mid2) / 2

print(f"Processed List: {unique_list}")


print(f"Mean: {mean_val}")
print(f"Median: {median_val}")
print("Mode: All values (Uniform distribution due to duplicate removal)")

except (ValueError, TypeError) as e:


print(f"Error Occurred: {e}")

def main():
print("--- Case 1: Valid Input with Duplicates ---")
numbers = [10, 2, 5, 10, 8, 2, 20]
process_stats(numbers)
DAA (IPCC22IS43) AY 2025-26
print(f"Original List Check: {numbers}") Page 6
numbers = [10, 2, 5, 10, 8, 2, 20]
process_stats(numbers)
print(f"Original List Check: {numbers}")
print("\n--- Case 2: Invalid Input ---")
mixed_data = [10, 20, "thirty", 40]
process_stats(mixed_data) if __name__ == "__main__": main()

b) Output:-

c) Detailed Explanation & Analysis

Logic: The function first validates the input type. It iterates through the list, checking if each element
is an integer (raising a ValueError if not) and appending it to a new list unique_list only if it hasn't been
added yet (removing duplicates). It then sorts this new list.
• Mean: Calculated by summing the unique elements and dividing by the count.
• Median: Determined by finding the middle index. If the length is even, it averages the two
middle numbers.
• Mode: Since duplicates are removed before calculation, every number appears exactly once.
Thus, there is no single mode (uniform distribution).
Analysis: List Mutability:
• Python Lists are Mutable: This means their content can be changed in place.
• Function Behavior: In the code above, we created a new list (unique_list) rather than modifying
the original data_list.
o If we had used data_list.sort() or data_list.remove(), these changes would reflect in the
main block variable numbers because lists are passed by object reference.
o By creating a new variable, we avoided "side effects," keeping the original data intact
(as seen in the output Original List Check). This is generally preferred in functional
programming to prevent data corruption.

DAA (IPCC22IS43) AY 2025-26 Page 7


Module-2 (Functions Lists, Tuples, Exception Handling)

Q2 ) Student Records (List of Tuples vs. Dictionary)

a) Program
def add_student(records, roll, marks):
# Check if roll already exists
for r, m in records:
if r == roll:
print(f"Error: Roll {roll} already exists.")
return
[Link]((roll, marks))
print(f"Added: Roll {roll}, Marks {marks}")

def update_student(records, roll, new_marks):


for i, (r, m) in enumerate(records):
if r == roll:
# Tuples are immutable, so we replace the element in the list
records[i] = (roll, new_marks)
print(f"Updated: Roll {roll} to Marks {new_marks}")
return
print(f"Error: Roll {roll} not found.")

def delete_student(records, roll):


for i, (r, m) in enumerate(records):
if r == roll:
del records[i]
print(f"Deleted: Roll {roll}")
return
print(f"Error: Roll {roll} not found.")

def search_student(records, roll):


for r, m in records:
if r == roll:
print(f"Found: Roll {roll}, Marks {m}")
return
print(f"Search: Roll {roll} not found.")

def main():
# List of tuples structure
student_records = []

print("--- Student Record Operations ---")


add_student(student_records, 101, 85)
add_student(student_records, 102, 90)
add_student(student_records, 101, 95) # Duplicate test

search_student(student_records, 102)

update_student(student_records, 101, 88)

delete_student(student_records, 102)

print(f"\nFinal List State: {student_records}")

'''
--- EVALUATION & JUSTIFICATION ---
DAA (IPCC22IS43) AY 2025-26 Page 8
1. Appropriateness:
Dictionaries are significantly more appropriate than a list of tuples
--- EVALUATION & JUSTIFICATION ---

1. Appropriateness:
Dictionaries are significantly more appropriate than a list of tuples
for this specific use case (associating unique keys to values).

2. Justification (Performance):
- List of Tuples: To find a student (Search, Update, or Delete),
we must iterate through the list using a loop.
This results in O(N) time complexity (Linear Time).
- Dictionary: A dictionary uses hashing. Looking up a key (Roll No)
does not require a loop. It happens in O(1) time complexity (Constant Time).

3. Conclusion:
For large datasets, the Dictionary is vastly superior in speed and
cleaner in syntax (e.g., records[roll] = marks).
'''

if __name__ == "__main__":
main()

b) Output :

c) Explanation & Analysis


Program Logic:
The program uses a list where each item is a tuple consisting of (roll_number, marks).
• Add: It iterates through the list to ensure the Roll Number doesn't exist (avoiding duplicates)
before appending.
• Update: It finds the index of the tuple matching the Roll Number. Since tuples are immutable
(cannot be changed),
it replaces the entire tuple at that index with a new one containing the updated marks.
• Delete/Search: Both operations require iterating through the list linearly to find the matching Roll
Number.

DAA (IPCC22IS43) AY 2025-26 Page 9


Evaluation (List vs. Dictionary):
As requested in the comments of the code:
• The List of Tuples approach is inefficient. Every time we want to find a student, we have to scan
the list one by one. In computer science terms, this is $O(N)$ complexity.
• The Dictionary approach is optimal. Dictionaries are built for "Key-Value" pairs. Python uses a
hash map for dictionaries, meaning it can jump directly to the memory location of "Roll 101" .
without scanning the whole dictionary.

• Conclusion: While a list of tuples works, a dictionary should always be the preferred
choice for record management based on unique IDs.

DAA (IPCC22IS43) AY 2025-26 Page 10


Module-3 (Dictionaries, Data Structuring, RegEx)

Q1) Dictionary-Based Library System

a) Program

import json

def add_book(library, book_id, title, author, year):


if book_id in library:
print(f"Error: Book ID {book_id} already exists.")
return

# Nested dictionary creation


library[book_id] = {
"title": title,
"author": author,
"year": year
}
print(f"Book '{title}' added successfully.")

def search_by_author(library, author_name):


found_books = {}
print(f"\n--- Searching for Author: {author_name} ---")

# Iterating through the nested structure


for bid, details in [Link]():
# Case-insensitive check
if details['author'].lower() == author_name.lower():
found_books[bid] = details

if found_books:
# Pretty printing using JSON module
print([Link](found_books, indent=4))
else:
print("No books found for this author.")

def main():
# Main dictionary to hold the library data
library_system = {}

# 1. Adding Books
add_book(library_system, "B001", "The Great Gatsby", "F. Scott Fitzgerald", 1925)
add_book(library_system, "B002", "1984", "George Orwell", 1949)
add_book(library_system, "B003", "Animal Farm", "George Orwell", 1945)
add_book(library_system, "B004", "Clean Code", "Robert Martin", 2008)

# 2. Searching by Author
search_by_author(library_system, "George Orwell")

# 3. Pretty Print Full Library


print("\n--- Current Library Catalog ---")
print([Link](library_system, indent=4))

if __name__ == "__main__":
main()

DAA (IPCC22IS43) AY 2025-26 Page 11


b) Output:-

DAA (IPCC22IS43) AY 2025-26 Page 12


c) Analysis: Nested Dictionaries for Real-World Data

1. Hierarchical Representation
Real-world data is rarely flat. Objects often contain other objects. Nested dictionaries allow us to model
this hierarchy naturally. In this program, the structure is:
Library (System) -> Book (Entity) -> Attributes (Title, Author, Year)
This structure mirrors standard data formats like JSON (JavaScript Object Notation), which is the
universal standard for exchanging data between servers and web applications.
2. Efficient Access (O(1))
By using a dictionary keyed by BookID, we can access any specific book record instantly without looping
through the entire collection. If we used a list, finding a specific book ID would require checking every
item one by one ($O(N)$), which is inefficient for large libraries.
3. Flexibility
Dictionaries are schema-less. If we needed to add a new field (e.g., "Genre" or "ISBN") to just one book,
we could do so easily without breaking the structure of the other records. This flexibility is essential for
handling real-world data where information might be incomplete or variable.

Code Explanation
• add_book Function:
o This function accepts the library dictionary and book details.
o It first checks if the book_id already exists to prevent overwriting data.
o If the ID is unique, it creates a nested dictionary containing title, author, and year, and assigns
it to the book_id key in the main library dictionary.
• search_by_author Function:
o This function iterates through the library using .items(), which gives us both the bid (Book ID)
and details (the nested dictionary).
o It compares the stored author name with the requested name (converting both to lowercase to
make the search case-insensitive).
o If a match is found, that specific book is added to a temporary found_books dictionary, which
is then printed using [Link]
• [Link](obj, indent=4):
This is used for "Pretty Printing." It takes a standard Python dictionary and converts it into a
formatted string with 4 spaces of indentation, making the nested structure readable for humans.

DAA (IPCC22IS43) AY 2025-26 Page 13


Module-3 (Dictionaries, Data Structuring, RegEx)

Q2) Data Extraction using Regular Expressions

a) Program

import re

def extract_data(text):
# Regex pattern for Email
# Matches alphanumeric characters, followed by @, domain name, and extension
email_pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'

# Regex pattern for Date


# Matches dd-mm-yyyy or dd/mm/yyyy
date_pattern = r'\b\d{2}[-/]\d{2}[-/]\d{4}\b'

emails = [Link](email_pattern, text)


dates = [Link](date_pattern, text)

print("--- Extracted Information ---")


print(f"Emails found: {emails}")
print(f"Dates found: {dates}")

def main():
paragraph = """
Please submit your project by 15-04-2024. If you have questions,
contact support@[Link] or [Link]@[Link] before the
deadline on 20/04/2024. Do not email invalid@addresses.
"""

extract_data(paragraph)

if __name__ == "__main__":
main()

b) Output :-

DAA (IPCC22IS43) AY 2025-26 Page 15


c) Program Explanation

• import re: Imports Python's built-in Regular Expression module.


• email_pattern:
o [A-Za-z0-9._%+-]+: Matches the username (letters, numbers, dots, etc.).
o @: Matches the literal "@" symbol.
o [A-Za-z0-9.-]+: Matches the domain name (e.g., "gmail", "tech-lab").
o \.[A-Z|a-z]{2,}: Matches the dot followed by the extension (e.g., ".com", ".org").
• date_pattern:
o \d{2}: Matches exactly 2 digits (Day).
o [-/]: Matches either a hyphen OR a forward slash.
o \d{4}: Matches exactly 4 digits (Year).
• [Link](): This is the core function. It scans the entire string and returns all non-overlapping matches as a list of
strings.

d) Evaluation: Why Regex is Necessary?

Comparing Regex to manual string scanning (using loops and indices):


1. Complexity Reduction:
o Manual: To find an email manually, you would need complex nested loops to find the "@", check
characters before and after it, and handle edge cases. This requires dozens of lines of code.
o Regex: It defines the pattern in a single line. The logic is declarative (describing what you want) rather than
imperative (describing how to step through the text).
2. Maintenance & Readability:
o If the date format changes (e.g., allowing "2024.04.15"), updating a manual parser requires rewriting logic
logic blocks. With Regex, you simply adjust the pattern string slightly (e.g., adding . to the character class
[-/.]).
3. Performance:
o The re module is implemented in C. It processes text much faster than a standard Python for loop,
especially for large documents.

DAA (IPCC22IS43) AY 2025-26 Page 16


Module-4 (OOP, Files, Shelve Module)

Q1) BankAccount Class & Encapsulation

a) Program

class BankAccount:
def __init__(self, name, initial_balance=0):
[Link] = name
[Link] = initial_balance

def deposit(self, amount):


if amount > 0:
[Link] += amount
print(f"Deposited: {amount}")
else:
print("Invalid deposit amount.")

def withdraw(self, amount):


if 0 < amount <= [Link]:
[Link] -= amount
print(f"Withdrawn: {amount}")
else:
print("Insufficient funds or invalid amount.")

def show_balance(self):
print(f"Account: {[Link]} | Balance: {[Link]}")

def main():
acc = BankAccount("John Doe", 1000)

acc.show_balance()

[Link](500)
acc.show_balance()

[Link](200)
acc.show_balance()

[Link](5000)

[Link](-100)

if __name__ == "__main__":
main()

DAA (IPCC22IS43) AY 2025-26 Page 17


b) Output:-

c) Detailed Explanation & Analysis :-

• The program defines a Class named BankAccount, which serves as a blueprint for creating individual
account objects. The __init__ method is a special constructor that runs automatically when a new
object is created; it initializes the object's distinct state (its name and balance) using the self keyword.
self is crucial because it differentiates between the specific object being called (e.g., John's account
vs. Jane's account).
• The methods deposit and withdraw are the primary ways to interact with the account. Instead of just
doing math, these methods include logic. For instance, withdraw doesn't just subtract money; it first
checks if the requested amount is valid (greater than zero) and if sufficient funds exist. This logic
ensures the method handles the data intelligently. Finally, show_balance provides a read-only view of
the current state without modifying it.

Analysis: How Encapsulation Improves Reliability

1. Controlled Access (Data Protection) : Encapsulation restricts direct access to an object's internal state.
Instead of allowing external code to modify balance directly (e.g., [Link] = -500), interaction happens
through methods (deposit, withdraw). This ensures that the balance cannot be set to an invalid state (like a
negative number) or modified unpredictably.

2. Validation Logic By bundling the data with the methods that operate on it, we can enforce rules. In the
program above, the withdraw method includes logic to check if amount <= [Link]. This validation logic
protects the integrity of the data, ensuring the program behaves reliably even when bad inputs (like
withdrawing more than you own) are attempted.

DAA (IPCC22IS43) AY 2025-26 Page 18


3. Modularity Encapsulation allows the internal implementation of the class to change without breaking the
code that uses it. For example, if we later decide to add a transaction fee to every withdrawal, we only need
to update the withdraw method, and the rest of the system remains unaffected.

DAA (IPCC22IS43) AY 2025-26 Page 19


Module-4 (OOP, Files, Shelve Module)

Q2) Word Frequency with Shelve vs. Pprint

a) Program
import shelve
import pprint
from collections import Counter
import ast

def count_words(filename):
with open(filename, 'r') as f:
text = [Link]().lower().replace('\n', ' ')
words = [Link]()
return dict(Counter(words))

def save_with_shelve(data, filename):


with [Link](filename) as db:
for key, value in [Link]():
db[key] = value
print(f"Data saved to shelve: {filename}")

def save_with_pprint(data, filename):


formatted_data = [Link](data)
with open(filename, 'w') as f:
[Link](formatted_data)
print(f"Data saved to text file: {filename}")

def retrieve_shelve(filename, key):


with [Link](filename) as db:
return [Link](key, "Not Found")

def retrieve_pprint(filename, key):


with open(filename, 'r') as f:
data_str = [Link]()
data = ast.literal_eval(data_str)
return [Link](key, "Not Found")

def main():
input_file = "[Link]"
with open(input_file, "w") as f:
[Link]("apple banana apple cherry banana apple date")

word_counts = count_words(input_file)

shelve_file = "my_shelve_db"
save_with_shelve(word_counts, shelve_file)

pprint_file = "my_pprint_data.txt"
save_with_pprint(word_counts, pprint_file)

print("\n--- Retrieval Test ---")


print(f"Shelve fetch 'banana': {retrieve_shelve(shelve_file, 'banana')}")
print(f"Pprint fetch 'banana': {retrieve_pprint(pprint_file, 'banana')}")

if __name__ == "__main__":
main()

DAA (IPCC22IS43) AY 2025-26 Page 20


b) Output:-

c) Detailed Explanation & Analysis :-

The program begins by importing necessary libraries: shelve for persistent storage, pprint for formatting,
Counter for easy counting, and ast for safely evaluating strings back into Python objects. It creates a dummy

text file to ensure data exists. The count_words function reads this file, normalizes the text to lowercase, and
uses Counter to create a frequency dictionary (e.g., {'apple': 3}).

For storage, the program uses two distinct approaches. The save_with_shelve function opens a binary file that
acts like a persistent dictionary. It iterates through the word count dictionary and stores every item directly
onto the disk using keys. Conversely, save_with_pprint converts the dictionary into a human-readable string
format using pformat and writes that string into a standard text file.

For retrieval, the difference in logic is significant. retrieve_shelve simply opens the database and asks for a
specific key. retrieve_pprint, however, must open the text file, read the entire string into memory, and then
use ast.literal_eval to parse that string back into a real Python dictionary before it can look up the key.

Evaluation: Shelve vs. Pprint Efficiency

Shelve is significantly more efficient for retrieval.

1. Access Method:
o Shelve: Acts as a persistent, disk-based hash map. It supports Random Access, meaning it
can jump directly to the specific key you want without loading the entire dataset into RAM.
Its complexity for retrieval is close to $O(1)$.
o Pprint: Stores data as a flat text string. To retrieve even a single item, the program must read
the entire file ($O(N)$) and parse the whole string back into a Python object.

DAA (IPCC22IS43) AY 2025-26 Page 21


2. Serialization:
o Shelve: Uses binary serialization (pickle protocol), which is compact and fast for the machine to read.
o Pprint: Is designed for human readability, not machine speed. Parsing text takes much more CPU power
than loading binary data.

Conclusion: Use pprint when a human needs to read the file (debugging/logging). Use shelve (or databases)
when the program needs to store and retrieve data efficiently.

DAA (IPCC22IS43) AY 2025-26 Page 22


Module-5 (Web Scraping, Data Science Libraries)

Q1) Web Hyperlink Scraper using BeautifulSoup

a) Program :-

import requests
from bs4 import BeautifulSoup

def get_hyperlinks(url):
try:
response = [Link](url)
response.raise_for_status()

soup = BeautifulSoup([Link], '[Link]')


links = []

for tag in soup.find_all('a'):


href = [Link]('href')
if href:
[Link](href)

return links

except [Link] as e:
print(f"Error fetching URL: {e}")
return []

def main():
target_url = "[Link]
all_links = get_hyperlinks(target_url)

print(f"Found {len(all_links)} links:")


for link in all_links[:10]:
print(link)

if __name__ == "__main__":
main()

DAA (IPCC22IS43) AY 2025-26 Page 23


b) Output:-

c) Detailed Explanation & Analysis :-

The program begins by importing the necessary libraries: requests for making HTTP calls and
BeautifulSoup from bs4 for parsing HTML.

The core logic is encapsulated in the get_hyperlinks function. It first executes a [Link](url) call to
download the webpage. The raise_for_status() method is immediately called to check for HTTP errors
(like 404 Not Found or 500 Server Error); if the request was unsuccessful, this raises an exception to halt
execution or trigger error handling.

Once the page content is successfully retrieved, BeautifulSoup parses the raw HTML text. The parser
creates a parse tree, which acts as a navigable structure of the HTML tags. The script uses
soup.find_all('a') to locate every anchor tag (<a>) in the document.

The loop iterates through these anchor tags. Crucially, we access the hyperlink using [Link]('href') rather
than standard dictionary access (tag['href']). This method returns None if the attribute is missing,
preventing the program from crashing on broken tags. Valid links are appended to the links list, which is
returned to the main function for printing.

DAA (IPCC22IS43) AY 2025-26 Page 24


Analysis of Common Scraping Problems :-

When scraping real-world websites, the HTML is rarely perfect. Here are the primary challenges:

A. Malformed HTML Browsers are very forgiving and will render pages even if tags are not closed
properly (e.g., a <div> missing its </div>). However, strict parsers may fail to read this.

• Impact: If the HTML structure is broken, the parser might misinterpret where one element ends
and another begins. This can cause find_all to return fewer items than expected because they are
"swallowed" inside a broken tag.
• Solution: BeautifulSoup is robust because it uses lenient parsers (like [Link] or lxml). These
parsers attempt to "fix" the HTML by guessing where closing tags should be, allowing the scrape
to continue even on poorly coded sites.

B. Missing Tags and Attributes Not all anchor tags contain links. Some might be used as anchors for
jumping to parts of a page (e.g., <a name="top"></a>) or for JavaScript triggers (<a onclick="...">).

• Impact: A naive script accessing tag['href'] directly will crash with a KeyError if the attribute is
missing.
• Solution: Using the .get('href') method is essential. It safely returns None instead of raising an
error, allowing the script to use a simple if href: check to filter out empty or invalid tags.

C. Dynamic Content The requests library only fetches the initial static HTML sent by the server.
• Impact: Many modern sites load links using JavaScript after the page loads (Client-Side
Rendering). In these cases, soup.find_all will find nothing because the tags simply don't exist in
the initial response.
• Solution: This requires advanced tools like Selenium or Playwright that launch a real browser to
execute the JavaScript before scraping.

DAA (IPCC22IS43) AY 2025-26 Page 25


Module-5 (Web Scraping, Data Science Libraries)

Q2) Data Analysis with Pandas & Matplotlib

a) Program :-
import pandas as pd
import [Link] as plt

def main():
data = {
'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Eva'],
'Math': [85, 70, 95, 60, 88],
'Science': [90, 75, 85, 55, 92],
'English': [88, 65, 90, 60, 85]
}
df_temp = [Link](data)
df_temp.to_csv('student_marks.csv', index=False)

df = pd.read_csv('student_marks.csv')

df['Total'] = df[['Math', 'Science', 'English']].sum(axis=1)


df['Average'] = df['Total'] / 3

def get_grade(avg):
if avg >= 90: return 'A'
elif avg >= 80: return 'B'
elif avg >= 70: return 'C'
elif avg >= 60: return 'D'
else: return 'F'

df['Grade'] = df['Average'].apply(get_grade)

print(df)

[Link](figsize=(8, 5))
[Link](df['Name'], df['Average'], color='skyblue')
[Link]('Student Name')
[Link]('Average Marks')
[Link]('Student Performance')
[Link]()

if __name__ == "__main__":
main()

DAA (IPCC22IS43) AY 2025-26 Page 26


b) Output:-

c) Graph : -

Fig 1 : Bar Graph representing Student average marks

DAA (IPCC22IS43) AY 2025-26 Page 27


d) Detailed Explanation & Analysis :-

Explanation : -

The program begins by importing the pandas library for data manipulation and [Link] for
visualization. Although the problem implies reading an existing CSV, the code includes a block to generate
a sample student_marks.csv file first to ensure the program runs successfully in any environment.

The core logic uses pd.read_csv to load the data into a DataFrame, which is the central data structure in
Pandas (essentially a programmable Excel sheet).

Calculations are performed using vectorized operations. Instead of looping through each student row by
row (as you would with standard Python lists), df['Math'] + df['Science'] adds the entire columns together
in a single step to compute the 'Total'. Similarly, division by 3 computes the 'Average' for the whole column
instantly.

For the 'Grade' classification, we define a helper function get_grade containing the conditional logic (A, B,
C, etc.). The .apply() method is then used to map this function to every value in the 'Average' column.

Finally, [Link] creates a bar chart using the 'Name' column for the x-axis and the computed 'Average'
column for the y-axis, providing a visual comparison of student performance.

Evaluation: Pandas vs. Pure Python Lists : -

1. Vectorization vs. Iteration

Pure Python: To calculate the total marks for 1,000 students using lists, you must write a for loop that
iterates 1,000 times, extracting and adding values one by one. This is slow and verbose.

Pandas: Uses Vectorization. Operations like df['col1'] + df['col2'] are applied to the entire array at once
using highly optimized C-code in the background. This is significantly faster and requires much less
code.

DAA (IPCC22IS43) AY 2025-26 Page 28


2. Data Alignment
Pure Python: You often have separate lists for Names, Math scores, and Science scores.
You must carefully manage indices (e.g., ensuring names[i] corresponds to math[i]).
Pandas: The DataFrame keeps all data aligned automatically. When you sort or filter the data
(e.g., remove failing students), the corresponding names and marks stay together without extra logic.

3. Built-in Functionality

Pandas comes with built-in methods for file I/O (read_csv, to_excel), statistics (mean, describe),
and handling missing data (dropna), which would require writing extensive, error-prone utility
functions if done with pure Python lists.

DAA (IPCC22IS43) AY 2025-26 Page 29

You might also like