Navigation

CRUD List CRUD Array CRUD Dictionary String Opperations Recursion and Iterative File i/o Number Base Conversion (Iterative) Number Base Conversion (Recursive) Data Validation Search Algorithms Sort Algorithms Object Oriented Programming Linked List Stack and Queue Binary Search Tree Hash Tables SQL workflow NoSQL (MongoDB) workflow Socket Programming Flask workflow

Practical Notes for Alevels

I have compiled all the code into a single page for quick viewing of chapters


CRUD Operations on a List

listA = [] #create items 1 - 10 in listA using for loop for i in range(10): listA.append(i+1) #read - find index of 3 in listA print(listA) #update - change value at an index in listA listA[0] = 2 print(listA) #delete - remove a value from the list listA.pop() #deletes last value by default print(listA) listA.pop(0) #deletes a value at an index print(listA) --------------------------------------- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] [2, 2, 3, 4, 5, 6, 7, 8, 9, 10] [2, 2, 3, 4, 5, 6, 7, 8, 9] [2, 3, 4, 5, 6, 7, 8, 9]

CRUD Operations on a Fixed-Size Array

# Create an array size = 5 # the array can only store 5 variables arr = [None] * size # Create items 1 - 5 in arr using a for loop for i in range(5): arr[i] = i + 1 # Read - Print the array print(arr) # Update - Change value at index 0 arr[0] = 99 print(arr) # Delete - Remove the last element by setting it to None arr[-1] = None print(arr) # Delete - Remove the value at index 0 arr[0] = None print(arr) --------------------------------------- [1, 2, 3, 4, 5] [99, 2, 3, 4, 5] [99, 2, 3, 4, None] [None, 2, 3, 4, None]

CRUD Operations on a Dictionary

Create

student = {'name': 'Tom', 'age': 17, 'class': '24/12', 'subjects': ['math', 'computing', 'physics', 'economics']} print(student) --------------------------------------- {'name': 'Tom', 'age': 17, 'class': '24/12', 'subjects': ['math', 'computing', 'physics', 'economics']}

Read

print(student.get('age')) # Output: 17 print(student.get('id')) # Output: None --------------------------------------- 17 None

Update

student['class'] = '24/13' # Update class student['subjects'][1] = 'biology' # Update subject print(student) --------------------------------------- {'name': 'Tom', 'age': 17, 'class': '24/13', 'subjects': ['math', 'biology', 'physics', 'economics']}

Delete

del student['age'] student.pop('subjects') print(student) --------------------------------------- {'name': 'Tom', 'class': '24/13'}

Insert

student['teacher'] = 'Mr Tan' print(student) --------------------------------------- {'name': 'Tom', 'class': '24/13', 'teacher': 'Mr Tan'}

Dictionary Operations Summary

print(student.keys()) print(student.values()) print(student.items()) print(len(student)) --------------------------------------- dict_keys(['name', 'class', 'teacher']) dict_values(['Tom', '24/13', 'Mr Tan']) dict_items([('name', 'Tom'), ('class', '24/13'), ('teacher', 'Mr Tan')]) 3

String Operations

1. String Creation

string1 = 'Hello' string2 = "World" string3 = '''This is a multiline string''' print(string1) print(string2) print(string3) --------------------------------------- Hello World This is a multiline string

2. Accessing Characters

string = "Python" print(string[0]) # Output: P print(string[-1]) # Output: n --------------------------------------- P n

3. String Slicing

string = "Python" print(string[0:3]) # Output: Pyt print(string[:3]) # Output: Pyt print(string[3:]) # Output: hon --------------------------------------- Pyt Pyt hon

4. String Concatenation

string1 = "Hello" string2 = "World" result = string1 + " " + string2 print(result) # Output: Hello World --------------------------------------- Hello World

5. String Multiplication

string = "Hello" print(string * 3) # Output: HelloHelloHello --------------------------------------- HelloHelloHello

6. String Length

string = "Python" print(len(string)) # Output: 6 --------------------------------------- 6

7. Using Loops

string = "Python" for char in string: print(char) --------------------------------------- P y t h o n

8. Changing Case

string = "Python Programming" print(string.upper()) # Output: PYTHON PROGRAMMING print(string.lower()) # Output: python programming print(string.title()) # Output: Python Programming print(string.capitalize()) # Output: Python programming print(string.swapcase()) # Output: pYTHON pROGRAMMING --------------------------------------- PYTHON PROGRAMMING python programming Python Programming Python programming pYTHON pROGRAMMING

9. Stripping Whitespace

string = " Hello World " print(string.strip()) # Output: Hello World #Optional print(string.lstrip()) # Output: Hello World print(string.rstrip()) # Output: Hello World --------------------------------------- Hello World Hello World Hello World

10. Splitting and Joining Strings

string = "Python,Java,C++" print(string.split(",")) # Output: ['Python', 'Java', 'C++'] words = ["Python", "is", "fun"] print(" ".join(words)) # Output: Python is fun --------------------------------------- ['Python', 'Java', 'C++'] Python is fun

11. Replacing Substrings

string = "I like Java" print(string.replace("Java", "Python")) # Output: I like Python --------------------------------------- I like Python

12. Finding Substrings

string = "Python Programming" print(string.find("Programming")) # Output: 7 print(string.find("Java")) # Output: -1 --------------------------------------- 7 -1

13. String Formatting

name = "Alice" age = 25 print(f"My name is {name} and I am {age} years old.") print("My name is {} and I am {} years old.".format(name, age)) --------------------------------------- My name is Alice and I am 25 years old. My name is Alice and I am 25 years old.

14. String Validation

string = "Python123" print(string.isalpha()) # Output: False print(string.isdigit()) # Output: False print(string.isalnum()) # Output: True --------------------------------------- False False True

15. Reversing a String

string = "Python" print(string[::-1]) # Output: nohtyP --------------------------------------- nohtyP

Recursive vs Iterative Approaches

1. What is Recursion?

Recursion is a method where a function calls itself to solve a problem. It breaks the problem into smaller sub-problems until reaching a base case.

2. What is Iteration?

Iteration uses loops (e.g., for, while) to repeatedly execute a block of code until a condition is met.

3. Comparison: Factorial Calculation

Recursive Implementation

def factorial_recursive(n): if n == 0 or n == 1: return 1 # Base case else: return n * factorial_recursive(n - 1) # Recursive case # main print(factorial_recursive(5)) # Output: 120 --------------------------------------- 120

Iterative Implementation

def factorial_iterative(n): result = 1 for i in range(1, n + 1): result *= i return result # main print(factorial_iterative(5)) # Output: 120 --------------------------------------- 120

4. Comparison: Fibonacci Calculation

Recursive Implementation

def fibonacci_recursive(n): if n == 0: return 0 elif n == 1: return 1 else: return fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2) # main print(fibonacci_recursive(6)) # Output: 8 --------------------------------------- 8

Iterative Implementation

def fibonacci_iterative(n): if n == 0: return 0 elif n == 1: return 1 a, b = 0, 1 for _ in range(2, n + 1): a, b = b, a + b return b # main print(fibonacci_iterative(6)) # Output: 8 --------------------------------------- 8

5. Key Differences


File I/O Operations

1. Reading from a Text File

with open('filename.txt', 'r') as infile: lines = infile.readlines() for line in lines: print(line)

2. Reading from a CSV File

import csv with open('filename.csv', 'r') as infile: lines = csv.reader(infile) next(lines) # Skip the header row for line in lines: print(line)

3. Reading from a JSON File

import json with open('filename.json', 'r') as infile: data = json.load(infile) for item in data: print(item)

4. Reading Text File Data

people.txt 1. 01John Doe 24/12 2. 02Jane Smith 25/13

with open('people.txt', 'r') as infile: lines = infile.readlines() print(lines) for line in lines: student_id = line[0:2] name = line[2:22] classid = line[22:].strip() print(student_id) print(name) print(classid) --------------------------------------- 01 John Doe 24/12 02 Jane Smith 25/13

5. Writing to a Text File

with open('result.txt', 'w') as outfile: student_id = "03" name = 'Ali' classid = '24/12' outfile.write(f'{student_id}{name:20}{classid}\n')

result.txt 1. 03Ali 24/12

6. Appending to a Text File

with open('result.txt', 'a') as outfile: student_id = "04" name = 'Jack' classid = '25/12' outfile.write(f'{student_id}{name:20}{classid}\n')

result.txt 1. 03Ali 24/12 2. 04Jack 25/12

Iterative Number Base Conversions

1. Decimal to Binary

def decimal_to_binary(dec_num): bin_num = '' while dec_num > 0: remainder = dec_num % 2 bin_num = str(remainder) + bin_num dec_num = dec_num // 2 return bin_num # main print(decimal_to_binary(1114)) # Output: 10001011010 --------------------------------------- 10001011010

2. Decimal to Hexadecimal

def decimal_to_hexadecimal(dec_num): dec_hex_map = {1:'1', 2:'2', 3:'3', 4:'4', 5:'5', 6:'6', 7:'7', 8:'8', 9:'9', \ 10:'A', 11:'B', 12:'C', 13:'D', 14:'E', 15:'F'} hex_num = '' while dec_num > 0: remainder = dec_num % 16 hex_num = str(dec_hex_map[remainder]) + hex_num dec_num = dec_num // 16 return hex_num # main print(decimal_to_hexadecimal(1114)) # Output: 45A --------------------------------------- 45A

3. Decimal to Base n, where n is an integer

def decimal_to_base(dec_num, n): num = '' while dec_num > 0: remainder = dec_num % n num = str(remainder) + num dec_num = dec_num // n return num # main print(decimal_to_base(1114, 8)) # Output: 2132 --------------------------------------- 2132

4. Binary to Decimal

def binary_to_decimal(bin_num): dec_num = 0 for i in range(len(bin_num)): digit = bin_num[len(bin_num) - 1 - i] dec_num += int(digit) * (2 ** i) return dec_num # main print(binary_to_decimal('10001011010')) # Output: 1114 --------------------------------------- 1114

5. Hexadecimal to Decimal

def hexadecimal_to_decimal(hex_num): hex_dec_map = {'1':1, '2':2, '3':3, '4':4, '5':5, '6':6, '7':7, '8':8, '9':9, \ 'A':10, 'B':11, 'C':12, 'D':13, 'E':14, 'F':15} dec_num = 0 for i in range(len(hex_num)): digit = hex_num[len(hex_num) - 1 - i] dec_num += int(hex_dec_map[digit]) * (16 ** i) return dec_num # main print(hexadecimal_to_decimal('45A')) # Output: 1114 --------------------------------------- 1114

You only need these 5 as you can use it to convert from one base to denary to another base. For example: binary to decimal to hexadecimal

6. Binary to Hexadecimal (OPTIONAL)

def binary_to_hexadecimal(bin_num): bin_hex_map = {'0000': '0', '0001': '1', '0010': '2', '0011': '3', \ '0100': '4', '0101': '5', '0110': '6', '0111': '7', \ '1000': '8', '1001': '9', '1010': 'A', '1011': 'B', \ '1100': 'C', '1101': 'D', '1110': 'E', '1111': 'F'} bin_num = bin_num.zfill((len(bin_num) + 3) // 4 * 4) hex_num = '' for i in range(0, len(bin_num), 4): hex_num += bin_hex_map[bin_num[i:i+4]] return hex_num # main print(binary_to_hexadecimal('10001011010')) # Output: 45A --------------------------------------- 45A

7. Hexadecimal to Binary (OPTIONAL)

def hexadecimal_to_binary(hex_num): hex_bin_map = {'0': '0000', '1': '0001', '2': '0010', '3': '0011', \ '4': '0100', '5': '0101', '6': '0110', '7': '0111', \ '8': '1000', '9': '1001', 'A': '1010', 'B': '1011', \ 'C': '1100', 'D': '1101', 'E': '1110', 'F': '1111'} bin_num = ''.join(hex_bin_map[digit.upper()] for digit in hex_num) return bin_num # main print(hexadecimal_to_binary('45A')) # Output: 10001011010 --------------------------------------- 10001011010

Recursive Number Base Conversions

1. Decimal to Binary (Recursive)

def decimal_to_binary_recursive(dec_num): if dec_num == 0: return '' else: return decimal_to_binary_recursive(dec_num // 2) + str(dec_num % 2) # main print(decimal_to_binary_recursive(1114)) # Output: 10001011010 --------------------------------------- 10001011010

2. Decimal to Hexadecimal (Recursive)

dec_hex_map = {1:'1', 2:'2', 3:'3', 4:'4', 5:'5', 6:'6', 7:'7', 8:'8', 9:'9', \ 10:'A', 11:'B', 12:'C', 13:'D', 14:'E', 15:'F'} def decimal_to_hexadecimal_recursive(dec_num): if dec_num == 0: return '' else: remainder = dec_num % 16 return decimal_to_hexadecimal_recursive(dec_num // 16) + str(dec_hex_map[remainder]) # main print(decimal_to_hexadecimal_recursive(1114)) # Output: 45A --------------------------------------- 45A

3. Binary to Decimal (Recursive)

def binary_to_decimal_recursive(bin_num): if len(bin_num) == 0: return 0 else: digit = bin_num[0] return int(digit) * (2 ** (len(bin_num) - 1)) + binary_to_decimal_recursive(bin_num[1:]) # main print(binary_to_decimal_recursive('10001011010')) # Output: 1114 --------------------------------------- 1114

4. Hexadecimal to Decimal (Recursive)

hex_dec_map = {'1':1, '2':2, '3':3, '4':4, '5':5, '6':6, '7':7, '8':8, '9':9, \ 'A':10, 'B':11, 'C':12, 'D':13, 'E':14, 'F':15} def hexadecimal_to_decimal_recursive(hex_num): if len(hex_num) == 0: return 0 else: digit = hex_num[0] return int(hex_dec_map[digit]) * (16 ** (len(hex_num) - 1)) + hexadecimal_to_decimal_recursive(hex_num[1:]) # main print(hexadecimal_to_decimal_recursive('45A')) # Output: 1114 --------------------------------------- 1114

5. Binary to Hexadecimal (Recursive)

bin_hex_map = {'0000': '0', '0001': '1', '0010': '2', '0011': '3', \ '0100': '4', '0101': '5', '0110': '6', '0111': '7', \ '1000': '8', '1001': '9', '1010': 'A', '1011': 'B', \ '1100': 'C', '1101': 'D', '1110': 'E', '1111': 'F'} def binary_to_hexadecimal_recursive(bin_num): padding = len(bin_num) % 4 if padding != 0: bin_num = '0' * (4 - padding) + bin_num if len(bin_num) == 0: return '' else: group = bin_num[-4:] return binary_to_hexadecimal_recursive(bin_num[:-4]) + bin_hex_map[group] # main print(binary_to_hexadecimal_recursive('10001011010')) # Output: 45A --------------------------------------- 45A

6. Hexadecimal to Binary (Recursive)

hex_to_bin_map = {'0': '0000', '1': '0001', '2': '0010', '3': '0011', \ '4': '0100', '5': '0101', '6': '0110', '7': '0111', \ '8': '1000', '9': '1001', 'A': '1010', 'B': '1011', \ 'C': '1100', 'D': '1101', 'E': '1110', 'F': '1111'} def hexadecimal_to_binary_recursive(hex_num): if len(hex_num) == 0: return '' else: return hex_to_bin_map[hex_num[0].upper()] + hexadecimal_to_binary_recursive(hex_num[1:]) # main print(hexadecimal_to_binary_recursive('45A')) # Output: 10001011010 --------------------------------------- 10001011010

Data Validation Techniques

1. Presence Check

data = input("Enter your name: ") if data.strip() == "": print("Error: This field cannot be empty.") else: print("Valid input!")

2. Length Check

data = input("Enter a password (min 8 characters): ") if len(data) < 8: print("Error: Password must be at least 8 characters long.") else: print("Valid password!")

3. Data Type Check

data = input("Enter a number: ") if data.isdigit(): print("Valid input!") else: print("Error: Input must be a number.")

4. Format Check

data = input("Enter an email address: ") symbol_count = 0 for char in data: if char = '@': symbol_count += 1 if count = 1: print("Valid email!") else: print("Error: Invalid email format.") print("Error: Invalid email format.")

5. Range Check

data = int(input("Enter a number between 1 and 100: ")) if 1 <= data <= 100: print("Valid input!") else: print("Error: Number out of range.")

6. Existence Check

valid_users = ["Alice", "Bob", "Charlie"] data = input("Enter your username: ") if data in valid_users: print("Valid username!") else: print("Error: Username does not exist.")

7. Check Digit

# Example: Simple Mod-10 Check data = "12345678" check_digit = int(data[-1]) calculated_digit = sum(int(digit) for digit in data[:-1]) % 10 if check_digit == calculated_digit: print("Valid check digit!") else: print("Error: Invalid check digit.")

1. Linear Search

def linear_search(A, target): for i in range(len(A)): if A[i] == target: # found return i return "not found" # not found # main data = [6, 2, 5, 1, 7, 10, 9, 3] print(linear_search(data, 1)) # success print(linear_search(data, 4)) # failure --------------------------------------- 3 "not found"

2. Binary Search

Iterative Binary Search

def binary_search(A, target): low = 0 high = len(A) - 1 while low <= high: mid = (low + high) // 2 if A[mid] == target: # found return mid elif A[mid] > target: # left half high = mid - 1 else: # right half low = mid + 1 return "not found" # main data = [1, 2, 3, 5, 6, 7, 9, 10] print(binary_search(data, 7)) # success print(binary_search(data, 8)) # failure --------------------------------------- 5 "not found"

Recursive Binary Search

def binary_searchr(A, target, low, high): if low > high: return -1 # not found mid = (low + high) // 2 if A[mid] == target: return mid elif target < A[mid]: return binary_searchr(A, target, low, mid - 1) # left half else: return binary_searchr(A, target, mid + 1, high) # right half # main data = [6, 8, 12, 17, 19] print(binary_searchr(data, 17, 0, len(data)-1)) # success print(binary_searchr(data, 13, 0, len(data)-1)) # failure --------------------------------------- 3 -1

Sort Algorithms

1. Bubble Sort


Non-Optimized Bubble Sort

def bubble_sort(A): n = len(A) for i in range(n): for j in range(n-1): if A[j] > A[j+1]: A[j], A[j+1] = A[j+1], A[j] return A # main data = [4, 7, 1, 5, 2, 8, 3, 6] print(bubble_sort(data)) --------------------------------------- [1, 2, 3, 4, 5, 6, 7, 8]

Optimized Bubble Sort

def bubble_sort(A): n = len(A) for i in range(n): swapped = False for j in range(n-1): if A[j] > A[j+1]: A[j], A[j+1] = A[j+1], A[j] swapped = True if not swapped: # no swaps -> already sorted break return A # main data = [4, 7, 1, 5, 2, 8, 3, 6] print(bubble_sort(data)) --------------------------------------- [1, 2, 3, 4, 5, 6, 7, 8]

2. Insertion Sort

Insertion Sort With key

def insertion_sort(A): for i in range(1, len(A)): key = A[i] j = i - 1 while j >= 0 and key < A[j]: A[j+1] = A[j] j -= 1 A[j+1] = key return A # main data = [4, 7, 1, 5, 2, 8, 3, 6] print(insertion_sort(data)) --------------------------------------- [1, 2, 3, 4, 5, 6, 7, 8]

Alternative Insertion Sort

def insertion_sort(A): for i in range(1, len(A)): j = i while A[j-1] > A[j] and j > 0: A[j-1], A[j] = A[j], A[j-1] j -= 1 return A # main data = [4, 7, 1, 5, 2, 8, 3, 6] print(insertion_sort(data)) --------------------------------------- [1, 2, 3, 4, 5, 6, 7, 8]

3. Quick Sort

With List Comprehension

def quicksort(A): if len(A) <= 1: return A pivot = A[len(A) // 2] left = [x for x in A if x < pivot] middle = [x for x in A if x == pivot] right = [x for x in A if x > pivot] return quicksort(left) + middle + quicksort(right) # main data = [4, 7, 1, 5, 2, 8, 3] print(quicksort(data)) --------------------------------------- [1, 2, 3, 4, 5, 6, 7, 8]

Without List Comprehension

def quicksort(A): if len(A) <= 1: # terminating cases (0 or 1 item) return A pivot = A[len(A) // 2] # midpoint left = [] middle = [] right = [] for element in A: if element < pivot: left.append(element) elif element == pivot: middle.append(element) else: right.append(element) return quicksort(left) + middle + quicksort(right) # recursive cases # main data = [4, 7, 1, 5, 2, 8, 3] print(quicksort(data)) --------------------------------------- [1, 2, 3, 4, 5, 6, 7, 8]

Without List Comprehension Simplified

def quicksort(A): if len(A) <= 1: # terminating cases (0 or 1 item) return A pivot = A[len(A) // 2] # midpoint left = [] # items less than pivot right = [] # items more than pivot for element in A: if element < pivot: left.append(element) elif element > pivot: right.append(element) return quicksort(left) + [pivot] + quicksort(right) # recursive cases # main data = [4, 7, 1, 5, 2, 8, 3] print(quicksort(data)) --------------------------------------- [1, 2, 3, 4, 5, 6, 7, 8]

4. Merge Sort

Iterative Merge Sort

def merge_sorti(A): n = len(A) if n <= 1: return A left = merge_sorti(A[:n//2]) right = merge_sorti(A[n//2:]) i = j = k = 0 sorted_list = [None] * n while i < len(left) and j < len(right): if left[i] < right[j]: sorted_list[k] = left[i] i += 1 else: sorted_list[k] = right[j] j += 1 k += 1 while i < len(left): sorted_list[k] = left[i] i += 1 k += 1 while j < len(right): sorted_list[k] = right[j] j += 1 k += 1 return sorted_list # main data = [4, 7, 1, 5, 2, 8, 3, 6] print(merge_sorti(data)) --------------------------------------- [1, 2, 3, 4, 5, 6, 7, 8]

Recursive Merge Sort

def merge_sortr(A): if len(A) <= 1: return A mid = len(A) // 2 left = merge_sortr(A[:mid]) right = merge_sortr(A[mid:]) return merge(left, right) def merge(left, right): merged_list = [] i = j = 0 while i < len(left) and j < len(right): if left[i] < right[j]: merged_list.append(left[i]) i += 1 else: merged_list.append(right[j]) j += 1 merged_list += left[i:] merged_list += right[j:] return merged_list # main data = [4, 7, 1, 5, 2, 8, 3, 6] print(merge_sortr(data)) --------------------------------------- [1, 2, 3, 4, 5, 6, 7, 8]

Object-Oriented Programming (OOP)

Object-Oriented Programming is a paradigm that uses objects and classes to structure and organize code. Below are the fundamental concepts of OOP with examples:

1. Class and Object

class Car: def __init__(self, brand, speed): self.brand = brand self.speed = speed def drive(self): return f"{self.brand} is driving at {self.speed} km/h." # Main car1 = Car("Toyota", 120) print(car1.drive()) --------------------------------------- Toyota is driving at 120 km/h.

2. Encapsulation

class BankAccount: def __init__(self, balance): self.__balance = balance # Private attribute def deposit(self, amount): self.__balance += amount def get_balance(self): return self.__balance # Main account = BankAccount(1000) account.deposit(500) print(account.get_balance()) --------------------------------------- 1500

3. Inheritance

class Animal: def __init__(self, name): self.name = name def speak(self): return f"{self.name} makes a sound." class Dog(Animal): def speak(self): return f"{self.name} barks." # Main dog = Dog("Buddy") print(dog.speak()) --------------------------------------- Buddy barks.

4. Polymorphism

class Bird: def fly(self): return "Birds can fly." class Penguin(Bird): def fly(self): return "Penguins cannot fly." # Main animals = [Bird(), Penguin()] for animal in animals: print(animal.fly()) --------------------------------------- Birds can fly. Penguins cannot fly.

Linked List

A linked list is a linear data structure in which elements (nodes) are connected using pointers.

Below are key operations and examples:

1. Structure of a Node

class Node: def __init__(self, data, pointer=None): self.__data = data self.__pointer = pointer def getPointer(self): return self.__pointer def setPointer(self, pointer): self.__pointer = pointer def getData(self): return self.__data def setData(self, data): self.__data = data

2. Operations in a Linked List

a) Inserting at the Head

Adds a new node at the beginning of the list.

class LinkedList: def insertHead(self, data): newnode = Node(data) if self.isEmpty(): self.__head = newnode else: newnode.setPointer(self.__head) self.__head = newnode

b) Inserting at the Tail

Adds a new node at the end of the list.

def insertTail(self, data): newnode = Node(data) if self.isEmpty(): self.__head = newnode else: curr = self.__head while curr.getPointer() != None: curr = curr.getPointer() curr.setPointer(newnode)

c) Deleting the Head

Removes the first node in the list.

def deleteHead(self): if self.isEmpty(): print(f'Nothing to delete') else: self.__head = self.__head.getPointer()

d) Calculating the Length

Counts the number of nodes in the list.

def length(self): count = 0 curr = self.__head while curr != None: count += 1 curr = curr.getPointer() return count

e) Displaying the List

Traverses and prints all nodes in the list.

def display(self): if self.isEmpty(): print(f'List is Empty') else: curr = self.__head while curr != None: print(f'{curr.getData()}') curr = curr.getPointer()

3. Example Usage

Below is an example demonstrating how to use the LinkedList class:

# Main linked_list = LinkedList() # Insert nodes linked_list.insertHead(10) linked_list.insertTail(20) linked_list.insertTail(30) # Display list linked_list.display() # Delete head linked_list.deleteHead() linked_list.display() # Length of list print(f'Length: {linked_list.length()}') --------------------------------------- 10 20 30 20 30 Length: 2

Stack and Queue

Stacks and Queues are fundamental data structures. They are used for managing collections of data with specific rules:

1. Stack

a) Example Using Python Built-in List

x = [] x.append(2) # First In x.append(5) x.append(1) x.append(3) x.append(9) print(x) # Output: [2, 5, 1, 3, 9] x.pop() # Last Out print(x) # Output: [2, 5, 1, 3]

b) Implementing Stack Class

class Stack: def __init__(self): self.items = [] def is_empty(self): return len(self.items) == 0 def push(self, item): self.items.append(item) def pop(self): if not self.is_empty(): return self.items.pop() else: raise IndexError("pop from empty stack") def peek(self): if not self.is_empty(): return self.items[-1] else: raise IndexError("peek from empty stack") def size(self): return len(self.items) # Stack example stack = Stack() stack.push(1) stack.push(2) stack.push(3) print(stack.pop()) # Output: 3 print(f'Value of last element: {stack.peek()}') # Output: 2 print(f'Size of stack: {stack.size()}') # Output: 2 --------------------------------------- 3 Value of last element: 2 Size of stack: 2

2. Queue

a) Example Using Python Built-in List

x = [] x.append(2) # First In x.append(5) x.append(1) x.append(3) x.append(9) print(x) # Output: [2, 5, 1, 3, 9] x.pop(0) # First Out print(x) # Output: [5, 1, 3, 9]

b) Implementing Queue Class

class Queue: def __init__(self): self.items = [] def is_empty(self): return len(self.items) == 0 def enqueue(self, item): self.items.append(item) def dequeue(self): if not self.is_empty(): return self.items.pop(0) else: raise IndexError("dequeue from empty queue") def front(self): if not self.is_empty(): return self.items[0] else: raise IndexError("front from empty queue") def size(self): return len(self.items) # Queue example queue = Queue() queue.enqueue('a') queue.enqueue('b') queue.enqueue('c') print(queue.dequeue()) # Output: 'a' print(f'Value of first element: {queue.front()}') # Output: 'b' print(f'Size of queue: {queue.size()}') # Output: 2 --------------------------------------- 'a' Value of first element: b Size of queue: 2

Binary Search Tree (BST) Implementations

1. Iterative BST Implementation


class Node: def __init__(self, data): self.data = data self.left = None self.right = None class BST: def __init__(self): self.root = None def insert(self, data): node = Node(data) if self.root is None: self.root = node return else: cur = self.root while cur: if cur.data > data: if cur.left is None: cur.left = node return else: cur = cur.left else: if cur.right is None: cur.right = node return else: cur = cur.right def search(self, data): if self.root is None: return 'empty bst' else: cur = self.root while cur: if cur.data == data: return True elif cur.data > data: if cur.left is None: return False else: cur = cur.left else: if cur.right is None: return False else: cur = cur.right def inorder(self): def helper(node): if node: helper(node.left) print(node.data, end=' ') helper(node.right) helper(self.root) print() def preorder(self): def helper(node): if node: print(node.data, end=' ') helper(node.left) helper(node.right) helper(self.root) print() def postorder(self): def helper(node): if node: helper(node.left) helper(node.right) print(node.data, end=' ') helper(self.root) print() def reverse(self): def helper(node): if node: helper(node.right) helper(node.left) print(node.data, end=' ') helper(self.root) print() def maximum(self): cur = self.root while cur: if cur.right is None: return cur.data else: cur = cur.right def minimum(self): cur = self.root while cur: if cur.left is None: return cur.data else: cur = cur.left bst = BST() values = [50, 30, 80, 10, 40, 90, 60] for value in values: bst.insert(value) print(bst.search(10)) # True print(bst.search(70)) # False bst.inorder() # 10 30 40 50 60 80 90 bst.preorder() # 50 30 10 40 80 60 90 bst.postorder() # 10 40 30 60 90 80 50 bst.reverse() # 90 80 60 50 40 30 10 print(bst.maximum()) # 90 print(bst.minimum()) # 10 --------------------------------------- True False 10 30 40 50 60 80 90 50 30 10 40 80 60 90 10 40 30 60 90 80 50 90 80 60 50 40 30 10 90 10

2. Recursive BST Implementation


class BST: def __init__(self, data): self.data = data self.left = None self.right = None def insert(self, value): if value < self.data: if self.left is None: self.left = BST(value) else: self.left.insert(value) else: if self.right is None: self.right = BST(value) else: self.right.insert(value) def search(self, target): if self.data == target: return "Found" elif target < self.data: if self.left is None: return "Not found" else: return self.left.search(target) else: if self.right is None: return "Not found" else: return self.right.search(target) def inorder(self): if self.left: self.left.inorder() print(self.data, end=' ') if self.right: self.right.inorder() def preorder(self): print(self.data, end=' ') if self.left: self.left.preorder() if self.right: self.right.preorder() def postorder(self): if self.left: self.left.postorder() if self.right: self.right.postorder() print(self.data, end=' ') def minimum(self): if self.left is None: print(self.data) else: self.left.minimum() def maximum(self): if self.right is None: print(self.data) else: self.right.maximum() # main bst = BST(50) values = [30, 80, 10, 40, 90, 60] for value in values: bst.insert(value) print(bst.search(10)) # Found print(bst.search(70)) # Not found bst.inorder() # 10 30 40 50 60 80 90 print() bst.preorder() # 50 30 10 40 80 60 90 print() bst.postorder() # 10 40 30 60 90 80 50 print() bst.minimum() # 10 bst.maximum() # 90 --------------------------------------- Found Not found 10 30 40 50 60 80 90 50 30 10 40 80 60 90 10 40 30 60 90 80 50 10 90

3. Array-Based BST Implementation


class BST: def __init__(self, size): self.size = size self.tree = [None] * size self.root = 0 def insert(self, value, index=0): if index >= self.size: print(f"Cannot insert {value}: Index {index} out of bounds") return if self.tree[index] is None: self.tree[index] = value elif value < self.tree[index]: self.insert(value, 2 * index + 1) else: self.insert(value, 2 * index + 2) def search(self, target, index=0): if index >= self.size or self.tree[index] is None: return "Not found" if self.tree[index] == target: return "Found" elif target < self.tree[index]: return self.search(target, 2 * index + 1) else: return self.search(target, 2 * index + 2) def inorder(self, index=0): if index < self.size and self.tree[index] is not None: self.inorder(2 * index + 1) print(self.tree[index], end=' ') self.inorder(2 * index + 2) def preorder(self, index=0): if index < self.size and self.tree[index] is not None: print(self.tree[index], end=' ') self.preorder(2 * index + 1) self.preorder(2 * index + 2) # main bst = BST(7) values = [50, 30, 70, 20, 40, 60, 90] for value in values: bst.insert(value) print(bst.search(10)) # Not found print(bst.search(70)) # Found bst.inorder() # 20 30 40 50 60 70 90 print() bst.preorder() # 50 30 20 40 70 60 90 print() --------------------------------------- Not found Found 20 30 40 50 60 70 90 50 30 20 40 70 60 90

Hash Table

1. Pigeonhole Principle

The Pigeonhole Principle states that if n pigeons are placed into m pigeonholes, with n > m, at least one pigeonhole must hold more than one pigeon.

A hash table is an application of this principle.


2. What is a Hash Table?

A Hash Table is a data structure that maps keys to values using a hash function. It condenses a large address space into a smaller, manageable index range, enabling efficient data retrieval.

An example of a hashing function:

def hash(key): ''' transform/map key to value (index) ''' total = 0 for char in key: total += ord(char) return total % N

% N confines the index within a valid range. The choice of N should consider the growth of the hash table.


3. Collisions and Resolution Strategies

A collision occurs when two different keys hash to the same index. This can lead to non-optimal performance and requires strategies to handle collisions:


4. Hash Table with Linear Probing

This implementation resolves collisions using linear probing.

def hash(key): return key % N def insert(key, value): index = hash(key) if hash_table[index] == -1: # available hash_table[index] = value # no collision else: # collision i = (index + 1) % N while hash_table[i] != -1: # find next free slot i = (i + 1) % N # linear probing hash_table[i] = value # insert collided record def search(key, target): index = hash(key) if hash_table[index] == -1: return "Record not found" elif hash_table[index] != target: # collided record i = (index + 1) % N while hash_table[i] != -1: if hash_table[i] == target: return "Found in collided records" i = (i + 1) % N return "Not found in collided records" else: return hash_table[index] # non-collided record # main N = 10 hash_table = [-1 for i in range(N)] records = {1234: 'Tom', 651: 'Mary', 238: 'Ali'} for key, value in records.items(): insert(key, value) print(hash_table)

Output after inserting and searching records will demonstrate linear probing.


5. Hash Table with Quadratic Probing

This implementation resolves collisions using quadratic probing.

def hash(key): return key % N def insert(key, value): index = hash(key) if hash_table[index] == -1: # available hash_table[index] = value # no collision else: # collision i = (index + 1) % N k = 2 while hash_table[i] != -1 and k < N: i = (i + k**2) % N # quadratic probing k += 1 hash_table[i] = value # insert collided record # main N = 10 hash_table = [-1 for i in range(N)] records = {1234: 'Tom', 651: 'Mary', 238: 'Ali'} for key, value in records.items(): insert(key, value) print(hash_table)

Quadratic probing helps reduce primary clustering.


6. Hash Table with Chaining

This implementation resolves collisions by using linked lists.

class Node: def __init__(self, data=None): self.data = None self.next = None def chain_insert(key, value): index = hash(key) if hash_table[index].data is None: hash_table[index].data = value else: curr = hash_table[index] while curr.next: curr = curr.next curr.next = Node(value) # main N = 10 hash_table = [Node() for i in range(N)] records = {1234: 'Tom', 651: 'Mary', 238: 'Ali'} for key, value in records.items(): chain_insert(key, value) print(chain_search(1234, 'Tom')) # Original record print(chain_search(7654, 'Alex')) # Collided record

Chaining provides a flexible way to handle collisions but uses additional memory.


7. Hashing Function for Strings

A hashing function that computes the sum of ASCII values modulo a number.

def hash(key): total = 0 for char in key: total += ord(char) return total % 13 # main print(hash("Good morning!")) print(hash("How are you?"))

This demonstrates how strings can be hashed effectively.


SQL Workflow

1. Setting Up SQLite3 Database

The following Python code demonstrates how to set up and interact with an SQLite3 database.

# Import modules import sqlite3 # Connect to database (or create it if it does not exist) conn = sqlite3.connect('school.db') # Create cursor object to interact with the database cur = conn.cursor() # Create table cur.execute('''CREATE TABLE IF NOT EXISTS students(sid INTEGER PRIMARY KEY, name TEXT NOT NULL, classid TEXT, phone TEXT UNIQUE);''') cur.execute('''CREATE TABLE IF NOT EXISTS Teachers(tid INTEGER PRIMARY KEY, tname TEXT NOT NULL, subject TEXT NOT NULL, classid TEXT salary REAL NOT NULL);''') cur.execute('''CREATE TABLE IF NOT EXISTS Tests(testid INTEGER PRIMARY KEY, sname TEXT NOT NULL, marks TEXT, phone TEXT UNIQUE);''') # Insert data cur.execute('''INSERT INTO students (name, classid, phone) VALUES ('Mary', '24/14', '77777777');''') # Update data cur.execute("UPDATE students SET phone='12341234' WHERE sid=1;") # Delete data cur.execute("DELETE FROM students WHERE classid='24/11';") # Select and display data rows = cur.execute("SELECT * FROM students;") for row in rows: print(row) # Commit changes to disk conn.commit() # Close connection conn.close()

Explanation:


2. SQL Select, Where, and Aggregate Functions

Select Query

cur.execute("SELECT name, classid FROM Students;")

Retrieves specific columns (`name` and `classid`) from the `Students` table.

Where Clause

cur.execute('''SELECT * FROM students WHERE classid='2512';''')

Filters rows where `classid` matches `2512`.

Order By

cur.execute('''SELECT * FROM Tests ORDER BY marks;''')

Sorts the results by the `marks` column in ascending order.

AND, OR, NOT Operators

cur.execute('''SELECT * FROM Teachers WHERE subject = 'Math' AND salary = '8888.88';''') cur.execute('''SELECT * FROM Teachers WHERE subject = 'Math' OR subject = 'Computing';''') cur.execute('''SELECT * FROM Teachers WHERE NOT subject = 'Math';''')

Aggregate Functions

cur.execute("SELECT MIN(marks) FROM Tests;") cur.execute("SELECT MAX(marks) FROM Tests;") cur.execute("SELECT COUNT(*) FROM Students;") cur.execute("SELECT SUM(salary) FROM Teachers;") cur.execute("SELECT AVG(marks) FROM Tests;")

3. SQL Joins

INNER JOIN

The `INNER JOIN` keyword selects records that have matching values in both tables.

cur.execute(''' SELECT Students.sname, Teachers.tname, Teachers.subject FROM Students INNER JOIN Teachers ON Students.classid = Teachers.classid; ''')

This query retrieves student names, teacher names, and subjects where the `classid` matches in both tables.

LEFT OUTER JOIN

The `LEFT OUTER JOIN` keyword returns all records from the left table and the matching records from the right table. If no match exists, NULL values are returned for the right table’s columns.

cur.execute(''' SELECT Students.sname, Teachers.tname, Teachers.subject FROM Students LEFT OUTER JOIN Teachers ON Students.classid = Teachers.classid; ''')

This query retrieves all student names and their respective teachers (if any). If no teacher exists for a student’s class, NULL is returned for the teacher fields.


4. Refering to Insert

This insert is provided during your exams and do use it only for reference

SQL Reference

5. Utilising DB browser


PyMongo and MongoDB (Non-SQL)

1. Connecting to MongoDB

MongoDB is a NoSQL database that stores data in a flexible, JSON-like format. The PyMongo library allows Python to interact with MongoDB.

# Import modules import pymongo from pprint import pprint # Connect to localhost MongoDB client = pymongo.MongoClient('localhost', 27017) # Create database db = client['school'] # Create collections student_coll = db['students'] teacher_coll = db['teachers'] # Display existing databases and collections database = client.list_database_names() print(database) coll_name = db.list_collection_names() print(coll_name)

Explanation:


2. Inserting Data

# Insert data into teacher collection teacher_coll.insert_one({"_id":1, "name": "Miss Tan", "subject": "math"}) teacher_coll.insert_many([{"_id":2, "name": "Mr Lim", "subject": "phy"}, {"_id":3, "name": "Mr Lee", "subject": "chem"}])

Explanation:


3. Querying Data

Find All Documents

docs = teacher_coll.find() for doc in docs: print(doc)

find(): Retrieves all documents from the collection (similar to SELECT * FROM table in SQL).

Find with Criteria

teacher_coll.find({"name": "Miss Tan", "subject": "math"})

Filters documents based on criteria (similar to WHERE in SQL).

Find One Document

query = teacher_coll.find_one({"name": "Mr Lim"}) print(query)

find_one(): Retrieves a single document that matches the criteria.

Comparison Operators

equal = teacher_coll.find_one({"_id": {"$eq": 3}}) for teacher in teacher_coll.find({"subject": {"$in": ["math", "chem"]}}): print(teacher)

Operators:


4. Logical Query Operators

# Logical queries for student in student_coll.find({"name": {"$not": {"$eq": "Raul"}}}): print(student)

Logical Operators:


5. Updating Data

teacher_coll.update_many({"name": {"$exists": True}}, {"$set": {"position": "HOD"}})

Explanation:


6. Deleting Data

teacher_coll.delete_one({"name": "Mr Lee"}) teacher_coll.drop()

Explanation:


7. Aggregation

teacher_coll.aggregate([ {"$group": {"_id": "$subject", "total": {"$sum": 1}}}, {"$sort": {"total": -1}} ])

Explanation:


8. Array Query Operators

student_coll.find({"hobbies": {"$type": "array"}})

Explanation:


9. Refering to Insert

This insert is provided during your exams and do use it only for reference

No SQL Reference

Socket Programming

1. What is Socket Programming?

Socket programming is a way of connecting two nodes on a network to communicate with each other. One socket (node) listens on a particular port, while the other socket reaches out to establish a connection. Once connected, they can exchange messages.


2. Server Code

The server listens for a connection on a specific port and sends a message to the client upon connection.

# Server Code import socket # Setup server server_socket = socket.socket() server_socket.bind(('localhost', 12345)) # Bind to localhost and port 12345 server_socket.listen() # Respond to client client_socket, client_conn = server_socket.accept() print(client_socket) print("Connected to " + str(client_conn)) client_socket.sendall(b'Hi from server') # Teardown client_socket.close() server_socket.close()

Explanation:


3. Client Code

The client connects to the server and receives a message.

# Client Code import socket # Setup client client_socket = socket.socket() server_addr = input("Enter server address: ") # e.g., localhost server_port = int(input("Enter server port: ")) # e.g., 12345 # Connect to server client_socket.connect((server_addr, server_port)) print(client_socket.recv(1024)) # Teardown client_socket.close()

Explanation:


4. Steps to Run the Code

  1. Open two Python IDLE instances (or terminals).
  2. Run the server code in one instance. The server will start listening on port 12345.
  3. Run the client code in the other instance. Provide the server address (localhost) and port (12345).
  4. Observe the interaction: The client receives the message "Hi from server".

5. Concepts of Socket Programming


6. Key Functions in Python Socket Programming


Flask Framework

1. What is Flask?

Flask is a lightweight and versatile Python web framework used to build web applications. It provides tools for routing, handling HTTP requests, and rendering templates. Flask is known for its simplicity, flexibility, and modular design.


2. Core Features


3. Basic Flask Application

A simple example of a Flask app that responds to requests at the root URL:

# Flask Basic App from flask import Flask app = Flask(__name__) @app.route('/') def home(): return "Hello, Flask!" if __name__ == '__main__': app.run()

Explanation:


4. Routing in Flask

Routing allows mapping of URLs to specific functions in the application.

@app.route('/about') def about(): return "This is the About page." @app.route('/contact') def contact(): return "This is the Contact page."

Explanation:


5. Dynamic Routing and Template Rendering

Flask supports dynamic URLs and renders templates using Jinja2.

from flask import render_template @app.route('/greet/') def greet(name): return render_template('greet.html', name=name)

Template (greet.html):

<!DOCTYPE html> <html> <head> <title>Greeting</title> </head> <body> <h1>Hello, {{ name }}!</h1> </body> </html>

Explanation:


6. Handling HTTP Methods

Flask supports HTTP methods like GET and POST to handle user input and form submissions.

from flask import request @app.route('/submit', methods=['POST']) def submit(): data = request.form['data'] return f"Received: {data}"

Explanation:


7. Running the Application

  1. Save the Flask application in a Python file (e.g., app.py).
  2. Run the application using python app.py.
  3. Access the application in a web browser at http://localhost:5000.

8. Advantages of Flask


Flask Web Applications with SQLite3

Flask is a lightweight web framework in Python. It provides powerful tools for building web applications, including routing, template rendering, and integration with databases like SQLite3.


1. Library Management Example

Python Backend (main.py)

import sqlite3 import flask app = flask.Flask(__name__) @app.route('/', methods=['GET', 'POST']) def home(): db = sqlite3.connect('LIBRARY.db') cur = db.execute("""SELECT FamilyName, GivenName, Title from Book, Member, Loan WHERE Book.BookID = Loan.BookID and Member.MemberNumber = Loan.MemberNumber and Loan.Returned = 'FALSE'""") data = cur.fetchall() return flask.render_template('index.html', data=data) app.run()

HTML Template (index.html):

<!DOCTYPE html> <html> <head> <title>Library System</title> </head> <body> <table> <tr> <th>Family Name</th> <th>Given Name</th> <th>Book Title</th> </tr> {% for item in data %} <tr> <td>{{ item[0] }}</td> <td>{{ item[1] }}</td> <td>{{ item[2] }}</td> </tr> {% endfor %} </table> </body> </html>

Explanation:

2. Sanitiser Management Example

Python Backend (main.py)

import sqlite3 import flask app = flask.Flask(__name__) @app.route('/') def home(): conn = sqlite3.connect('sanitisers.db') cur = conn.cursor() cur.execute('''SELECT product_name, active_ingredient, alcohol_based FROM sanitisers''') data = cur.fetchall() return flask.render_template('index.html', data=data) @app.route('/process_form/', methods=["POST"]) def process_home(): ingredient = flask.request.form['ingredient'] conn = sqlite3.connect('sanitisers.db') cur = conn.cursor() cur.execute('''SELECT product_name, alcohol_based FROM sanitisers WHERE active_ingredient = ?''', (ingredient,)) sanitisers = cur.fetchall() return flask.render_template('result.html', sanitisers=sanitisers) app.run()

HTML Template (index.html)

<!DOCTYPE html> <html> <head> <title>Sanitisers</title> </head> <body> <table> <tr> <th>Product Name</th> <th>Active Ingredient</th> <th>Alcohol Based</th> </tr> {% for item in data %} <tr> <td>{{ item[0] }}</td> <td>{{ item[1] }}</td> <td>{{ item[2] }}</td> </tr> {% endfor %} </table> <form action="/process_form/" method="POST"> <p> Active Ingredient: <input type="text" name="ingredient"> </p> <p> <input type="submit" value="Search"> </p> </form> </body> </html>

Explanation:


3. Image Upload and View Example

Python Backend (main.py)

import os from flask import Flask, render_template, request, send_from_directory from werkzeug.utils import secure_filename app = Flask(__name__) @app.route('/', methods=['GET', 'POST']) def home(): if request.method == 'POST' and request.files and 'image' in request.files: image = request.files['image'] filename = secure_filename(image.filename) path = os.path.join('uploads', filename) image.save(path) with open('all.txt', 'a') as file: file.write(filename + '\n') return render_template('index.html', status='ok') return render_template('index.html') @app.route('/view') def view(): with open('all.txt', 'r') as file: filenames = file.readlines() images = [filename.strip() for filename in filenames] return render_template('view.html', images=images) @app.route('/images/') def get_file(filename): return send_from_directory('uploads', filename) app.run()

Explanation:

  1. Import Statements:
  2. Flask Application Setup:
  3. Route: / (Home Page)
  4. Route: /view (Gallery Page)
  5. Route: /images/<filename> (Serve Images)
  6. Run the App:

HTML template (index.html)

<!DOCTYPE html> <html> <head> <title>Upload</title> </head> <body> <form method="post" enctype="multipart/form-data"> Image: <input name="image" type="file"><br/> </form> <p><a href="{{ url_for('view') }}">View</a></p> {% if status %} {{ status }} {% endif %} </body> </html>

Explanation:

  1. Purpose:
  2. Structure:

HTML template (view.html)

<!DOCTYPE html> <html> <head> <title>View</title> </head> <body> {% for image in images %} <img src="{{ url_for('get_file', filename=image) }}" alt="{{ image }}"> {% endfor %} <p><a href="{{ url_for('home') }}">Home</a></p> </body> </html>

Explanation:

  1. Purpose:
  2. Structure: