A2 Computer Science - Practicals

Complete Notes for Python & Pseudocode


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)

  • Write algorithms using exception handling (i.e. try-except statements)

  • 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

Searching Algorithms

  • 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, -1 otherwise.

Time ComplexitySpace Complexity
O(n)O(n)
  • 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, -1 otherwise.

  • There are two implementations of binary search; iterative and recursive.

ImplementationTime ComplexitySpace Complexity
IterativeO(logn)O(n)
RecursiveO(logn)O(nlogn)

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.

Recursive Implementation

  • 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 O(n):

Sorting Algorithms

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 ComplexitySpace Complexity
O(n2)O(n)
  • Maximum number of passes: (n1)

  • Maximum number of comparisons: n(n1)2

  • The pseudocode implementation of bubble sort uses a 0-indexed array - therefore, while i < len(items) - 1 becomes WHILE i < ARRAY_SIZE - the -1 is removed:

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 ComplexitySpace Complexity
O(n2)O(n)

User Defined Data Types

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)

  • An array of records can be initialized as follows:

  • Following the initialization of the array, it can be traversed and output using the following syntax:

Abstract Data Types (ADTs)

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 topPointer is required to save the index of the item at top of the stack

  • A basePointer may be used to save the index of the bottom of the stack (i.e. lower bound of the array)

  • stackful is 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)

  • 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() and pop() as well:

push() Procedure

pop() Function

Example Main Program

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 frontPointer is required to save the index of the item at the front of the queue (used during dequeue)

  • A rearPointer is required to save the index of the item at the rear of the queue (used during enqueue)

  • queueFul is a variable used to keep track of the maximum size of the queue (i.e. length of the array)

Initialization

  • 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() and dequeue() as well:

enqueue() Procedure

dequeue() Function

  • However the issue with a linear queue is that when the frontPointer reaches 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

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

enqueue() Procedure

dequeue() Function

Example Main Program

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:

    1. initialize(): To reset and initialize all pointers and values, the startPointer and heapPointer of the linked list.

    2. insert(): To insert a value to the start of the linked list.

    3. delete(): To search and delete a value from the linked list.

    4. find(): To return whether or not an item was found in the linked list.

  • The following implementation also has a 2 helper modules:

    1. findPrevious(): To find the index of the node before the node you're search for; called within the delete() procedure to re-align the pointers.

    2. 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()
O(1)O(n)O(n)

Declaration of Node Class + Variables

tabulate()Helper Procedure

initialize() Procedure

insert() Procedure

findPrevious() Helper Function

  • 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.

delete() Procedure

  • 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 after delete()-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.

find() Function

Example Main Program

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 tree

  • A binary tree has 3 major operations:

    1. add(): To insert a value to the binary tree.

    2. find(): To return whether or not an item was found in the binary tree.

    3. 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()
O(1)O(logn)O(n)
  • 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

  • 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:

tabulate()Helper Procedure

add() Procedure

find() Function (Recursive)

traverse() Functions - preOrder(), inOrder(), postOrder() (Recursive)

preOrder() Procedure

  • Outputs the root node's item, then traverses the left branch, followed by the right branch.

    RootLeftRight

inOrder() Procedure

  • Traverses the left branch, outputs the root node's item, and then traverses the right branch.

    LeftRootRight

  • The items will output in ascending order.

postOrder() Procedure

  • Traverses the left branch, the right branch and then outputs the root node's item

    LeftRightRoot

Example Main Program

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 self attribute can be now accessed, enabling simpler recursive calls.

  • The leftPointer and rightPointer of the previous implementation is now replaced by left and right - 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

Example Main Program

Recursion

Example #1 - Determining the nth factorial

Example #2 - Determining Sn for the first n integers from 0.

Example #3 - Determining whether input string s is a palindrome

  • A palindrome is a word spelled the same way forwards and backwards; e.g. radar.

Example #4 - Count the number of vowels in an input string s

Example #5 - Convert a denary number into binary

Object Oriented Programming (OOP)

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. IDNumber is set to 0)

    • 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. DOB or Grade)

  • Attributes can also be either public or private (defined with two leading underscores, e.g. self.__IDNumber) to implement data hiding.

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.

  • In the above class, getISBN is used to return the the current ISBN value within a Book object.

  • In contrary, setISBN is used to update the existing ISBN value with a newISBN.

  • Getters and setters are most useful for private attributes; as they cannot be accessed from the main program explicitly:

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 del in the exam.

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:

 

Class Diagram for Sports Club

 

  • In the above diagram, PLAYER and OFFICIAL inherit from the MEMBER parent 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% reduction

    • any other sport: 10% reduction

  • Officials of the sports club don't have a fee.

  • This class diagram can be implemented is as follows:

  • 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 the Member parent class is redefined for customized behavior within the Player and Official child classes.

    • Overloading: The SetJobRole() method of the Official class has a default parameter of "Referee" for the parameter newJobRole - which means that this method can be called with/without a newJobRole parameter:

      • 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 args and kwargs parameter - 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.Name

  • With reference to the previous Inheritance example, here's how the various classes can be instantiated:

Exception Handling

Example #1 - Catching generic errors

Example #2 - Catching file handling errors

Example #3 - Catching seperate / named errors

File Handling