Wednesday, 25 February 2015

Entry #3: Recursion, Recursion, Recursion! (As Well As A Summary On OOP)

NOTE: I would like this blog entry to be graded.

Recursion is perhaps one of the more interesting parts of programming as it manages to simplify processes that would otherwise be more convoluted to code. It focuses on using properties already displayed in a function again when a certain case comes up. Recursive functions are very helpful for going over nested lists or tuples, restarting games or when a while loop is too inconvenient or complicated to be implemented. I had already messed around with a sort of simple version of recursion before, which I'll demonstrate in this first function.

def recurs(n):
    if n % 2 == 1 and n < 16:
        if n in [11, 13, 15]:
            n = n - 7
            print(n)
            recurs(n)            
        else:
            n = abs(n) * 2
            print(n)
            recurs(n)            
    elif n % 2 == 0 and n < 16:
        n = n - 11
        print(n)
        recurs(n)
    else:

        print(str(n) + ' is greater than 16')

This function has the interesting property of printing each number that goes through its designated step and returning '18 is greater than 16' for any number below 16. For example, recurs(7) produces:

14
3
6
-5
10
-1
2
-9
18

18 is greater than 16

It's easy for anyone to see that the function could very well just be a while loop, but it's fun to play around with this sort of style of repeating steps. But let's start going with functions that are more reminiscent of work we've done in class/labs.

def sumlen(L):
    """ (arbitrarily nested list of int) -> int
    
    Return the sum of the ints in the list divided by the length of the list
    """
    if isinstance(L, list):
        return int(sum([sumlen(x) for x in L]) / len(L))
    else:

        return L

The docstring clearly explains what the function does, and for the sake of analyzation, we'll look at some examples on how this function works.

>>> sumlen([1, 2, 3])

The list's sum is 1+2+3 and the length is 3. 6 / 3 = 2

>>> sumlen([10, [1, 2, 3], 3])
    
We have inside a recursive list. The whole list is length 3 and the inner list is length 3. Since we already calculated it's value, we know that what we're adding is 10+2+3 which divided by 3 would produce 5.

>>> sumlen([1, [2, 3, [4, 5], 6], 7, 9])

Now we have a recursive list of depth 2. First we'd concern ourselves with the most inner list which is int((4+5)/2) = 4. Now the 1st-depth inner list results in int((2+3+4+6)/4) = 3. This leaves with the list as [1, 3, 7, 9] which is (1+3+7+9)/4 = 5.

>>> sumlen([1, [1, 3], 2, [10, 15, 2], 3])

Again, we consider the inner lists first and then the whole list. The whole list would be [1, 2, 2, 9, 3] whose sum is 17 and divided by 5 and floored (or converted to an int) results in 3

Here we have a more complicated function.

def negative_hiccup(L):
    """ (arbitrarily nested list of int) -> int
    
    Consider a man who adds two numbers, but then subtracts the third
    number, following a pattern of (0 + a + b - c + d + e - f...). Return
    an int based on this pattern.
    
    """
    
    result = 0
    
    for i in range(len(L)):
        if i % 3 == 0 or i % 3 == 1:
            if isinstance(L[i], list):
                result = result + negative_hiccup(L[i])
            else:
                result = result + L[i]
        else:
            if isinstance(L[i], list):
                result = result - negative_hiccup(L[i])
            else:
                result = result - L[i]     
                

    return result

Technically, we could have converted this into a helper function for just a list and then used it recursively if we wanted it to consider it for a recursive list, but this is still a valid way of implementing it. Let's look at the examples.

>>> negative_hiccup([4])

This is simply 4 as the first item is added to 0.

>>> negative_hiccup([4, 1])

The pattern goes 0 + 4 + 1, so it results in 5.
    
>>> negative_hiccup([4, 1, 5])
    
As a reminder, when we have the third item (or an item in an index which % by 3 results in 2), we subtract it from the total, so 0 + 4 + 1 - 5 results in 0.

>>> negative_hiccup([4, 1, 5, 2])

Again, we know the pattern, so it'll just be 2. Where's the fun stuff?!

>>> negative_hiccup([4, 1, [5, 2]])

Ah! Now we've got something going on. See, when we hit index 2, the function considers the list as the start of another negative-hiccup calculation, adding 5 and 2 to 0. This makes the whole list [4, 1, 7]. So now when negative_hiccup looks over the whole list, it simply subtracts 7, leaving -2.

>>> negative_hiccup([4, 1, 5, 2, 3])
    
Gah, boring stuff again! It's 5! Next!

>>> negative_hiccup([4, 1, [5, 2, 3]])
   
Now, we're back to same example in which we just go through the pattern anew on the list. This time it's 0 + 5 + 2 - 3 which results in the whole list being [4, 1, 4], resulting in 1.

>>> negative_hiccup([4, 1, 5, 2, 3, 10])

Okay, back to this tripe, it's -5!

>>> negative_hiccup([4, [1, 5], 2, [3, 10]])

By now we know what to do with those inner lists, so we basically would go like this:

0 + 4 + (1 + 5) - 2 + (3 + 10) = 0 + 4 + 6 - 2 + 13 = 21

>>> negative_hiccup([4, [1, [5, 2, 3], 10]])

Oh! Now we got a deeper list to look at. By now we know we start with 4, but in the second index, we have [1, [5, 2, 3], 10]. We would have to go by the negative-hiccup calculation in the second index's inner list which result in the second index being reduced to [1, 4, 10] which equals -5. When added to 4, we get -1.

>>> negative_hiccup([45, [10, 17, [8, 9, 5], [15, [19, 32, [27, [5, 2, [3, 7, 2]], 39], 11], 10, 48], 33], 6, 24, 10, [5, 24]])

Uh...let's just say 220 and leave it at that.

So, there you have a little bit of recursive function fun!

##########################################################################

Object Oriented Programming Concept Summary:


  • Object Oriented Programming is focused on using custom classes with various special methods and properties included, some of which serve as Abstract Data Types (like lists, stacks, queues, trees).
  • Stacks follow a First In Last Out format in which objects are pushed to the top and then popped when necessary. It's easy to think of a stack because one can imagine a pile of books used for a project. You get the one on the top, use it's information and then follow with the next one. 
  • Queues follow a First In First Out format in which objects are enqueued into a queue and then dequequed when necessary. A very prominent example of a queue is a playlist. No matter what you add to it, the song you put in first plays. 
  • Trees are constructed with nodes, some which have children and others which do not (called leaves). A tree is a very useful ADT to visualize as based on the logic one applies to it, it can lead one to understand the relationship between a node and its children. Trees require an understanding of recursion as they are far more complex to implement than both stacks and queues. 

Monday, 2 February 2015

Entry #2: Creating A Lottery Ticket Subclass And Superclass

Recently we had learned about creating superclasses and subclasses and implementing them into the course. I decided that I wanted to demonstrate my understanding of this by making a lottery ticket using this approach. First I had to consider the barebones of any lottery ticket. These barebones would be essential in implementing the superclass. The idea behind a lottery ticket is that it contains information which based on certain rules will give out a prize. A ticket tends to be "empty" until it is revealed. Once it is revealed, then the prize is determined. 

This means:


  • A lottery ticket needs contents and a place to hold the prize
  • A lottery ticket starts off empty or "not revealed"
  • When a lottery ticket is revealed, the contents are set on the ticket
  • Having the contents, one can then determine the prize. 

So with that in mind, I create my lottery ticket superclass:


class LotteryTicket():
    """ A class to represent a lottery ticket """
    
    def __init__(self):
        """ (LotteryTicket) -> NoneType
        
        Initialize a lottery ticket to contain contents and prize
        
        >>> lot = LotteryTicket()
        >>> isinstance(lot, LotteryTicket)
        True
        >>> lot.contents
        ''
        >>> lot.prize
        0
        """
        
        self.contents = ''
        self.prize = 0

Here, it is noted that contents and prize are set right from the get-go, though since we are not sure what the contents will be, we leave it as a blank string. Prize is also set to 0 since we are not aware of what it will be based on the contents.
    
    def __str__(self):
        """ (LotteryTicket) -> str
        
        Return a string representation of LotteryTicket
        
        >>> lot = LotteryTicket()
        >>> print(lot)
        LotteryTicket
        Contents: 
        Prize: 0
        """
        
        return "LotteryTicket\nContents: {0}\nPrize: {1}".format(self.contents, self.prize)

I've defined the __str__ method more to give an idea of what a generic ticket should look like. 
    
    def __repr__(self):
        """ (LotteryTicket) -> str
        
        Return a shell representation of LotteryTicket
        
        """
        
        raise NotImplementedError('Subclass needed')
    
    
    def __eq__(self, other):
        """ (LotteryTicket, LotteryTicket) -> bool
        
        Return whether two LotteryTickets are the same
        
        """
        
        raise NotImplementedError('Subclass needed')
    
    def set_ticket(self):
        """ (LotteryTicket) -> NoneType
        
        Set the contents of LotteryTicket
        
        """
        
        raise NotImplementedError('Subclass needed')
    
    def get_prize(self):
        """ (LotteryTicket) -> NoneType
        
        Obtain prize based on the contents of LotteryTicket
        
        """
        
        raise NotImplementedError('Subclass needed')

All of these methods have not been implemented but they are crucial to the skeleton of a lottery ticket so they've been included for the sake of reference and convenience. 

Now that I have a basic representation of a generic lottery ticket dubbed LotteryTicket, I can now start to think of making more specific LotteryTickets. There are many ways I can go about making more specific lottery tickets and I have even managed to come up with a few. But for the sake of convenience, I'm going to pool all of these ideas into one LotteryTicket subclass I'll call MegaWinningsTicket. 

from LotteryTicket import LotteryTicket

import random

class MegaWinningsTicket(LotteryTicket):
    """ A lottery ticket with Mega Winnings! 
    
    Price of MegaWinningsTicket determine which game you play!

    5 - Pick 2
    Try to get the winning two numbers!
    10 - Pick 3
    Try to get the winning three numbers!
    15 - Crossword
    Try to get the necessary letters to complete the crossword!
    20 - Golden Diamond

    Try to get the elusive Golden Diamond!
    """
    
    def __init__(self, price):
        """ (MegaWinningsTicket) -> NoneType
        
        Precondition: price must be in [5, 10, 15, 20]
            
        Initialize a MegaWinnings lottery ticket to contain contents and prize
        and to hold the value of price
            
        >>> mega = MegaWinningsTicket(5)
        >>> isinstance(mega, MegaWinningsTicket)
        True
        >>> mega.contents
        ''
        >>> mega.prize
        0
        >>> mega.price
        5
        """
            
        LotteryTicket.__init__(self)

        self.price = price

Notice that now there is an added parameter in MegaWinningsTicket dubbed price. While it may not be advisable to name this parameter price since it is so close to prize, this little bit allows me to extend the __init__ method to suit this subclass. Specifically, the price allows you to play a certain game that the MegaWinningsTicket can support. Let's go through each of them in order.


  • Pick 2
    Price: 5
    Contents: 2 numbers
    Objective: Obtain two numbers that are exactly the same to the results
    Prize: 2500 for both numbers
    Odds of winning the big prize: 1 in 100
  • Pick 3
    Price: 10
    Contents: 3 numbers
    Objective: Obtain three numbers that are exactly the same to the results
    Prize: 10000 for complete match (big prize), 5000 for two numbers that match
    Odds of winning the big prize: 1 in 1000
  • Crossword
    Price: 15
    Contents: 10 letters
    Objective: To get all the words in this crossword

      r
    c a b
      r
    b e l l
    a     a
    r     b
    e r r
    Prize: 50000 for all words removed (big prize), 100 for 5 words removed, 50 for 4 words removed, 25 for 3 words removed, 10 for 2 words removed, 2 for one removed
    Odds of winnings the big prize: 1 in 25294 (5311735 possible combinations / 210 that contain the letters c, a, b, r, e, l)
  • Golden Diamond
    Price: 20
    Contents: One of the following objects: Golden Diamond, Diamond, Gold, Zirconium, Pyrite, Gold-Painted Rock, Shiny Rock
    Objective: Obtain a golden diamond
    Prize: 10000000 for a golden diamond (big prize), 500000 for a diamond, 100000 for gold, 1000 for zirconium, 20 for pyrite
    Odds of winning the big prize:
    1 in 111111 (1 Golden Diamond + 10 Diamonds + 100 Gold + 1000 Zirconium + 10000 Pyrite + 50000 Gold-Painted Rocks + 50000 Shiny Rocks)
Each of these will be shown in more detail but one can see that there will have to be an implementation of __eq__ that is specific to this function 

def __str__(self):
        """ (MegaWinningsTicket) -> str
               
        Return a string representation of MegaWinningsTicket
               
        >>> mega = MegaWinningsTicket(5)
        >>> print(mega)
        MegaWinningsTicket
        Contents: 
        Prize: 0
        Price: 5
        """
        
        return "MegaWinningsTicket\nContents: {0}\nPrize: {1}\nPrice: {2}".format(self.contents, self.prize, self.price)

For the __str__ method, I overwrote the method so that it would also include the price in the string.
    
    def __repr__(self):
        """ (MegaWinningsTicket) -> str
           
        Return a shell representation of MegaWinningsTicket
        
        >>> mega = MegaWinningsTicket(5)
        >>> mega
        MegaWinningsTicket(, 0, 5)
        """    
        
        return "MegaWinningsTicket({0}, {1}, {2})".format(self.contents, self.prize, self.price)

I have implemented the __repr__ method more as practice in doing so since there is no purpose it serves in the code.

def __eq__(self, other):
        """ (MegaWinningsTicket, MegaWinningsTicket) -> bool
        
        Return whether two MegaWinningsTickets are the same
        
        >>> mega = MegaWinningsTicket(5)
        >>> winnings = MegaWinningsTicket(5)
        >>> ticket = MegaWinningsTicket(10)
        >>> mega == winnings
        True
        >>> mega == ticket
        False
        >>> mega.contents = '3'
        >>> mega == winnings
        False
        >>> winnings.contents = '3'
        >>> winnings.prize = 20
        >>> mega == winnings
        True
        """
        
        return self.contents == other.contents and self.price == other.price

Here, I've made it clear that contents have to be the same as well as price (which isn't necessary in the following code, but asserts that the tickets belong to the same game). This brings up a question as to why I didn't have this implemented in the LotteryTicket superclass:

return self.contents == other.contents

Well that is because the way that certain tickets and games conduct themselves may rely on checking for equality in different ways, which may be a matter of parameters that the LotteryTicket subclass contains or what is being checked in contents.

 def set_ticket(self):
        """ (MegaWinningsTicket) -> NoneType
        
        Set the contents of MegaWinningsTicket based on price
        
        """
        
        number_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
        
        if self.price == 5:  # Pick 2
            self.contents = [random.choice(number_list), random.choice(number_list)]
            
        if self.price == 10: # Pick 3
            self.contents = [random.choice(number_list), random.choice(number_list), random.choice(number_list)]
            
        if self.price == 15: # Crossword
            alphabet = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
            words = ['cab', 'bell', 'rare', 'err', 'bare', 'lab']
            print('The words are: {}'.format(words))
            letters = []
            
            for i in range(10):
                letter = random.choice(alphabet)
                letters.append(letter)
                alphabet.remove(letter)
                
            print('Your letters are: {}'.format(letters))
            new_words = []
            
            for word in words:
                for letter in letters:
                    word = word.replace(letter, '')
                new_words.append(word)
                
            print(new_words)
            self.contents = new_words.count('')
            
        if self.price == 20: #Golden Diamond
            
            gem_list = ['Golden Diamond'] + ['Diamond'] * 10 + ['Gold'] * 100 + ['Zirconium'] * 1000 + ['Pyrite'] * 10000 + ['Gold-Painted Rock'] * 50000 + ['Shiny Rock'] * 50000
            self.contents = random.choice(gem_list)

Now this is where we get to the implementation of an important part of the LotteryTicket subclass, that being the set_ticket method which "reveals" the ticket by setting the contents depending on the price. For Pick 2, a list of two numbers is set. For Pick 3, a list of three numbers is set. For Crossword, the amount of words removed is set. And lastly, for Golden Diamond, a string is set. Having contents set will then help us to figure out how to get the prize.
            
    def get_prize(self):
        """ (MegaWinningsTicket) -> NoneType
        
        Precondition: self.price == 15 or self.price == 20 and MegaWinningsTicket
        has been given contents by the set_ticket method
        
        Obtain prize based on the contents of MegaWinningsTicket
        
        """
        
        if self.price == 15:
            possible_winnings = [0, 2, 10, 25, 50, 100, 50000]
            self.prize = possible_winnings[self.contents]
        
        if self.price == 20:
            possible_winnings = {'Gold Diamond': 10000000, 'Diamond': 500000, 'Gold': 100000, 'Zirconium': 1000, 'Pyrite': 20, 'Gold-Painted Rock': 0, 'Shiny Rock': 0}
            self.prize = possible_winnings[self.contents]

Notice how in the docstring, it specifically indicates that set_ticket must have been run on the MegaWinningsTicket for get_prize to properly work. For Crossword, the contents is used as an index on the list possible_winnings. For example, if 2 words were removed, 2 is the contents. And if 2 is the contents, the prize is possible_winnings[2] which equals 10, thus getting the prize and setting it. Golden Diamond works similarly with the contents serving as a key in the dict possible_winnings. These approaches do not rely on another ticket as the results, so therefore Pick 2 and Pick 3 need their own method
    
    def pick_prize(self, results):
        """ (MegaWinningsTicket, MegaWinningsTicket) -> NoneType
        
        Precondition: self.price == 5 or self.price == 10 and both MegaWinningsTickets
        has been given contents by the set_ticket method
        
        Obtain prize based on how well the ticket compares with results
        
        >>> mega = MegaWinningsTicket(5)
        >>> results = MegaWinningsTicket(5)
        >>> mega.contents = [4, 2]
        >>> results.contents = [4, 2]
        >>> mega.pick_prize(results)
        >>> mega.prize
        2500
        >>> mega.contents = [0, 2]
        >>> mega.pick_prize(results)
        >>> mega.prize
        0
        >>> mega = MegaWinningsTicket(10)
        >>> results = MegaWinningsTicket(10)
        >>> mega.contents = [1, 2, 3]
        >>> results.contents = [1, 2, 3]
        >>> mega.pick_prize(results)
        >>> mega.prize
        10000
        >>> mega.contents = [1, 2, 4]
        >>> mega.pick_prize(results)
        >>> mega.prize
        5000
        >>> mega.contents = [1, 5, 4]
        >>> mega.pick_prize(results)
        >>> mega.prize
        0
        """
        
        if self.price == 5:
            if self == results:
                self.prize = 2500
            else:
                self.prize = 0
        
        if self.price == 10:
            numbers = list(results.contents)
            for num in self.contents:
                if num in numbers:
                    numbers.remove(num)
            if len(numbers) == 0:
                self.prize = 10000
            if len(numbers) == 1:
                self.prize = 5000
            if len(numbers) >= 2:
                self.prize = 0

Pick 2 was easy to implement since it just relies on using what we wrote on with __eq__. Pick 3 needed to check if at the very least 2 numbers were the same, so we couldn't have simply used the __eq__ approach. Though we could have if the conditions were equally as harsh.
                    
    def is_mega_winner(self):
        """ (MegaWinningsTicket) -> str
        
        Return a string indicating if MegaWinningsTicket is a winner 
        
        >>> mega = MegaWinningsTicket(5)
        >>> mega.is_mega_winner()
        'Sorry, try again'
        >>> mega.prize = 1000
        >>> mega.is_mega_winner()
        'Sorry, try again'
        >>> mega.prize = 2500
        >>> mega.is_mega_winner()
        'MEGA WINNER!'
        >>> mega = MegaWinningsTicket(10)
        >>> mega.is_mega_winner()
        'Sorry, try again'
        >>> mega.prize = 10000
        >>> mega.is_mega_winner()
        'MEGA WINNER!'
        >>> mega = MegaWinningsTicket(15)
        >>> mega.is_mega_winner()
        'Sorry, try again'
        >>> mega.prize = 50000
        >>> mega.is_mega_winner()
        'MEGA WINNER!'
        >>> mega = MegaWinningsTicket(20)
        >>> mega.is_mega_winner()
        'Sorry, try again'
        >>> mega.prize = 100000
        >>> mega.is_mega_winner()
        'MEGA WINNER!'
        >>> mega.prize = 10000000
        >>> mega.is_mega_winner()
        'ULTIMATE MEGA WINNER! CONGRATULATIONS'
        """
        
        if self.prize == 10000000:
            return 'ULTIMATE MEGA WINNER! CONGRATULATIONS'
        elif 10000000 > self.prize >= 2500:
            return 'MEGA WINNER!'
        else:
            return 'Sorry, try again'

Last and not least is a simple method that tells us whether or not the prize we have is worth of a mega win. A mega win is dictated by if we get a prize who is larger than 2500. Though it we were to get the Golden Diamond in Golden Diamond, we would receive a more congratulatory string. 

There's so many other subclasses of LotteryTicket we can create, each having their own implementations of the methods necessary for the class, some which may overwrite the code, others which may extend it. Though one thing is for sure...trying to win these games might not be as simple.