Saturday 7 February 2015

Recursion(Recursion(Recursion(...)))

Recursion is a powerful property of many programming languages. Alex's SLOG called it "code-ception", which I thought was a catchy name and a spot on way to describe it. Just like a dream inside a dream inside a dream, a recursive function is a function that goes inside itself, and then goes inside itself, and so on (as many times as needed). The difference between Inception and Recursion is that in Inception you go, while in a dream, into dreams of different people, while in Recursion you go, while the code is being executed, into the same code. Hence, the title Recursion(Recursion(Recursion(...))).

Why is it useful?
Save space; save trees. Well, not physical trees (unless for some reason you want to print your code [but not print() your code]), but memory space. The alternative is writing several functions with if statements that call each other when needed. A clear problem appears as you realize that you would have to write a lot of code if many steps are needed!

Tracing Back 
Trace back is a good exercise when wanting to understand what a piece of recursive code does. Let's try tracing back the following piece of code:

def looper(L): '''
 (list) -> int
 
 Awful docstring that explains nothing.
 '''

 return sum([looper(x) for x in L]) if isinstance(L, list) else 5

(Oh! BTW, that is a new trick I learned from the labs. You can make a return statement while implementing an if statement)

These are the examples that we will trace back:
looper([]) --> 0 #since L has no elements, recursion does not take place

looper([17]) --> sum([looper(x) for x in [17])
-- > sum([5])
--> 5

looper([1, 2, 3, 4, [5, 6]]) --> sum([looper(x) for x in [1, 2, 3, 4, [5, 6]])
--> sum([5, 5, 5, 5, 10]) #we do not solve when the function is executed inside
--> 30

looper(['some', 'here', 5, ['hello', 'there']]) --> sum([looper(x) for x in ['some', 'here', 5, ['hello', 'there']]])
--> sum([5, 5, 5, 10])
--> 25

Wednesday 4 February 2015

Of Properties and property()

Cruising through Piazza I found someone asking a question about property(). While we have not seen it extensively in class (even being snuffed out of our lab), it is a very important function when wanting to exert more control on our object's attribute, thus limiting the control that a client might have over it. This is done for several reasons, one of them being security and another code consistency.

Examples:

  • For security: You have a variable that controls a game's score. You would not want someone to easily change the score by changing the value of the attribute!
  • For consistency: You have a method that involves, among other things, the division of two attributes. You would not want someone to change one of the attributes to 0 by mistake, crashing your program!

The person who posted the question was clearly unsure about how property() works, and the person who answered did not really expand much on its use, thus I decided to intervene and write some example code. Here is that example (with additional code):

class MagicNumber():
 '''
 A magic number with the attributes:
  x: int - The magic number
 '''
 
 def __init__(self):
  '''
  (MagicNumber) --> NoneType
  
  Initializes the magic number (self) with x as 42.
  '''
  #notice that we do not call it x, but rather _x
  #in order to keep it private
  self._x = 42

  def get_x(self):
  '''
  (MagicNumber) --> int
  
  Returns the magic number (x)
  '''
   #we simply return the value of x
   #or any thing we want
   #depends on what you want the client to see
   return self._x

  def set_x(self, num):
  '''
  (MagicNumber, int) --> NoneType
  
  If num is not zero, change the magic number (x) to num * 42.
  Otherwise, changes the value of the magic number to 42.
  '''
   #we can control how the attribute is modified
   #useful when we do not want certain values
   if num != 0:
    self._x = num * 42
   else:
    self._x = 42

  #important part!
  #we define here the properties of the attribute x
  #additionally, add a del_x and descriptor
  x = property(get_x, set_x)

#we do not address self._x, but rather self.x
>>> self.x = 1 #we just called the method set_x
>>> self.x #we just called the method get_x
42

Sunday 1 February 2015

Like Father, like Son == Like Class, like Subclass.

For newcomers to programming, classes and subclasses are an amazing thing. Since part of the core learning in programming is using predefined classes (such as str, int, list, etc.), the ability to create your own classes is just incredible. Don't like how Python manages your strings? Bam! Create a subclass that inherits all of its properties, yet has different / additional functionality through code of your own. If you need a class that combines the power of the different predefined classes, you can always do it by having different objects inside your class.

I love a good analogy! Python delivers a good one with its class / subclass relationship. Just like father and son, in classes and subclasses the child can:
  • Inherit characteristics, in this case attributes and methods
  • Change some of the characteristics (and even create new ones!)
So even if your kid does not resemble you that much, he will still inherit some or all of your characteristics. You might have math in the list self.skills, yet he might have French instead of math. You might have a method called def snores() that is not implemented, yet he might have that method implemented. Even when your kid says "I want to be a Computer Scientist / Software Engineer / Programmer / etc., just like my dad", this will hint that your child might have the same purpose as you have. Bottom line, it is just a happy family of classes and subclasses.

From a programmers point of view, implementing classes in Python is a dead easy task. I'll come upfront and confess my love for Python: it is versatile, powerful, elegant, and so, so easy to use. Hence, let's start with a simple example:

You work for a small animal clinic. You are tasked with creating a combination of class / subclass that will be able to hold the information of each patient. The parent class defines an animal patient, while the subclasses define a specific type of animal (e.g. dog) patient.

**DISCLAIMER: THIS CODE WAS CREATED SPECIFICALLY FOR THIS SLOG. IT NEEDS MORE TESTING! IT WAS CREATED TO SHOW THE POWER OF CLASSES AND SUBCLASSES. YOU MAY USE SNIPPETS OF THIS CODE FREELY, BUT IF YOU WISH TO USE IT ALL, PLEASE LET ME KNOW! IF YOU HAVE ANY QUESTIONS ABOUT ANY STEP, FEEL FREE TO CONTACT ME. FINALLY, IF YOU USE THIS FOR AN ASSIGNMENT, THINK TWICE! IT CONSTITUTES ACADEMIC FRAUD.**

-----------------------------------------

#so that we can use the datetime type for recording dates
import datetime

#dict for switch-like method, inspired by
#Vincent's Slog!
#cool SLOG! go check it out!
dogshotlist = {1: "Booster Shot 1",
               2: "Booster Shot 2",
               3: "Booster Shot 3",
               4: "Rabies",
               5: "Bordatella"}

class AnimalPatient():
    
    def __init__(self, owner, name, breed, bdyear, bdmonth, bdday):
        """
        (AnimalPatient, str, str, str, int, int, int) -> NoneType
        
        """
        
        self.owner = owner
        self.name = name
        self.bday = datetime.date(bdyear, bdmonth, bdday)
        self.breed = breed
        self.shothistory = {}
        self.appointment = False
        self.appdate = datetime.date(2015, 1, 1)
        
    def createapp(self, appyear, appmonth, appday):
        """
        (AnimalPatient, int, int, int) -> NoneType
        
        """
        
        self.appdate = datetime.date(appyear, appmonth, appday)
        self.appointment = True

    def addshot(self, shotnum, shyear, shmonth, shday):
        """
        (AnimalPatient, int, int, int, int) -> NoneType

        Create the next sequential key in dict shothistory that holds
        the shot's name, determined by shotnum, and the date with shyear
        as year, shmonth as month, and shday as day
        
        Needs to be implemented in a subclass.
        
        """        
        #different animals have different shots, so we need a subclass
        #to specify what needs to be done, so we raise an error
        #if this is not used within a subclass with a working method
        
        raise NotImplementedError('Subclass needed')

    def missingshots(self):
        """
        (AnimalPatient) -> str
        
        Return string that displays the missing shots of the patient.
        
        Needs to be implemented in a subclass.

        """        
        #just like in add shot, we need something specific to an animal
        #subclass to execute this method
        raise NotImplementedError('Subclass needed')

    def __repr__(self):
        """
        (AnimalPatient) -> str

        Return string that represents how this object was initialized
        
        """
        
        #string representation of how the object was initialized
        return "AnimalPatient(owner = {}, name = {}, breed = {}, bdyear = {}, bdmonth = {}, bdday = {})".format(self.owner, self.name, self.breed, str(self.bday)[:4], str(self.bday)[5:7], str(self.bday)[8:10])
    
    def __str__(self):
        """
        (AnimalPatient) -> str

        Return string that displays the information held by this object
        
        """
        
        #not all animals have breeds! so you can make a different
        #__str__ method for those animals
        info = "Owner name: {}\nPet name: {}\nBirthday: {}\nBreed: {}".format(self.owner, self.name, self.bday, self.breed)

        shots = ""
        
        i = 1
        
        for shot in self.shothistory:

            shots += shot + ". " + self.shothistory[shot] + "\n"
        
        if self.appointment:
            appointment = "\nAppointment Date: {}".format(self.appdate)
        
        else:
            appointment = "\n**No Appointment Recorded**\n"
            
        return info + shots + appointment
    
    def __eq__(self, other):
        
        self.owner = other.owner
        self.name = other.name
        self.bday = other.bday
        self.shothistory = other.shothistory
        self.appointment = other.appointment
        self.appdate = other.appdate

    #we mention AnimalPatient here so that we can inherit its characteristics
class DogPatient(AnimalPatient):
    def __init__(self, owner, name, breed, bdyear, bdmonth, bdday):
        """
        (DogPatient, str, str, str, int, int, int) -> NoneType
        
        """        
        
        #initializes the parent AnimalPatient type to inherit
        #its methods and attributes
        AnimalPatient.__init__(self, owner, name, breed, bdyear, bdmonth, bdday)
        
        #dogs need anual deworming, hence we make an extra attribute here
        self.lastdeworm = datetime.date(2015, 1, 1)
        
    #this is a method exclusive of DogPatient
    def addlastdeworm(self, dwyear, dwmonth, dwday):
        """
        (DogPatient, int, int, int) -> NoneType
        
        Change lastdeworm date to dwyear for year, dwmonth for month, and 
        dwday for day
        
        """
        
        self.lastdeworm = datetime.date(dwyear, dwmonth, dwday)
    
    def addshot(self, shotnum, shyear, shmonth, shday):
        """
        (DogPatient, int, int, int, int) -> NoneType

        Create the next sequential key in dict shothistory that holds
        the shot's name, determined by shotnum, and the date with shyear
        as year, shmonth as month, and shday as day
        
        """   
        
        if len(self.shothistory) == 0:
            self.shothistory[1] = (dogshotlist[shotnum], datetime.date(shyear, shmonth, shday))
            
        else:
            self.shothistory[len(self.shothistory) + 1] = (dogshotlist[shotnum], datetime.date(shyear, shmonth, shday))
        
    def missingshots(self):
        """
        (AnimalPatient) -> str
        
        Return string that displays the missing shots of the patient.
        Needs to be implemented in a subclass.

        """           
        #creates shallow copy of the dogshotlist to avoid mutation
        missingshots = dict(dogshotlist)
        missingstring = "List of missing shots:\n"
        
        for i in self.shothistory:
            for j in missingshots:
                if self.shothistory[i][0] == missingshots[j]:
                    del missingshots[j]
                else:
                    pass

        if len(missingshots) == 0:
            missingstring = "\n**No missing shots**\n"
        
        else:
            k = 1        
            for l in missingshots:
                missingstring += str(k) + ". " + missingshots[l] + "\n"
                k += 1
            
        return missingstring
                
    #DogPatient has a new attribute, so we need a new eq method
    def __eq__(self, other):
        
        self.owner = other.owner
        self.name = other.name
        self.bday = other.bday
        self.shothistory = other.shothistory
        self.appointment = other.appointment
        self.appdate = other.appdate
        
        #this new attribute forces us to create a new __eq__
        self.lastdeworm = other.lastdeworm
        
        #alternatively, we could call
        #the method AnimalPatient.__eq__(self, other) and add the last line
        #for the extra attribute
    
    #we do need to change repr since the class has a different name
    #we do not make a str method since AnimalPatient has a pretty strong
    #and useful one
    def __repr__(self):
        """
        (DogPatient) -> str

        Return string that represents how this object was initialized
        
        """
        
        #same as for AnimalPatient, except that we need to change
        #AnimalPatient to DogPatient
        return "DogPatient(owner = {}, name = {}, breed = {}, bdyear = {}, bdmonth = {}, bdday = {})".format(self.owner, self.name, self.breed, str(self.bday)[:4], str(self.bday)[5:7], str(self.bday)[8:10])

-----------------------------------------

(I apologize for the poor formatting! I will check how to properly insert code into Blogger at some later stage. For now, you can copy and paste this code into Wing or other Python editor for better visualization!)

Let's test the code!

>>> d = DogPatient('Jose', 'Miguelito', 'Poodle', 2015, 9, 15)
>>> print(d)
Owner name: Jose
Pet name: Miguelito
Birthday: 2015-09-15
Breed: Poodle
**No Appointment Recorded**

>>> print(d.missingshots())
List of missing shots:
1. Booster Shot 1
2. Booster Shot 2
3. Booster Shot 3
4. Rabies
5. Bordatella

>>> d.createapp(2015, 4, 5)
>>> print(d)
Owner name: Jose
Pet name: Miguelito
Birthday: 2015-09-15
Breed: Poodle
Appointment Date: 2015-04-05

As you can see, the function createapp() was inherited from AnimalPatient, and missingshots() is specific to DogPatient.

Try taking this class / subclass combo for a spin ;)