Saturday, April 5, 2014

Extra #4: Overview Of OOP, Exception, Unittest and Inheritance

This is a quick review of some of the early topics covered in the course, including Object Orientated Programming, Raising/Catching Exception, Unit Testing and Inheritance.

Week 1-2: Object Orientated Programming.
We were introduced two abstract data types (ADT) that commonly used in Computer Science which were Stack and Queue. Stack and Queue are both container class just like normal list. Stack adds (push) and removes (pop) data from the back of the existing data series, Queue also adds data (prepend) to the back of the data series but it removes (dequeue) data, starting from the front of the data series.

Since Stack behaves almost like list, and in fact we can easily implement Stack ADT, using normal Python list:
class Stack:
    def __init__(self: 'Stack') -> None:
        self.data = []
    def pop(self: 'Stack') -> object:
        return self.data.pop() # pop data from the top of the stack (back of the list
    def is_empty(self: 'Stack') -> bool:
        return self.data == [] # check if a stack is empty
    def push(self: 'Stack', o: object) -> None:
        self.data.append(o) # new data is pushed to the top of the stack
I know you are thinking that i'm going to use the same strategy to implement Queue. And yes we can use list to implement queue and use the built-in function del to remove the first element in the list. Below is the code, is not it the best way to implement Queue ?
class Queue: # O(n) implementation
    def __init__(self) -> None:
        self.data = []
    def dequeue(self) -> object:
        first = self.data[0] # temporarily store first element in the list
        del(self.data[0]) # delete the first element
        return first # return the element that has been deleted.
    def is_empty(self) -> bool:
        return self.data == [] # check if the queue is empty
    def enqueue(self, o) -> None:
        self.data.append(o) # new data is added to back of the queue
The problem for such an implementation is that when we delete an element in a list, all of the elements that come after the it will need to be shift by 1 to the front. So the running time complexity of my dequeue function is O(n-1) or simply O(n), which is not too bad but can be improved!

Let take a look at my second implementation of Queue:
class Queue2: # O(1) implementation
    def __init__(self) -> None:
        self.data = []
        self.first = None # tracking the index of first element
    def dequeue(self) -> object:
        if len(self.data) > 1:
            self.first += 1
            return self.data[self.first-1]
        else:
            self.first = None
            return self.data[0]
    def is_empty(self) -> bool:
        return self.first == None
    def enqueue(self, o) -:
        self.data.append(o)
        # If an element is added into the list, then the list list is not
        # blank and the first index of first element in the list is 0.
        self.first = 0 
In this second implementation, i still use Python list to store data in the queue. However, I do not physically remove any element in the list self.data. I has the variable named 'self.first', which is an index in list 'self.data' that represents for the first element in the Queue. When method dequeue is called on an object, there are two possible cases:
  1. If there are more than 1 element in the list self.data, the index reference self.first is incremented by 1. The element at index [self.first - 1] in list self.data, which is an element that has been "removed" from the Queue, is then returned.
  2. If there is only one element in the list self,data, the index reference self.first becomes None, indicating that the Queue is empty, although the list self.data is not empty! The only element in the list self.data is then returned.
In both of the case, since the state of the list self.data is unchanged, the only thing that changes is the variable self.first. So the running time complexity of my dequeue function is only O(1) and is must faster than the first implementation. Using similar approach, we can implement Queue using ADT LinkedList. 
Week 3: Raising and Catching Exception.
I personally found it to be the easiest topic of the course. The purpose for raising and catching the Exceptions is to keep the program running when the user accidentally inputs invalid data or carries out illegal operation. An example of this can be found in the assignment 1, when an error message is rase but the program keeps running when the user unintentionally put a big cheese on top of a smaller cheese. I am not going revise that code, but instead do another example that works well when raising Exception works well with recursion! Consider the function slog_grade below which asks the TA for my Slog grade.
def slog_grade(): #abc
    grade = int(input("Please type in a grade for my Slog: "))
    try:
        if grade != 100:
            raise InvalidSlogGrade
        else:
            return grade # thank you for giving me 100%, haha
    except InvalidSlogGrade:
        print("You are only allowed to type in 100%")
        slog_grade()
So the program asks the TAs to input a grade for my Slog. If the input grade is not equal to 100%, the InvalidSlogGrade error is raised and caught by the except statement. The function slog_grade is then recursively call in the except statement until the InvalidSlogGrade error is no longer raised. In order to stop the error from happening, the TA have to give me 100%. 
Note that the function above can easily be done by a single while loop, but for the purpose of illustrating how exception works with recursion i made it little bit more complex. 
Week 3(2): Unit Testing.
That is what the TAs always do, making test files and test all of the functions that we wrote for the assignments and exercises. I just tried to be a TA and wrote a class that test the function slog_grade() that i wrote above.

import unittest
class Test_grade(unittest.TestCase):
    def test_slog_grade(self):
        self.assertEqual(slog_grade(), 100)
def test_grade_to_percentage(self):
        self.assertEqual(grade_to_percentage(slog_grade(), 100), 100)
if __name__ == '__main__':
    unittest.main(exit = False)

The code looks fairly simple but it is a good example of how a testing file should be structured.
Week 4: Inheritance
Inheritance is often used in making a new class that is almost identical to some other but with some additional features. There are basically two pieces of code that we have to remember for the exam.
  1. derived_class_name(base_class_name) which is the syntax used for inheritance when declaring a new class. 
  2. base_class_name.function_name() is the syntax used to call a function available in the parent classes. It is used in case when a programmer wants to reuse a method available in the parent classes, that has been override in the current class. 
There are considerable advantages of using inheritance:
  • Reusability: It reduces the duplicated code, the hard drive memory used to store these duplicated codes as well as time of a programmer.
  • Upgrading: A programmer can add additional features into their program without alternating the current state of the program. 
  • Data Hiding:  In Java, we can declare a variable or function to be “private”, so that the derived class cannot directly access or alter private data in parent classes. Although there is built in “property” in Python that make accessing variables in parent classes a little bit tricky, there exist no private variables or private methods that cannot be externally accessed. Java therefore is considerably more secure than Python. 
  • Overriding: A function that appear in the parent class can be overridden in derived class to perform a new operation. 
Surprisingly, there are disadvantages as well:
  • Multiple Inheritance: Python object is extremely powerful since it allows multiple inheritance, a class can inherit methods from more than one parent class. But very unfortunate that not all programming language support multiple inheritance. The more rigorous programming language than Python, Java does not support multiple inheritance. 
  • Running Time: If we have a very complex inheritance hierarchy, jumping from one to another level of inheritance may cost the machine some processing time.
  • Upgrading: If a function in a parent class is changed, all of the daughter class also need to be upgraded. 

Friday, April 4, 2014

Week 12 Last words

12 weeks passed so fast, I just got the last CSC148 lecture this morning. So in this very final slog, i just want to give some comments on my overall learning experience.

Danny Heap was a great lecturer and very approachable outside the class. Unlike other professors, he always spent considerable amount of time during the lecture to discuss the assignments. He gave class plenty of hints for the assignment as well as the exercises. The assignment he made are extremely useful, while the first assignment helped me to understand the basic concept of recursion, the second assignment made me become very fluent in using recursing!  

 The only thing that i did not like were the midterms. I don't understand the 20% policy for blank answer. It discouraged the students to think about the problem and express the idea on paper. Since there were always multiple versions of a test, it sometimes lead to unfair results for some students. For example, two students who study nothing before the exams, they end up getting 20% by leaving the entire test blank. However, the first student was lucky because it was the harder version of the test so he got an additional 20 % after the bell curve while the grade of the second student was unchanged... I am not one of those two, it is just my opinion on how the test might became unfair for some students. 

My TA, Jack, in W 1-3 lab session was a very nice guide too, he spent plenty of time talking about ADT, LinkedList, Tree, BST... the abstract concepts that the students are usually weak at. His accent was a little bit difficult to hear but he was very familiar with the course materials and engaged in the lab works. My partner for the labs was a Japanese girl, who was very nice and hard-working. 

The overall learning experience is great, I definitely learnt much more stuffs than what i learnt in CSC108. However, i am little bit disappointed about my performance in the course, especially my grades for the two term tests. I got about the class average in both but i felt like i could do much better if did not misread the question in the first midterm or choose easier questions to do first in the second midterm. But any way, i am working hard for the final, i hope that i can get around 85% so i won't lose my 4.0 CGPA .

Lastly, i want to say thank you to the professor, automaker TAs, Slog TAs who spent lot of time marking my works. I also want to say thank you to the TA in CSC help center, who always willing to help me to understand the materials covered in the lecture. I hope you all will have a great summer vacation and see you next year :).