Lecture 3

Warmup

  1. Write a function that simulates a dice roll every time the function is called.

In [41]:
import random

def dice():
    return random.randint(1,6)

def roll_dice(n):
    for i in range(n):
        print(dice())
        
roll_dice(5)


5
6
1
4
2
  1. Rewrite your function to take in an int n and simulate n dice rolls.
  2. Write a function that takes in a list of red and blue balls and on each call, pulls a ball randomly out of the list and updates the list.

In [125]:
balls = ['r', 'r', 'b', 'b', 'b']

def ball_game(num_red, num_blue, select_k):
    balls = ['r']*num_red + ['b']*num_blue
    res = []
    for k in range(select_k):
        random.shuffle(balls)
        res.append(balls.pop())
        print('ratio of blue to red in sack', balls.count('b')/balls.count('r') )
    
    assert len(balls) + len(res) == num_red + num_blue
    
    return ("balls = ", balls,"result = ", res)

#ball_game(1000,100, 1000)

In [62]:
balls.count('r')


Out[62]:
2
  1. Simulate n coin flips for n = 10, 100, 1000, 10000. Are the ratios of heads to tails what you would expect?

In [95]:
def coin(n):
    res = []
    for i in range(n):
        res.append(random.randint(0,1))
    return res

In [82]:
# %load ../examples/compressor
    
def groupby_char(lst):
    """Returns a list of strings containing identical characters.
    
    Takes a list of characters produced by running split on a string. 
    Groups runs (in order sequences) of identical
    characters into string elements in the list.
    
    Parameters
    ---------
    Input:
    lst: list
    A list of single character strings.
    
    Output:
    grouped: list
    A list of strings containing grouped characters."""

    new_lst = []

    count = 1

    for i in range(len(lst) - 1): # we range to the second to last index since we're checking if lst[i] == lst[i + 1]. 
        if lst[i] == lst[i + 1]:
            count += 1        
        else:
            new_lst.append([lst[i],count]) # Create a lst of lists. Each list contains a character and the count of adjacent identical characters.
            count = 1
            
    new_lst.append((lst[-1],count)) # Return the last character (we didn't reach it with our for loop since indexing until second to last).


    grouped = [char*count for [char, count] in new_lst] 

    return grouped

def compress_group(string):
    """Returns a compressed two character string containing a character and a number.
    
    Takes in a string of identical characters and returns the compressed string consisting of the character and the length of the original string.
    
    Example
    -------
    "AAA"-->"A3"

    Parameters:
    -----------
    Input:
    string: str
    A string of identical characters.
    
    Output:
    ------
    compressed_str: str
    A compressed string of length two containing a character and a number.
    """
    

    return str(string[0]) + str(len(string))


def compress(string):
    """Returns a compressed representation of a string.
    
    Compresses the string by mapping each run of identical characters to a 
    single character and a count. 
    
    Ex.
    --
    compress('AAABBCDDD')--> 'A3B2C1D3'.
    
    Only compresses string if the compression is shorter than the original string.

    Ex.
    --
    compress('A')--> 'A' # not 'A1'.

    Parameters
    ----------

    Input:
    string: str
    The string to compress

    Output:
    compressed: str
    The compressed representation of the string.
    """

    try:
        split_str = [char for char in string] # Create list of single characters.
        grouped = groupby_char(split_str) # Group characters if characters are identical.
        compressed = ''.join( # Compress each element of the grouped list and join to a string.
                [compress_group(elem) for elem in grouped])

        if len(compressed) < len(string): # Only return compressed if compressed is actually shorter.
            return compressed
        else:
            return string

    except IndexError: # If our input string is empty, return an empty string.
        return ""

    except TypeError: # If we get something that's not compressible (including NoneType) return None.
        return None


if __name__ == "__main__":
    import sys
    print(sys.argv[0])
    string = sys.argv[1]
    print("string is", string)
    print("compression is", compress(string))


/home/thunder/anaconda3/lib/python3.5/site-packages/ipykernel/__main__.py
string is -f
compression is -f

In [103]:
lst = [0,0,0,0,1,1,1,1,1, 0, 0, 0, 0, 0]
lst_str = [str(elem) for elem in lst]
lst_str


Out[103]:
['0', '0', '0', '0', '1', '1', '1', '1', '1', '0', '0', '0', '0', '0']

In [104]:
grouped = groupby_char(lst_str)
grouped


Out[104]:
['0000', '11111', '00000']

In [105]:
lengths = [len(elem) for elem in grouped]
lengths


Out[105]:
[4, 5, 5]

In [106]:
max(lengths)


Out[106]:
5

In [107]:
def get_max_run(n):
    """Generates n coin flips and returns the max run over all the coin flips"""
    lst = coin(n)
    print(lst)
    lst_str = [str(elem) for elem in lst]

    grouped = groupby_char(lst_str)
    lengths = [len(elem) for elem in grouped]
    return max(lengths)

get_max_run(10)


[1, 0, 0, 0, 0, 0, 1, 0, 1, 0]
Out[107]:
5
  1. Write a function that pulls the text from http://www.py4inf.com/code/romeo-full.txt and displays all the lines containing the word 'love'. Use the requests library.

  2. Find a built in python library that you haven't heard of before. Learn how some of the functions work. Write a small script testing out the functions.

Introducing classes

Python: everything is an object

So everything has a class???

Classes: a blueprint for creating objects


In [108]:
class Customer(object):
    """A customer of ABC Bank with a checking account. Customers have the
    following properties:

    Attributes:
        name: A string representing the customer's name.
        balance: A float tracking the current balance of the customer's account.
    """

    def __init__(self, name, balance=0.0):
        """Return a Customer object whose name is *name* and starting
        balance is *balance*."""
        self.name = name
        self.balance = balance

    def withdraw(self, amount):
        """Return the balance remaining after withdrawing *amount*
        dollars."""
        if amount > self.balance:
            raise RuntimeError('Amount greater than available balance.')
        self.balance -= amount
        return self.balance

    def deposit(self, amount):
        """Return the balance remaining after depositing *amount*
        dollars."""
        self.balance += amount
        return self.balance

In [124]:
help(jeff)


Help on Customer in module __main__ object:

class Customer(builtins.object)
 |  A customer of ABC Bank with a checking account. Customers have the
 |  following properties:
 |  
 |  Attributes:
 |      name: A string representing the customer's name.
 |      balance: A float tracking the current balance of the customer's account.
 |  
 |  Methods defined here:
 |  
 |  __init__(self, name, balance=0.0)
 |      Return a Customer object whose name is *name* and starting
 |      balance is *balance*.
 |  
 |  deposit(self, amount)
 |      Return the balance remaining after depositing *amount*
 |      dollars.
 |  
 |  withdraw(self, amount)
 |      Return the balance remaining after withdrawing *amount*
 |      dollars.
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

Be careful what you __init__

After init has finished, the caller can rightly assume that the object is ready to use. That is, after jeff = Customer('Jeff Knupp', 1000.0), we can start making deposit and withdraw calls on jeff; jeff is a fully-initialized object.


In [ ]:
class Customer(object):
    """A customer of ABC Bank with a checking account. Customers have the
    following properties:

    Attributes:
        name: A string representing the customer's name.
        balance: A float tracking the current balance of the customer's account.
    """

    def __init__(self, name):
        """Return a Customer object whose name is *name*.""" 
        self.name = name

    def set_balance(self, balance=0.0):
        """Set the customer's starting balance."""
        self.balance = balance

    def withdraw(self, amount):
        """Return the balance remaining after withdrawing *amount*
        dollars."""
        if amount > self.balance:
            raise RuntimeError('Amount greater than available balance.')
        self.balance -= amount
        return self.balance

    def deposit(self, amount):
        """Return the balance remaining after depositing *amount*
        dollars."""
        self.balance += amount
        return self.balance

A change in perspective

Objects can have state

Exercise

With your partner:

Create a new class, SMS_store. The class will instantiate SMS_store objects, similar to an inbox or outbox on a cellphone:

my_inbox = SMS_store()

This store can hold multiple SMS messages (i.e. its internal state will just be a list of messages). Each message will be represented as a tuple:

(has_been_viewed, from_number, time_arrived, text_of_SMS)

The inbox object should provide these methods:

my_inbox.add_new_arrival(from_number, time_arrived, text_of_SMS)
    # Makes new SMS tuple, inserts it after other messages
    # in the store. When creating this message, its
    # has_been_viewed status is set False.

my_inbox.message_count()
    # Returns the number of sms messages in my_inbox

my_inbox.get_unread_indexes()
    # Returns list of indexes of all not-yet-viewed SMS messages

my_inbox.get_message(i)
    # Return (from_number, time_arrived, text_of_sms) for message[i]
    # Also change its state to "has been viewed".
    # If there is no message at position i, return None

my_inbox.delete(i)     # Delete the message at index i
my_inbox.clear()       # Delete all messages from inbox

Write the class, create a message store object, write tests for these methods, and implement the methods.


In [288]:
class SMS_store(object):
    
    def __init__(self):
        self.inbox = []
    
    def add_new_arrival(self, from_number, time_arrived, text_of_SMS, has_been_viewed=False):
        # Makes new SMS tuple, inserts it after other messages
        # in the store. When creating this message, its
        # has_been_viewed status is set False.
        
        msg = (has_been_viewed, from_number, time_arrived, text_of_SMS)
        self.inbox.append(msg)
      

    def message_count(self):
        # Returns the number of sms messages in my_inbox
        return len(self.inbox)
  
    def get_unread_indexes(self):
        # Returns list of indexes of all not-yet-viewed SMS messages
        return [i for i, elem in enumerate(self.inbox) if not elem[0]]
"""
    my_inbox.get_message(i)
        # Return (from_number, time_arrived, text_of_sms) for message[i]
        # Also change its state to "has been viewed".
        # If there is no message at position i, return None
        

    my_inbox.delete(i)     # Delete the message at index i
    my_inbox.clear()       # Delete all messages from inbox

"""


Out[288]:
'\n    my_inbox.get_message(i)\n        # Return (from_number, time_arrived, text_of_sms) for message[i]\n        # Also change its state to "has been viewed".\n        # If there is no message at position i, return None\n        \n\n    my_inbox.delete(i)     # Delete the message at index i\n    my_inbox.clear()       # Delete all messages from inbox\n\n'

In [289]:
my_inbox = SMS_store()

In [299]:
my_inbox.add_new_arrival('adasf', 'asdf', 'asdf')
my_inbox.inbox


Out[299]:
[(False, 'adasf', 'asdf', 'asdf'),
 (False, 'adasf', 'asdf', 'asdf'),
 (False, 'adasf', 'asdf', 'asdf'),
 (True, 'adasf', 'asdf', 'asdf'),
 (True, 'adasf', 'asdf', 'asdf'),
 (False, 'adasf', 'asdf', 'asdf')]

In [300]:
my_inbox.get_unread_indexes()


Out[300]:
[0, 1, 2, 5]

In [226]:
class WhatDoesThe(object):
    
    def cow_say():
        return "MOOO"
    
    def elephant_say():
        return "PFHARGLE"
    
    def seal_say():
        return "AUGHAUGHAUGH"
    
    def fox_say():
        return "tingalingalingalinga"
    
    @staticmethod
    def FoxSay(self):
        print(cow_say())
        print(elephant_say())
        print(seal_say())
        print(fox_say())

In [227]:
WhatDoesThe.FoxSay()


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-227-c0a1bc42483c> in <module>()
----> 1 WhatDoesThe.FoxSay()

TypeError: FoxSay() missing 1 required positional argument: 'self'

In [224]:
what_does_the.FoxSay()


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-224-b1b4ddb9b2d6> in <module>()
----> 1 what_does_the.FoxSay()

TypeError: FoxSay() takes 0 positional arguments but 1 was given

Class attributes

Class attributes are attributes that are set at the class-level, as opposed to the instance-level. Normal attributes are introduced in the init method, but some attributes of a class hold for all instances in all cases. For example, consider the following definition of a Car object:


In [12]:
class Car(object):

    wheels = 4

    def __init__(self, make, model):
        self.make = make
        self.model = model

In [14]:
mustang = Car('Ford', 'Mustang')
print(mustang.wheels)


4

In [15]:
print(Car.wheels)


4

Static methods


In [18]:
class Car(object):

    wheels = 4
    
    def make_car_sound():
        print('VRooooommmm!')

    def __init__(self, make, model):
        self.make = make
        self.model = model

In [25]:
my_car = Car('ford', 'mustang')

# my_car.make_car_sound() # This will break
Car.make_car_sound()


VRooooommmm!
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-25-8663117d7689> in <module>()
      1 my_car = Car('ford', 'mustang')
      2 Car.make_car_sound()
----> 3 my_car.make_car_sound()

TypeError: make_car_sound() takes 0 positional arguments but 1 was given

In [26]:
class Car(object):

    wheels = 4
    
    @staticmethod
    def make_car_sound():
        print('VRooooommmm!')

    def __init__(self, make, model):
        self.make = make
        self.model = model

In [27]:
my_car = Car('ford', 'mustang')

In [28]:
my_car.make_car_sound()


VRooooommmm!