Checklist
Write algorithms for linear search and binary search
Write algorithms for insertion sort and bubble sort
Write algorithms for stacks (initialization, push, pop)
Write algorithms for (linear and circular) queues (initialization, enqueue, dequeue)
Write algorithms for (array-based) linked lists; (initialization, add, delete, search)
Write algorithms for (array-based and OOP) binary trees; (initialization, add, search, traversal; pre-order, in-order and post-order)
Write algorithms for initializing records
Write algorithms for recursion
Write algorithms using object-oriented programming (OOP)
Establish classes with a constructor, getters, setters and destructor
Establish constructors with/without parameters
Initialize public/private attributes
Establish inheritance (i.e. parent and child classes)
Establish polymorphism
Establish method overloading (i.e. methods within a class that have different signatures/prototypes)
Establish method overriding (i.e. methods within a child class that re-implements those of the parent class)
Instantiate objects of classes (including child classes) and arrays of objects
Write algorithms using exception handling (i.e.
try-exceptstatements)Write algorithms using file handling
Read/write data to/from serial/sequential files
Read/write data to/from random files
Read/write data to/from files into/out of records (i.e. objects)
Read/write data to/from files with exception handling
Linear Search
Brute force algorithm to search for an item in a structure (e.g. array, linked list) by comparing it with every element in the structure.
Efficient linear search must stop searching once the item is found.
A variant of the following implementation could be to return the index of where the item was found,
-1otherwise.
Time Complexity Space Complexity xxxxxxxxxxitems = [5, 4, 3, 6, 2, 7, 8, 1, 9, 0] # items: ARRAY OF INTEGERdef linearSearch(item): # item: INTEGERfound = False # found: BOOLEANi = 0 # i: INTEGERwhile i < len(items) and found == False:if items[i] == item:found = Truei += 1return foundprint(linearSearch(5)) # OUTPUT: Trueprint(linearSearch(10)) # OUTPUT: FalsexxxxxxxxxxCONSTANT ARRAY_SIZE = 10DECLARE items: ARRAY[1:ARRAY_SIZE] OF INTEGERitems <- [5, 4, 3, 6, 2, 7, 8, 1, 9, 0]FUNCTION linearSearch(BYVAL item: INTEGER) RETURNS BOOLEANDECLARE i: INTEGERDECLARE found: BOOLEANfound <- FALSEi <- 1WHILE i <= ARRAY_SIZE AND found = FALSE DOIF items[i] = item THENfound <- TRUEENDIFi <- i + 1ENDWHILERETURN foundENDFUNCTIONOUTPUT linearSearch(5) // OUTPUT: TRUEOUTPUT linearSearch(10) // OUTPUT: FALSE
Binary Search
Divide and conquer algorithm to search for an item in a structure (e.g. array, heap) by checking the middle; otherwise repeating this process on the left or right halves of the structure; until an item is found or the respective half is empty.
In order for this to work, the structure must be sorted; thereby adding overhead to the practical efficiency of Binary Search.
A variant of the following implementation(s) could be to return the index of where the item was found,
-1otherwise.There are two implementations of binary search; iterative and recursive.
Implementation Time Complexity Space Complexity Iterative Recursive Iterative Implementation
Note that right is initialized as
len(items)-1- because the upper bound of a Python 0-indexed array is it's length - 1.xxxxxxxxxxitems = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # items: ARRAY OF INTEGERdef binarySearch(item): # item: INTEGERfound = False # found: BOOLEANleft = 0 # left: INTEGERright = len(items) - 1 # right: INTEGERwhile left <= right and found == False:mid = (left + right) // 2 # mid: INTEGERif items[mid] == item:found = Trueelif item > items[mid]:left = mid + 1else:right = mid - 1return foundprint(binarySearch(5)) # OUTPUT: Trueprint(binarySearch(10)) # OUTPUT: FalsexxxxxxxxxxCONSTANT ARRAY_SIZE = 10DECLARE items: ARRAY[1:ARRAY_SIZE] OF INTEGERitems <- [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]FUNCTION binarySearch(BYVAL item: INTEGER) RETURNS BOOLEANDECLARE mid: INTEGERDECLARE left: INTEGERDECLARE right: INTEGERDECLARE found: BOOLEANfound <- FALSEleft <- 1right <- ARRAY_SIZEWHILE left <= right AND found = FALSE DOmid <- (left + right) DIV 2IF items[mid] = item THENfound <- TRUEELSEIF item > items[mid] THENleft <- mid + 1ELSEright <- mid - 1ENDIFENDIFENDWHILERETURN foundENDFUNCTIONOUTPUT binarySearch(5) // OUTPUT: TRUEOUTPUT binarySearch(10) // OUTPUT: FALSERecursive Implementation
xxxxxxxxxxitems = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # items: ARRAY OF INTEGERdef binarySearch(array, item): # array: ARRAY OF INTEGER, item: INTEGERif len(array) == 0:return Falsem = len(array) // 2 # m: INTEGERif item == array[m]:return Trueelif item > array[m]:return binarySearch(array[m+1:], item)else:return binarySearch(array[:m], item)print(binarySearch(items, 5)) # OUTPUT: Trueprint(binarySearch(items, 10)) # OUTPUT: False
The pseudocode implementation for recursive binary search is slightly different since splitting the array into two halves isn't straightforward like Python's slicing syntax.
Hence, the left and right indices are also passed in as parameters (excluding the array, which is treated as a global variable) - this reduces this implementation's space complexity to
: xxxxxxxxxxCONSTANT ARRAY_SIZE = 10DECLARE items: ARRAY[1:ARRAY_SIZE] OF INTEGERitems <- [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]FUNCTION binarySearch(BYVAL item: INTEGER, BYVAL left: INTEGER, BYVAL right: INTEGER) RETURNS BOOLEANDECLARE m: INTEGERIF left > right THENRETURN FALSEENDIFm <- (left + right) DIV 2IF item = items[m] THENRETURN TRUEELSEIF item > items[m] THENRETURN binarySearch(item, m + 1, right)ELSERETURN binarySearch(item, left, m - 1)ENDIFENDIFENDFUNCTIONOUTPUT binarySearch(5, 1, ARRAY_SIZE) // OUTPUT: TRUEOUTPUT binarySearch(10, 1, ARRAY_SIZE) // OUTPUT: FALSE
Bubble Sort
Brute force algorithm to sort a structure (i.e. array, linked list) in ascending/descending order of its elements by comparing and swapping every single element into its right position in the structure.
Efficient bubble sort must stop sorting once no more items are being swapped within a pass.
Time Complexity Space Complexity
Maximum number of passes:
Maximum number of comparisons:
xxxxxxxxxxitems = [1, 5, 7, 6, 4, 3, 8, 9, 2, 0] # items: ARRAY OF INTEGERdef bubbleSort():global itemsi = 0 # i: INTEGERswapped = True # swapped: BOOLEAN# (1) outer loop for every "pass"# ... if nothing was swapped by the end of a pass, the array is already sortedwhile i < len(items) - 1 and swapped:swapped = False# (2) inner loop for every "comparision"for j in range(len(items)-i-1): # j: INTEGER# (3) check if the current item is greater than the next itemif items[j] > items[j+1]:# (4) if true, swap the itemsitems[j], items[j+1] = items[j+1], items[j]swapped = Truei += 1bubbleSort()print(items) # OUTPUT: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
The pseudocode implementation of bubble sort uses a 0-indexed array - therefore,
while i < len(items) - 1becomesWHILE i < ARRAY_SIZE- the-1is removed:xxxxxxxxxxCONSTANT ARRAY_SIZE = 10DECLARE items: ARRAY[1:ARRAY_SIZE] OF INTEGERitems <- [1, 5, 7, 6, 4, 3, 8, 9, 2, 0]PROCEDURE bubbleSort()DECLARE i: INTEGERDECLARE j: INTEGERDECLARE temp: INTEGERDECLARE swapped: BOOLEANi <- 1swapped <- TRUEWHILE i < ARRAY_SIZE AND swapped = TRUE DOswapped <- FALSEFOR j <- 1 TO (ARRAY_SIZE - i - 1)IF items[j] > items[j + 1] THENtemp <- items[j]items[j] <- items[j + 1]items[j + 1] <- tempswapped <- TRUEENDIFNEXT ji <- i + 1ENDWHILEENDPROCEDURECALL bubbleSort()OUTPUT items // OUTPUT: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Insertion Sort
Sorts a structure (i.e. array, linked list) in ascending/descending order of its elements by moving items from an unsorted part in the array to a sorted part (i.e. right to left)
On average, insertion sort performs faster than bubble sort because when moving an item into the sorted part, it doesn't have to swap all its elements; just the ones larger than the item being inserted in. Hence, it can stop the inner-loop early.
Time Complexity Space Complexity xxxxxxxxxxitems = [1, 5, 7, 6, 4, 3, 8, 9, 2, 0] # items: ARRAY OF INTEGERdef insertionSort():global items# (1) outer loop for every "pass"for i in range(1, len(items)): # i: INTEGERvalue = items[i] # value: INTEGERj = i - 1 # j: INTEGER# (2) inner loop for moving the items in the sorted part to the right (i.e. making space)while j >= 0 and value < items[j]:items[j + 1] = items[j]j -= 1# (3) insert item into the determined location in the sorted partitems[j + 1] = valueinsertionSort()print(items) # OUTPUT: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]xxxxxxxxxxCONSTANT ARRAY_SIZE = 10DECLARE items: ARRAY[1:ARRAY_SIZE] OF INTEGERitems <- [1, 5, 7, 6, 4, 3, 8, 9, 2, 0]PROCEDURE insertionSort()DECLARE i: INTEGERDECLARE j: INTEGERDECLARE value: INTEGERFOR i <- 2 TO ARRAY_SIZEvalue <- items[i]j <- i - 1WHILE j >= 1 AND value < items[j] DOitems[j + 1] <- items[j]j <- j - 1ENDWHILEitems[j + 1] <- valueNEXT iENDPROCEDURECALL insertionSort()OUTPUT items // OUTPUT: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Records
A composite data type comprising of several related items that may be of different data types.
In Python, records are declared as classes with just a constructor (type annotations must be included as comments)
xxxxxxxxxxfrom datetime import dateclass Student:def __init__(self):self.name = "" # STRINGself.dob = date.today() # DATEself.mark = 0 # INTEGERxxxxxxxxxxTYPE StudentDECLARE name: STRINGDECLARE dob: DATEDECLARE mark: INTEGERENDTYPE
An array of records can be initialized as follows:
xxxxxxxxxxARRAY_SIZE = 10 # CONSTANTStudents = [Student() for i in range(ARRAY_SIZE)]xxxxxxxxxxCONSTANT ARRAY_SIZE = 10DECLARE Students: ARRAY[1:ARRAY_SIZE] OF Student
Following the initialization of the array, it can be traversed and output using the following syntax:
xxxxxxxxxxfor student in Students:print(f"Student Name: {student.name} | Student DOB: {student.dob} | Student Mark: {student.mark}")# OUTPUT (assuming Students was given values):# Student Name: Bob | Student DOB: 05-06-2003 | Student Mark: 89# Student Name: Alice | Student DOB: 02-11-2003 | Student Mark: 75# ...xxxxxxxxxxDECLARE i: INTEGERFOR i <- 1 TO ARRAY_SIZEOUTPUT "Student Name: ", Students[i].name, " | Student DOB: ", Students[i].dob, " | Student Mark: ",Students[i].markNEXT i
Stacks
LIFO (Last-in, First-out) data structure (implemented as an array) that implements push to add an item to the top of the stack, and pop to remove an item from the top of the stack.
A
topPointeris required to save the index of the item at top of the stackA
basePointermay be used to save the index of the bottom of the stack (i.e. lower bound of the array)
stackfulis a variable used to keep track of the maximum size of the stack (i.e. length of the array)An efficient linear search algorithm can be used to search a stack.
Initialization (Main Program)
xxxxxxxxxxSTACKFUL = 5 # CONSTANTstack = [None for i in range(STACKFUL)] # stack: ARRAY OF INTEGERtopPointer = -1 # topPointer: INTEGERbasePointer = 0 # basePointer: INTEGER
Note that topPointer and basePointer are initialized with a starting value 1 more than that of Python - this is because of pseudocode arrays being 1-indexed.
The same principles apply to the pseudocode-respective implementations of
push()andpop()as well:xxxxxxxxxxCONSTANT STACKFUL = 5DECLARE stack: ARRAY[1:STACKFUL] OF INTEGERDECLARE topPointer: INTEGERDECLARE basePointer: INTEGERtopPointer <- 0basePointer <- 1
push()Procedurexxxxxxxxxxdef push(item):global stack, topPointerif topPointer == STACKFUL - 1:print("The stack is full.")else:topPointer += 1stack[topPointer] = itemxxxxxxxxxxPROCEDURE push(BYVAL item: INTEGER)IF topPointer = STACKFUL THENOUTPUT "The stack is full."ELSEtopPointer <- topPointer + 1stack[topPointer] <- itemENDIFENDPROCEDURE
pop()Functionxxxxxxxxxxdef pop():global stack, topPointeritem = None # item: INTEGERif topPointer == basePointer - 1:print("The stack is empty.")else:item = stack[topPointer]stack[topPointer] = NonetopPointer -= 1return itemxxxxxxxxxxFUNCTION pop() RETURNS INTEGERDECLARE item: INTEGERitem <- -1 // NULLIF topPointer = basePointer - 1 THENOUTPUT "The stack is empty."ELSEitem <- stack[topPointer]stack[topPointer] <- -1 // NULLtopPointer <- topPointer - 1ENDIFRETURN itemENDFUNCTIONExample Main Program
xxxxxxxxxxpush(10)push(15)push(19)push(14)item = pop()print(item) # OUTPUT: 14push(25)push(15)push(26) # OUTPUT: The stack is full.pop()pop()pop()item = pop()print(item) # OUTPUT: 15print(stack) # OUTPUT: [10, None, None, None, None]print("Top Pointer:", topPointer) # OUTPUT: Top Pointer: 0print("Base Pointer:", basePointer) # OUTPUT: Base Pointer: 0xxxxxxxxxxCALL push(10)CALL push(15)CALL push(19)CALL push(14)DECLARE item: INTEGERitem <- pop()OUTPUT itemCALL push(25)CALL push(15)CALL push(26)pop()pop()pop()item <- pop()OUTPUT itemOUTPUT stackOUTPUT "Top Pointer: ", topPointerOUTPUT "Base Pointer: ", basePointer
Linear Queue
FIFO (First-in, First-out) data structure (implemented as an array) that implements enqueue to add an item to the rear of the queue, and dequeue to remove an item from the front of the queue.
A
frontPointeris required to save the index of the item at the front of the queue (used during dequeue)A
rearPointeris required to save the index of the item at the rear of the queue (used during enqueue)
queueFulis a variable used to keep track of the maximum size of the queue (i.e. length of the array)Initialization
xxxxxxxxxxQUEUEFUL = 5 # CONSTANTqueue = [None for i in range(QUEUEFUL)] # queue: ARRAY OF INTEGERrearPointer = -1 # rearPointer: INTEGERfrontPointer = 0 # frontPointer: INTEGERxxxxxxxxxxCONSTANT QUEUEFUL = 5DECLARE queue: ARRAY[1:QUEUEFUL] OF INTEGERDECLARE rearPointer: INTEGERDECLARE frontPointer: INTEGERrearPointer <- 0frontPointer <- 1
Note that rearPointer and frontPointer are initialized with a starting value 1 more than that of Python - this is because of pseudocode arrays being 1-indexed.
The same principles apply to the pseudocode-respective implementations of
enqueue()anddequeue()as well:
enqueue()Procedurexxxxxxxxxxdef enqueue(item):global queue, rearPointerif rearPointer == QUEUEFUL - 1:print("The queue is full.")else:rearPointer += 1queue[rearPointer] = itemxxxxxxxxxxPROCEDURE enqueue(BYVAL item: INTEGER)IF rearPointer = QUEUEFUL THENOUTPUT "The queue is full."ELSErearPointer <- rearPointer + 1queue[rearPointer] <- itemENDIFENDPROCEDURE
dequeue()Functionxxxxxxxxxxdef dequeue():global queue, frontPointeritem = None # item: INTEGERif frontPointer == QUEUEFUL:print("The queue is empty.")else:item = queue[frontPointer]queue[frontPointer] = NonefrontPointer += 1return itemxxxxxxxxxxFUNCTION dequeue() RETURNS INTEGERDECLARE item: INTEGERitem <- -1 // NULLIF frontPointer > QUEUEFUL THENOUTPUT "The queue is empty."ELSEitem <- queue[frontPointer]queue[frontPointer] <- -1 // NULLfrontPointer <- frontPointer + 1ENDIFRETURN itemENDFUNCTION
However the issue with a linear queue is that when the
frontPointerreaches the rear of the queue (i.e.queueFul) - even when there's space remaining at the front of the queue - the locations at the front of the queue become unreachable.Example Main Program
xxxxxxxxxxenqueue(10)enqueue(15)enqueue(19)enqueue(14)item = dequeue()print(item) # OUTPUT: 10enqueue(25)enqueue(15) # OUTPUT: The queue is full.enqueue(26) # OUTPUT: The queue is full.dequeue()dequeue()item = dequeue()print(item) # OUTPUT: 14print(queue) # OUTPUT: [None, None, None, None, 25]print("Rear Pointer:", rearPointer) # OUTPUT: Rear Pointer: 4print("Front Pointer:", frontPointer) # OUTPUT: Front Pointer: 4xxxxxxxxxxCALL enqueue(10)CALL enqueue(15)CALL enqueue(19)CALL enqueue(14)DECLARE item: INTEGERitem <- dequeue()OUTPUT itemCALL enqueue(25)CALL enqueue(15)CALL enqueue(26)CALL dequeue()CALL dequeue()item <- dequeue()OUTPUT itemOUTPUT queueOUTPUT "Rear Pointer: ", rearPointerOUTPUT "Front Pointer: ", frontPointer
Circular Queue
An improved implementation of a linear queue with altered implementations for enqueue and dequeue - to allow for items to "loop back around" the rear of the queue to the front - eliminating unreachable locations.
In addition to the previous variables, we'll also need
queueLength; to save the number of items currently in the queue.Initialization
xxxxxxxxxxqueueFul = 5 # queueFul: INTEGERqueueLength = 0 # queueLength: INTEGERqueue = [None for i in range(queueFul)] # queue: ARRAY OF INTEGERrearPointer = -1 # rearPointer: INTEGERfrontPointer = 0 # frontPointer: INTEGERxxxxxxxxxxCONSTANT QUEUEFUL = 5DECLARE queueLength: INTEGERDECLARE queue: ARRAY[1:QUEUEFUL] OF INTEGERDECLARE rearPointer: INTEGERDECLARE frontPointer: INTEGERqueueLength <- 0rearPointer <- 0frontPointer <- 1
enqueue()Procedurexxxxxxxxxxdef enqueue(item):global queue, queueLength, rearPointer# (1) check whether queue is fullif queueLength == queueFul:print("The queue is full.")else:# (2) check whether the space is at the front / rearif rearPointer == queueFul - 1:rearPointer = 0else:rearPointer += 1# (3) add item to determined locationqueue[rearPointer] = item# (4) increment queue lengthqueueLength += 1xxxxxxxxxxPROCEDURE enqueue(BYVAL item: INTEGER)IF queueLength = QUEUEFUL THENOUTPUT "The queue is full."ELSEIF rearPointer = QUEUEFUL THENrearPointer <- 1ELSErearPointer <- rearPointer + 1ENDIFqueue[rearPointer] <- itemqueueLength <- queueLength + 1ENDIFENDPROCEDUREdequeue() Function
xxxxxxxxxxdef dequeue():global queue, queueLength, frontPointeritem = None # item: INTEGER# (1) check if queue is emptyif queueLength == 0:print("The queue is empty.")else:# (2) remove the item at the front of the queueitem = queue[frontPointer]queue[frontPointer] = None# (3) reset the front pointer to the beginning, or incrementif frontPointer == queueFul:frontPointer = 0else:frontPointer += 1# (4) decrement queue lengthqueueLength -= 1return itemxxxxxxxxxxFUNCTION dequeue() RETURNS INTEGERDECLARE item: INTEGERitem <- -1 // NULLIF queueLength = 0 THENOUTPUT "The queue is empty."ELSEitem <- queue[frontPointer]queue[frontPointer] <- -1 // NULLIF frontPointer > QUEUEFUL THENfrontPointer <- 1ELSEfrontPointer <- frontPointer + 1ENDIFqueueLength <- queueLength - 1ENDIFRETURN itemENDFUNCTIONExample Main Program
xxxxxxxxxxenqueue(10)enqueue(15)enqueue(19)enqueue(14)item = dequeue()print(item) # OUTPUT: 10enqueue(25)enqueue(15)enqueue(26) # OUTPUT: The queue is full.dequeue()dequeue()item = dequeue()print(item) # OUTPUT: 14print(queue) # OUTPUT: [15, None, None, None, 25]print("Rear Pointer:", rearPointer) # OUTPUT: Rear Pointer: 0print("Front Pointer:", frontPointer) # OUTPUT: Front Pointer: 4xxxxxxxxxxCALL enqueue(10)CALL enqueue(15)CALL enqueue(19)CALL enqueue(14)DECLARE item: INTEGERitem <- dequeue()OUTPUT itemCALL enqueue(25)CALL enqueue(15)CALL enqueue(26)CALL dequeue()CALL dequeue()item <- dequeue()OUTPUT itemOUTPUT queueOUTPUT "Rear Pointer:", rearPointerOUTPUT "Front Pointer:", frontPointer
Linked List
A linear data structure (implemented using an array) composed of nodes which each have a value and a pointer to the next node; until null.
Besides the normal pointers in a linked list, it also has 2 external pointers:
startPointer: Index of the first populated node in the linked list.
heapPointer / freePointer / freeList: Index of the next free node in the linked list.A linked list has 4 major operations:
initialize(): To reset and initialize all pointers and values, thestartPointerandheapPointerof the linked list.
insert(): To insert a value to the start of the linked list.
delete(): To search and delete a value from the linked list.
find(): To return whether or not an item was found in the linked list.The following implementation also has a 2 helper modules:
findPrevious(): To find the index of the node before the node you're search for; called within thedelete()procedure to re-align the pointers.
tabulate(): To display the linked list's indices, items and pointers in a tabular format.The above linked list operations have the following time complexities:
insert()delete()find()Declaration of Node Class + Variables
xxxxxxxxxxclass Node:def __init__(self, item, pointer):self.item = item # item: INTEGERself.pointer = pointer # pointer: INTEGERLIST_SIZE = 5 # CONSTANTNULL_POINTER = -1 # CONSTANTlinkedList = [Node(0, NULL_POINTER) for i in range(LIST_SIZE)] # linkedList: ARRAY OF NodestartPointer = NULL_POINTER # startPointer: INTEGERheapPointer = NULL_POINTER # heapPointer: INTEGERxxxxxxxxxxTYPE NodeDECLARE item: INTEGERDECLARE pointer: INTEGERENDTYPECONSTANT LIST_SIZE = 5CONSTANT NULL_POINTER = -1DECLARE linkedList: ARRAY[1:LIST_SIZE] OF NodeDECLARE startPointer: INTEGERDECLARE heapPointer: INTEGER
tabulate()Helper Procedurexxxxxxxxxxdef tabulate():print("INDEX\tITEM\tPOINTER")for i in range(LIST_SIZE): # i: INTEGERprint(f"{i}\t{linkedList[i].item}\t{linkedList[i].pointer}")print("Start Pointer:", startPointer)print("Heap Pointer:", heapPointer)xxxxxxxxxxPROCEDURE tabulate()DECLARE i: INTEGEROUTPUT "INDEX\tITEM\tPOINTER"FOR i <- 1 TO LIST_SIZEOUTPUT i, " \t ", linkedList[i].item, " \t ", linkedList[i].pointerNEXT iOUTPUT "Start Pointer: ", startPointerOUTPUT "Heap Pointer: ", heapPointerENDPROCEDURE
initialize()Procedurexxxxxxxxxxdef initialize():global linkedList, startPointer, heapPointer# (1) set startPointer to null (since the list is initially empty)startPointer = NULL_POINTER# (2) set heapPointer to 0 (since the first free node is at index 0)heapPointer = 0# (3) connect all nodes to the next nodefor i in range(LIST_SIZE): # i: INTEGERlinkedList[i].item = 0linkedList[i].pointer = i + 1# (4) set the last node's pointer to null (to indicate the end of the list)linkedList[LIST_SIZE - 1].pointer = NULL_POINTERxxxxxxxxxxPROCEDURE initialize()DECLARE i: INTEGERstartPointer <- NULL_POINTERheapPointer <- 1FOR i <- 1 TO LIST_SIZElinkedList[i].item <- 0linkedList[i].pointer <- i + 1NEXT ilinkedList[LIST_SIZE].pointer <- NULL_POINTERENDPROCEDURE
insert()Procedurexxxxxxxxxxdef insert(value):global linkedList, startPointer, heapPointer# (1) check if linked list is fullif heapPointer == NULL_POINTER:print("Cannot insert, linked list is full.")else:# (2) otherwise, temporarily save the current free node's pointertempPointer = linkedList[heapPointer].pointer # tempPointer: INTEGER# (3) set the free node's item and pointer (to current start)linkedList[heapPointer].item = valuelinkedList[heapPointer].pointer = startPointer# (4) set startPointer to heapPointer (since the first node is now the free node)startPointer = heapPointer# (5) set the heapPointer to the temporarily saved pointer to the free node# (set the heapPointer to the index of the node after the free node)heapPointer = tempPointerxxxxxxxxxxPROCEDURE insert(BYVAL value: INTEGER)DECLARE tempPointer: INTEGERIF heapPointer = NULL_POINTER THENOUTPUT "Cannot insert, linked list is full."ELSEtempPointer <- linkedList[heapPointer].pointerlinkedList[heapPointer].item <- valuelinkedList[heapPointer].pointer <- startPointerstartPointer <- heapPointerheapPointer <- tempPointerENDIFENDPROCEDURE
findPrevious()Helper Functionxxxxxxxxxxdef findPrevious(value): # value: INTEGER# (1) establish pointers for the previous and current nodesprevPointer = NULL_POINTER - 1 # prevPointer: INTEGERcurrentPointer = startPointer # currentPointer: INTEGER# (2) set found to False; to stop the search earlyfound = False # found: BOOLEAN# (3) search the linked list for a matching value until null is reachedwhile currentPointer != NULL_POINTER and found == False:if linkedList[currentPointer].item == value:found = Trueelse:prevPointer = currentPointercurrentPointer = linkedList[currentPointer].pointer# (4) if not found, return nullif found == False:prevPointer = NULL_POINTER# (5) otherwise, return prevPointer (i.e. index of the node before that found)return prevPointer
The
findPrevious()procedure returns one of 3 possible values:
-2: represents that the item to delete is that at the head (i.e. start of the linked list)
-1: represents that the item to delete is not found.
0 ... n: item was found, and the index of the node previous to it is returned.xxxxxxxxxxFUNCTION findPrevious(BYVAL value: INTEGER) RETURNS INTEGERDECLARE currentPointer: INTEGERDECLARE prevPointer: INTEGERDECLARE found: BOOLEANprevPointer <- NULL_POINTER - 1currentPointer <- startPointerfound <- FALSEWHILE currentPointer <> NULL_POINTER AND found = FALSE DOIF linkedList[currentPointer].item = value THENfound <- TRUEELSEprevPointer <- currentPointercurrentPointer <- linkedList[currentPointer].pointerENDIFENDWHILEIF found = FALSE THENprevPointer <- NULL_POINTERENDIF// [prevPointer = NULL_POINTER - 1] if item is at head// [prevPointer = NULL_POINTER] if item not found// [prevPointer = index] if item foundRETURN prevPointerENDFUNCTION
delete()Procedurexxxxxxxxxxdef delete(value): # value: INTEGERglobal linkedList, startPointer, heapPointer# (1) determine the index of the node before that being deletedprevPointer = findPrevious(value) # previousPointer: INTEGER# (2) check if the linked list is emptyif startPointer == NULL_POINTER:print("Cannot delete, linked list is empty.")# (3) check if the prevPointer is (-1); item is not foundelif prevPointer == NULL_POINTER:print("Cannot delete, item doesn't exist.")else:# (4) check if the prevPointer is (-2); item is found at the headcurrentPointer = startPointer # currentPointer: INTEGERif prevPointer == NULL_POINTER - 1:startPointer = linkedList[currentPointer].pointerelse:# (5) otherwise, item is found elsewherecurrentPointer = linkedList[prevPointer].pointerlinkedList[prevPointer].pointer = linkedList[currentPointer].pointer# (6) reset the deleted item's value and pointerlinkedList[currentPointer].item = 0linkedList[currentPointer].pointer = NULL_POINTER# (7) set the heapPointer to accomodate for the deleted node's indexheapPointer = currentPointer
Based on the current implementation of
delete(), items cannot be inserted to the linked list after deletion since the pointers are null'ed.Furthermore, any deleted nodes - although pointed to by the
heapPointer- become inaccessible thereafter.In addition, you cannot
find()items afterdelete()-ing them (since the connections to those nodes are lost)This can be prevented by using an additional array to keep track of those deleted locations, but is not implemented since it is out of the syllabus' scope.
xxxxxxxxxxPROCEDURE delete(BYVAL value: INTEGER)DECLARE prevPointer: INTEGERDECLARE currentPointer: INTEGERprevPointer <- findPrevious(value)IF startPointer = NULL_POINTER THENOUTPUT "Cannot delete, linked list is empty."ELSEIF prevPointer = NULL_POINTER THENOUTPUT "Cannot delete, item doesn't exist."ELSEIF prevPointer = NULL_POINTER - 1 THENcurrentPointer <- startPointerstartPointer <- linkedList[currentPointer].pointerELSEcurrentPointer <- linkedList[prevPointer].pointerlinkedList[prevPointer].pointer <- linkedList[currentPointer].pointerENDIFlinkedList[currentPointer].item <- 0linkedList[currentPointer].pointer <- NULL_POINTERheapPointer <- currentPointerENDIFENDIFENDPROCEDURE
find()Functionxxxxxxxxxxdef find(value): # value: INTEGER# (1) initialize pointer for the current nodecurrentPointer = startPointer # currentPointer: INTEGER# (2) set found to False; to stop the search earlyfound = False # found: BOOLEAN# (3) search the linked list for a matching value until null is reachedwhile currentPointer != NULL_POINTER and found == False:if linkedList[currentPointer].item == value:found = Trueelse:currentPointer = linkedList[currentPointer].pointer# (4) if found, returnreturn foundxxxxxxxxxxFUNCTION find(BYVAL value: INTEGER) RETURNS BOOLEANDECLARE currentPointer: INTEGERDECLARE found: BOOLEANcurrentPointer <- startPointerfound <- FALSEWHILE currentPointer <> NULL_POINTER AND found = FALSE DOIF linkedList[currentPointer].item = value THENfound <- TRUEELSEcurrentPointer <- linkedList[currentPointer].pointerENDIFENDWHILERETURN foundENDFUNCTIONExample Main Program
xxxxxxxxxxinitialize()tabulate()# OUTPUT:# INDEX ITEM POINTER# 0 0 1# 1 0 2# 2 0 3# 3 0 4# 4 0 -1# Start Pointer: -1# Heap Pointer: 0insert(50)insert(20)insert(10)insert(80)insert(70)insert(40) # OUTPUT: Cannot insert, linked list is full.tabulate()# OUTPUT:# INDEX ITEM POINTER# 0 50 -1# 1 20 0# 2 10 1# 3 80 2# 4 70 3# Start Pointer: 4# Heap Pointer: -1print(find(20)) # OUTPUT: Trueprint(find(100)) # OUTPUT: Falsedelete(50)delete(10)delete(20)delete(60) # OUTPUT: Cannot delete, item doesn't exist.tabulate()# INDEX ITEM POINTER# 0 0 -1# 1 0 -1# 2 0 -1# 3 80 -1# 4 70 3# Start Pointer: 4# Heap Pointer: 1xxxxxxxxxxCALL initialize()CALL tabulate()CALL insert(50)CALL insert(20)CALL insert(10)CALL insert(80)CALL insert(70)CALL insert(40)CALL tabulate()OUTPUT find(20)OUTPUT find(100)CALL delete(50)CALL delete(10)CALL delete(20)CALL delete(60)CALL tabulate()
Binary Tree (Array-Based)
A tree-structure that stored items in a root, left branch and right branch - in multiple levels. Items larger than the root are located in the right branch, while items smaller than the root is located in the left branch.
Each item in a binary tree is a node; which has an item, a left pointer (i.e. reference to / index of the node on the left) and a right pointer (i.e. reference to / index of the node on the right)
Besides the left and right pointers in a binary tree, it also has 2 external pointers:
rootPointer: Index of the first populated node in the binary tree (i.e. root node)
freePointer: Index of the next free node in the binary treeA binary tree has 3 major operations:
add(): To insert a value to the binary tree.
find(): To return whether or not an item was found in the binary tree.
traverse(): To output the binary tree's items in one of 3 traversal orders; pre-order, in-order and post-order.The following implementation also has 1 helper module:
tabulate(): To display the binary tree's indices, items, left pointers and right pointers in a tabular format.The above binary tree operations have the following time complexities:
add()find()traverse()
At the exam, the binary tree implementation can also be questioned as a 2D Array - which is very similar to the implementation below, but instead of a
Node()class, you'll be using a 3-element row to represent each node.Declaration of Node Class + Variables
xxxxxxxxxxclass Node:def __init__(self, item, leftPointer, rightPointer):self.item = item # item: INTEGERself.leftPointer = leftPointer # leftPointer: INTEGERself.rightPointer = rightPointer # rightPointer: INTEGERTREE_SIZE = 5 # CONSTANTNULL_POINTER = -1 # CONSTANTbinaryTree = [Node(None, NULL_POINTER, NULL_POINTER) for i in range(TREE_SIZE)] # binaryTree: ARRAY OF NoderootPointer = NULL_POINTER # rootPointer: INTEGERfreePointer = 0 # freePointer: INTEGER
The list comprehension method used above for initializing the binary tree is not available in pseudocode - instead a generic index-based for-loop can be used:
xxxxxxxxxxTYPE NodeDECLARE item: INTEGERDECLARE leftPointer: INTEGERDECLARE rightPointer: INTEGERENDTYPECONSTANT TREE_SIZE = 5CONSTANT NULL_POINTER = -1DECLARE binaryTree: ARRAY[1:TREE_SIZE] OF NodeDECLARE rootPointer: INTEGERDECLARE freePointer: INTEGERrootPointer <- NULL_POINTERfreePointer <- 1FOR i <- 1 TO TREE_SIZEbinaryTree[i].item <- NULL_POINTERbinaryTree[i].leftPointer <- NULL_POINTERbinaryTree[i].rightPointer <- NULL_POINTERNEXT i
tabulate()Helper Procedurexxxxxxxxxxdef tabulate():print("INDEX\tITEM\tLEFT\tRIGHT")for i in range(TREE_SIZE): # i: INTEGERprint(f"{i}\t{binaryTree[i].item}\t{binaryTree[i].leftPointer}\t{binaryTree[i].rightPointer}")print("Root Pointer:", rootPointer)print("Free Pointer:", freePointer)xxxxxxxxxxPROCEDURE tabulate()OUTPUT "INDEX\tITEM\tLEFT\tRIGHT"FOR i <- 1 TO TREE_SIZEOUTPUT i, " \t ", binaryTree[i].item, " \t ", binaryTree[i].leftPointer, " \t ", binaryTree[i].rightPointerNEXT iOUTPUT "Root Pointer: ", rootPointerOUTPUT "Free Pointer: ", freePointerENDPROCEDURE
add()Procedurexxxxxxxxxxdef add(item): # item: INTEGERglobal binaryTree, rootPointer, freePointer# (1) check if tree if empty; add to rootif rootPointer == NULL_POINTER:rootPointer = 0binaryTree[rootPointer].item = item# (2) otherwise, see if free pointer has exceeded max. nodeselif freePointer >= TREE_SIZE:print("Cannot add, binary tree is full.")return# (3) otherwise, search where to addelse:previousPointer = NULL_POINTER # previousPointer: INTEGERcurrentPointer = rootPointer # currentPointer: INTEGERaddToLeft = False # addToLeft: BOOLEAN# (4) until the current pointer is null...while currentPointer != NULL_POINTER:# ... (5) by first saving the parent node's indexpreviousPointer = currentPointer# ... (6) traversing left branch if item less than root node's valueif item < binaryTree[currentPointer].item:currentPointer = binaryTree[currentPointer].leftPointeraddToLeft = True# ... (7) otherwise, right branchelse:currentPointer = binaryTree[currentPointer].rightPointeraddToLeft = False# (8) add the item to the new node (i.e. pointed to by the free pointer)binaryTree[freePointer].item = item# (9) connect the new node to the previous nodeif addToLeft:binaryTree[previousPointer].leftPointer = freePointerelse:binaryTree[previousPointer].rightPointer = freePointer# (10) increment freePointerfreePointer += 1xxxxxxxxxxPROCEDURE add(BYVAL item: INTEGER)DECLARE previousPointer: INTEGERDECLARE currentPointer: INTEGERDECLARE addToLeft: BOOLEANIF rootPointer = NULL_POINTER THENrootPointer <- 1binaryTree[rootPointer].item <- itemELSEIF freePointer > TREE_SIZE THENOUTPUT "Cannot add, binary tree is full."ELSEpreviousPointer <- NULL_POINTERcurrentPointer <- rootPointeraddToLeft <- FALSEWHILE currentPointer <> NULL_POINTER DOpreviousPointer <- currentPointerIF item < (binaryTree[currentPointer].item) THENcurrentPointer <- (binaryTree[currentPointer].leftPointer)addToLeft <- TRUEELSEcurrentPointer <- (binaryTree[currentPointer].rightPointer)addToLeft <- FALSEENDIFENDWHILEbinaryTree[freePointer].item <- itemIF addToLeft = TRUE THENbinaryTree[previousPointer].leftPointer <- freePointerELSEbinaryTree[previousPointer].rightPointer <- freePointerENDIFfreePointer <- freePointer + 1ENDIFENDIFENDPROCEDURE
find()Function (Recursive)xxxxxxxxxxdef find(currentPointer, searchItem):# (1) return False if not foundif currentPointer == NULL_POINTER:return False# (2) otherwise, check root; return Trueif binaryTree[currentPointer].item == searchItem:return True# (3) otherwise, traverse left branch (if smaller)elif searchItem < binaryTree[currentPointer].item:return find(binaryTree[currentPointer].leftPointer, searchItem)# (4) or, traverse right branch (if larger)else:return find(binaryTree[currentPointer].rightPointer, searchItem)xxxxxxxxxxFUNCTION find(BYVAL currentPointer: INTEGER, BYVAL searchItem: INTEGER) RETURNS BOOLEANIF currentPointer = NULL_POINTER THENRETURN FALSEENDIFIF binaryTree[currentPointer].item = searchItem THENRETURN TRUEELSEIF searchItem < binaryTree[currentPointer].item THENRETURN find(binaryTree[currentPointer].leftPointer, searchItem)ELSERETURN find(binaryTree[currentPointer].rightPointer, searchItem)ENDIFENDIFENDFUNCTION
traverse()Functions -preOrder(),inOrder(),postOrder()(Recursive)
preOrder()Procedure
Outputs the root node's item, then traverses the left branch, followed by the right branch.
xxxxxxxxxxdef preOrder(currentPointer):# (1) return if currentPointer is null (i.e. -1)if currentPointer == NULL_POINTER:return# (2) output root node's item (in one-line)print(binaryTree[currentPointer].item, end=" ")# (3) fully traverse left branch (i.e. until null pointer is reached)preOrder(binaryTree[currentPointer].leftPointer)# (4) fully traverse right branch (i.e. until null pointer is reached)preOrder(binaryTree[currentPointer].rightPointer)xxxxxxxxxxPROCEDURE preOrder(BYVAL currentPointer: INTEGER)IF currentPointer <> NULL_POINTER THENOUTPUT binaryTree[currentPointer].item, " "CALL preOrder(binaryTree[currentPointer].leftPointer)CALL preOrder(binaryTree[currentPointer].rightPointer)ENDIFENDPROCEDURE
inOrder()Procedure
Traverses the left branch, outputs the root node's item, and then traverses the right branch.
The items will output in ascending order.
xxxxxxxxxxdef inOrder(currentPointer):# (1) return if currentPointer is null (i.e. -1)if currentPointer == NULL_POINTER:return# (2) fully traverse left branch (i.e. until null pointer is reached)inOrder(binaryTree[currentPointer].leftPointer)# (3) output root node's item (in one-line)print(binaryTree[currentPointer].item, end=" ")# (4) fully traverse right branch (i.e. until null pointer is reached)inOrder(binaryTree[currentPointer].rightPointer)xxxxxxxxxxPROCEDURE inOrder(BYVAL currentPointer: INTEGER)IF currentPointer <> NULL_POINTER THENCALL inOrder(binaryTree[currentPointer].leftPointer)OUTPUT binaryTree[currentPointer].item, " "CALL inOrder(binaryTree[currentPointer].rightPointer)ENDIFENDPROCEDURE
postOrder()Procedure
Traverses the left branch, the right branch and then outputs the root node's item
xxxxxxxxxxdef postOrder(currentPointer):# (1) return if currentPointer is null (i.e. -1)if currentPointer == NULL_POINTER:return# (2) fully traverse left branch (i.e. until null pointer is reached)postOrder(binaryTree[currentPointer].leftPointer)# (3) fully traverse right branch (i.e. until null pointer is reached)postOrder(binaryTree[currentPointer].rightPointer)# (4) output root node's item (in one-line)print(binaryTree[currentPointer].item, end=" ")xPROCEDURE postOrder(BYVAL currentPointer: INTEGER)IF currentPointer <> NULL_POINTER THENCALL postOrder(binaryTree[currentPointer].leftPointer)CALL postOrder(binaryTree[currentPointer].rightPointer)OUTPUT binaryTree[currentPointer].item, " "ENDIFENDPROCEDUREExample Main Program
xxxxxxxxxxtabulate()# OUTPUT:# INDEX ITEM LEFT RIGHT# 0 None -1 -1# 1 None -1 -1# 2 None -1 -1# 3 None -1 -1# 4 None -1 -1# Root Pointer: -1# Free Pointer: 0add(50)add(20)add(80)add(10)add(70)add(40) # OUTPUT: Cannot add, binary tree is full.tabulate()# INDEX ITEM LEFT RIGHT# 0 50 1 2# 1 20 3 -1# 2 80 4 -1# 3 10 -1 -1# 4 70 -1 -1# Root Pointer: 0# Free Pointer: 5print(find(rootPointer, 70)) # OUTPUT: Trueprint(find(rootPointer, 100)) # OUTPUT: FalsepreOrder(rootPointer) # OUTPUT: 50 20 10 80 70print("") # to output newline after traversalinOrder(rootPointer) # OUTPUT: 10 20 50 70 80print("") # to output newline after traversalpostOrder(rootPointer) # OUTPUT: 10 20 70 80 50print("") # to output newline after traversalxxxxxxxxxxCALL tabulate()CALL add(50)CALL add(20)CALL add(80)CALL add(10)CALL add(70)CALL add(40)CALL tabulate()OUTPUT find(rootPointer, 70)OUTPUT find(rootPointer, 100)CALL preOrder(rootPointer)CALL inOrder(rootPointer)CALL postOrder(rootPointer)
Binary Tree (OOP-based)
A dynamically re-sizable implementation of the fixed-sized array-based Binary Tree, where the entire tree is built using a Node class, and all operations being methods of this class.
This means that the
selfattribute can be now accessed, enabling simpler recursive calls.The
leftPointerandrightPointerof the previous implementation is now replaced byleftandright- which are references to the left and right node objects; instead of indices.The only external reference required in this implementation is a root; the object corresponding to the root node of the tree.
All other operations are the same, and exhibit similar time complexities.
Declaration of Node Class + Methods
xxxxxxxxxxclass Node:# (1) create the Node object's attributesdef __init__(self, value):self.value = valueself.left = Noneself.right = None# (2) add a new node to the treedef add(self, value):if value > self.value:if self.right == None:self.right = Node(value)else:self.right.add(value)else:if self.left == None:self.left = Node(value)else:self.left.add(value)# (3) find an item in the treedef find(self, item):if self.value == item:return Trueelif item > self.value:if self.right == None:return Falseelse:return self.right.find(item)else:if self.left == None:return Falseelse:return self.left.find(item)# (4) implement pre-order traversaldef preOrder(self):# output root node's itemprint(self.value, end=" ")# fully traverse left branch (until null)if self.left != None:self.left.preOrder()# fully traverse right branch (until null)if self.right != None:self.right.preOrder()# (5) implement in-order traversaldef inOrder(self):# fully traverse left branch (until null)if self.left != None:self.left.inOrder()# output root node's itemprint(self.value, end=" ")# fully traverse right branch (until null)if self.right != None:self.right.inOrder()# (6) implement post-order traversaldef postOrder(self):# fully traverse left branch (until null)if self.left != None:self.left.postOrder()# fully traverse right branch (until null)if self.right != None:self.right.postOrder()# output root node's itemprint(self.value, end=" ")Example Main Program
xxxxxxxxxx# establish root node + subsequent valuesvalues = [50, 20, 80, 10, 70]root = Node(values[0])for value in values[1:]:# values[1:] is used to add all values except the first in the arrayroot.add(value)print(root.find(70)) # OUTPUT: Trueprint(root.find(100)) # OUTPUT: Falseroot.preOrder() # OUTPUT: 50 20 10 80 70print("") # to output newline after traversalroot.inOrder() # OUTPUT: 10 20 50 70 80print("") # to output newline after traversalroot.postOrder() # OUTPUT: 10 20 70 80 50print("") # to output newline after traversal
Recursive algorithms are those defined within a function, that calls itself.
The condition under which the function repeatedly calls itself is known as the recursive case. The opposite - the condition under which the function stops recursing and returns - is known as the base case.
The process of calling a function in-on-itself until the base case is reached is known as winding, while the process of returning after reaching the base-case is known as unwinding.
The above process is enabled by maintaining a stack of function calls (i.e. call stack) where each call gets pushed upon recursion and popped upon return.
Recursion is used to write simple algorithms for binary trees, binary search, factorials, counting, summation, checking for palindromes, merge sort and generating permutations - here are 5 examples:
Example #1 - Determining the
factorial xxxxxxxxxxdef factorial(n):if n == 0:return 1else:return n * factorial(n - 1)
Example #2 - Determining
for the first integers from . xxxxxxxxxxdef summation(n):if n == 0:return 0else:return n + summation(n - 1)
Example #3 - Determining whether input string
is a palindrome
A palindrome is a word spelled the same way forwards and backwards; e.g.
radar.xxxxxxxxxxdef is_palindrome(s):if len(s) <= 1:return Trueelse:return s[0] == s[-1] and is_palindrome(s[1:-1])
Example #4 - Count the number of vowels in an input string
xxxxxxxxxxdef count_vowels(s):vowels = "aeiou"if not s:return 0else:return (1 if s[0].lower() in vowels else 0) + count_vowels(s[1:])
Example #5 - Convert a denary number into binary
xxxxxxxxxxdef to_binary(n):if n == 0:return "0"elif n == 1:return "1"else:return to_binary(n // 2) + str(n % 2)
Implementing Classes
A class begins with a constructor (
__init__) - which is the method that gets called when a class gets instantiated.All attributes of the class need to be declared and initialized within the constructor.
The attributes can be set within the constructor in multiple different ways
Default: A default value is given to an attribute explicity (e.g.
IDNumberis set to0)Parameterized: A value is given to an attribute via a parameter defined in the constructor header (e.g.
FirstName)Default Parameterized: A value is given to an attribute via a parameter that also has a default value (e.g.
DOBorGrade)Attributes can also be either public or private (defined with two leading underscores, e.g.
self.__IDNumber) to implement data hiding.xxxxxxxxxxclass Student:def __init__(self, FirstName, DOB="01/01/1990", Grade=7):self.__IDNumber = 0 # PRIVATE IDNumber: INTEGERself.FirstName = FirstName # FirstName: STRINGself.DOB = DOB # DOB: DATEself.Grade = Grade # Grade: Grade
Setters and Getters
Setters are methods that are used to set values to attributes of a class.
Getters are method that are used to return values of a class to the main program.
xxxxxxxxxxclass Book:def __init__(self, ISBN, Title):# PRIVATE ISBN: STRING# DECLARE Title: STRINGself.__ISBN = ISBNself.Title = Titleself.BorrowerList = [None for i in range(5)]def getISBN(self):return self.__ISBNdef setISBN(self, newISBN):self.__ISBN = newISBN
In the above class,
getISBNis used to return the the current ISBN value within a Book object.In contrary,
setISBNis used to update the existing ISBN value with anewISBN.Getters and setters are most useful for private attributes; as they cannot be accessed from the main program explicitly:
xxxxxxxxxxMyBook = Book("978-0735211292", "Atomic Habits")print(MyBook.__ISBN) # OUTPUT: AttributeError: 'Book' object has no attribute '__ISBN'print(MyBook.getISBN()) # OUTPUT: 978-0735211292
Destructors
A class ends with a destructor (
__del__) - which is the method that gets called when an object gets deleted.This is done automatically by Python's garbage collector; but explicitly call
delin the exam.xxxxxxxxxxclass Student:def __init__(self, FirstName, DOB="01/01/1990", Grade=7):# PRIVATE IDNumber: INTEGER# DECLARE FirstName: STRING# DECLARE DOB: DATE# DECLARE Grade: Gradeself.__IDNumber = 0self.FirstName = FirstNameself.DOB = DOBself.Grade = Gradedef getIDNumber(self):return self.__IDNumberdef setIDNumber(self, newIDNumber):self.__IDNumber = newIDNumberdef __del__(self):print("Student was destroyed.")MyStudent = Student("Edwin Fisher", "05/01/2003", 13)del MyStudent # OUTPUT: Student was destroyed.
Inheritance
Inheritance is a pillar of OOP where a class inherits attributes and methods of a parent class.
This way, the methods common to a class can be defined in the parent and those different can be defined in the child.
When a child class is defined, the parent class must also be initialized by either using
super()or the parent class' name explicitly.Consider the following class diagram (each box is a class; composed of a class name, attribute list and method list) for a sports club:
In the above diagram,
PLAYERandOFFICIALinherit from theMEMBERparent class (hierarchal inheritance; one parent, multiple children).Let's imagine a scenario where each member is charged a monthly fee of £50 to join the sports club. However, players are deducted a percentage of this fee depending on the sport they do:
basketball: 50% reduction
badminton: 20% reductionany other sport: 10% reduction
Officials of the sports club don't have a fee.
This class diagram can be implemented is as follows:
xxxxxxxxxx# (1) establish parent class constructor, methods and destructorclass Member:def __init__(self, FirstName, LastName, DOB):# PRIVATE FirstName: STRING# PRIVATE LastName: STRING# PRIVATE DOB: DATEself.__FirstName = FirstNameself.__LastName = LastNameself.__DOB = DOBdef GetFullName(self):return f"{self.__FirstName} {self.__LastName}"def GetFee(self):return 50def __del__(self):print("Member was destroyed.")# (2) establish the "Player" child class constructor, methods and destructorclass Player(Member):def __init__(self, FirstName, LastName, DOB):# (2.1) initialize the parent classsuper().__init__(self, FirstName, LastName, DOB)# ALTERNATIVE: Member.__init__(self, FirstName, LastName, DOB)self.__Sport = None # Sport: STRINGdef SetSport(self, newSport): # newSport: STRINGself.__Sport = newSportdef GetFee(self):if self.__Sport == "basketball":return super().GetFee() * 0.5elif self.__Sport == "badminton":return super().GetFee() * 0.8else:return super().GetFee() * 0.9def __del__(self):print("Player was destroyed.")# (3) establish the "Official" child class constructor, methods and destructorclass Official(Member):def __init__(self, FirstName, LastName, DOB):# (3.1) initialize the parent classsuper().__init__(self, FirstName, LastName, DOB)# ALTERNATIVE: Member.__init__(self, FirstName, LastName, DOB)self.__JobRole = None # JobRole: STRINGdef SetJobRole(self, newJobRole="Referee"): # newJobRole: STRINGself.__JobRole = newJobRoledef GetFee(self):return 0def __del__(self):print("Official was destroyed.")
In the above implementation, polymorphism aka. overriding (methods of the parent class is redefined for child classes) and overloading (method of a class being re-defined) can be seen:
Polymorphism: the
GetFee()method of theMemberparent class is redefined for customized behavior within thePlayerandOfficialchild classes.Overloading: The
SetJobRole()method of theOfficialclass has a default parameter of"Referee"for the parameternewJobRole- which means that this method can be called with/without anewJobRoleparameter:
This is "technically" not proper overloading, but the best we can do with Python - since Python doesn't allow the same class to have two methods with the same name.
The other way of doing this is using an
argsandkwargsparameter - which is again out of the scope of the syllabus.
Instantiating Classes
Instantiating a class is to create objects using a defined class.
This object is in memory, and cannot be printed directly - it'll only print its reference in memory.
You can access it's method and attributes using the dot operator; e.g.
MyStudent.NameWith reference to the previous Inheritance example, here's how the various classes can be instantiated:
xxxxxxxxxx# (1) creating an instance of the Player classplayer1 = Player("John", "Doe", "1990-01-01")player1.SetSport("basketball")# (2) displaying the player's full name and feeprint(player1.GetFullName()) # OUTPUT: John Doeprint(f"Fee for basketball: {player1.GetFee()}") # OUTPUT: 25.0 (50 * 0.5 due to discount)# (3) creating an instance of the Official class with a default job roleofficial1 = Official("Alice", "Smith", "1985-03-15")official1.SetJobRole() # Default job role is set to 'Referee'# (4) displaying the official's full name and feeprint(official1.GetFullName()) # OUTPUT: Alice Smithprint(f"Fee for official: {official1.GetFee()}") # OUTPUT: 0 (Officials do not have a fee)# (5) calling the destructorsdel player1 # OUTPUT: "Player was destroyed."del official1 # OUTPUT: "Official was destroyed."
An exception is an unexpected event that disrupts the execution of a program.
Exception handling is the process of responding to an exception within the program without the program crashing / halting unexpectedly.
Example #1 - Catching generic errors
xtry:a = b + cexcept:print("Addition failed.")else:print("Addition was successful.")xxxxxxxxxxDECLARE a, b, c: INTEGERTRYa <- b + cEXCEPTOUTPUT "Addition failed."ENDTRY
Example #2 - Catching file handling errors
xxxxxxxxxxtry:file = open("myFile.txt", 'r')except:print("File was not found.")else:print("File was found.")xxxxxxxxxxTRYOPENFILE "myFile.txt" FOR READEXCEPTOUTPUT "File was not found."ENDTRY
You can even specify which type of exception to catch and thereby segment out your expect statements.
Examples of exceptions include: EOFError, ImportError, IndexError, KeyError, NameError, TypeError, ValueError, ZeroDivisionError, FileNotFoundError, IOError, OSError.
Example #3 - Catching seperate / named errors
xxxxxxxxxxtry:x = 5 / 0except ZeroDivisionError:print("You can't divide by zero.")except:print("Some other error occured.")else:print("No error occured.")
Access this Google Drive to find implementation details of the different file handling methods in Python.