STAT 19000: Project 12 — Spring 2021

Motivation: We’d be remiss spending almost an entire semester solving data driven problems in python without covering the basics of classes. Whether or not you will ever choose to use this feature in your work, it is best to at least understand some of the basics so you can navigate libraries and other code that does use it.

Context: We’ve spent nearly the entire semester solving data driven problems in python, and now we are going to learn about one of the primary features in python: classes. Python is an object oriented programming language, and as such, much of python, and the libraries you use in python are objects which have properties and methods. In this project we will explore some of the terminology and syntax relating to classes.

Scope: python

Learning objectives
  • Explain the basics of object oriented programming, and what a class is.

  • Use classes to solve a data-driven problem.

Make sure to read about, and use the template found here, and the important information about projects submissions here.

Dataset

In this project, and the next, we will learn about classes by simulating a simplified version of Blackjack! Don’t worry, while this may sound intimidating, there is very little coding involved, and much of the code will be provided to you to use!

Questions

Question 1

Create a Card class with a number and a suit. The number should be any number between 2-10 or any of a 'J', 'Q', 'K', or 'A' (for Jack, Queen, King, or Ace). The suit should be any of: "Clubs", "Hearts", "Spades", or "Diamonds". You should initialize a Card by first providing the number then the suit. Make sure that any provided number is one of our valid values, if it isn’t, throw an exception (that is, stop the function and return a message):

Here are some examples to test:

my_card = Card(11, "Hearts") # Exception: Number wasn't 2-10 or J, Q, K, or A.
my_card = Card(10, "Stars") # Suit wasn't one of: clubs, hearts, spades, or diamonds.
my_card = Card("10", "Spades")
my_card = Card("2", "clubs")
my_card = Card("2", "club") # Suit wasn't one of: clubs, hearts, spades, or diamonds.

To raise an exception, you can do:

raise Exception("Suit wasn't one of: clubs, hearts, spades, or diamonds.")

Here is some starter code to fill in:

class Card:
    _value_dict = {"2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8":8, "9":9, "10": 10, "j": 11, "q": 12, "k": 13, "a": 14}
    def __init__(self, number, suit):
        # if number is not a valid 2-10 or j, q, k, or a
        # raise Exception

        # else set the value for self.number to str(self.number)
        # if the suit.lower() isn't a valid suit: clubs hearts diamonds spades
        # raise Exception

        # else, set the value for self.suit to suit.lower()

Accept both upper and lowercase variants for both suit and number. To do this, convert any input to lowercase prior to processing/saving. For number, you can do str(num).lower().

Items to submit
  • Python code used to solve the problem.

  • Output from running your code.

Question 2

Usually when we talk about a particular card, we say it is a "Four of Spades" or "King of Hearts", etc. Right now, if you print(my_card) you will get something like <main.Card object at 0x7fccd0523208>. Not very useful. We have a dunder method for that!

Implement the __str__ dunder method to work like this:

print(Card("10", "Spades")) # 10 of spades
print(Card("2", "clubs")) # 2 of clubs

Another, closely related dunder method is called __repr__ — short for representation. This is similar to __str__ in that it should print the code used to create the object being printed. So for our examples:

repr(Card("10", "Spades")) # Card(str(10), "spades")
repr(Card("2", "clubs")) # Card(str(2), "clubs")

Implement both dunder methods to function as exemplified.

This article has examples of both __str__ and __repr__.

Items to submit
  • Python code used to solve the problem.

  • Output from running your code.

Question 3

It is natural that we should be able to compare cards, after all thats necessary to play nearly any game. Typically there are two ways to "sort" cards. Ace high, or ace low. Ace high is when the Ace represents the highest card.

Implement the following dunder methods to enable comparison of cards where ace is high:

  • __eq__

  • __lt__

  • __gt__

Make sure the following examples work:

card1 = Card(2, "spades")
card2 = Card(3, "hearts")
card3 = Card(3, "diamonds")
card4 = Card(3, "Hearts")
card5 = Card("A", "Spades")
card6 = Card("A", "Hearts")
card7 = Card("K", "Diamonds")
print(card1 < card2) # True
print(card1 < card3) # True
print(card2 == card3) # True
print(card2 == card4) # True
print(card3 < card4) # False
print(card4 < card3) # False
print(card5 > card4) # True
print(card5 > card6) # False
print(card5 == card6) # True
print(card7 < card5) # True
print(card7 > card1) # True

Two cards are deemed equal if they have the same number, regardless of their suits.

There are many ways to deal with comparing the "JKQA" against other numbers. One possibility is to have a dict that maps the value of the card to it’s numeric value.

This example shows a short example of how to implement these dunder methods.

Items to submit
  • Python code used to solve the problem.

  • Output from running your code.

Question 4

We’ve provided you with the code below:

class Deck:
    _suits = ["clubs", "hearts", "diamonds", "spades"]
    _numbers = [str(num) for num in range(2, 11)] + list("jqka")
    def __init__(self):
        self.cards = [Card(number, suit) for suit in self._suits for number in self._numbers]

As you can see, we are working on building a Deck class. Use the code provided and create an instance of a new Deck called lucky_deck. Print the cards out to make sure it looks right. Make sure that the Deck has the correct number of cards, print the len of the Deck. What happens? Instead of trying to find the length, try to access and print a single card: print(lucky_deck[10]). What happens?

Items to submit
  • Python code used to solve the problem.

  • Output from running your code.

  • 1-2 sentences explaining what happens when you try doing what we ask.

Question 5

As it turns out, we can fix both of the issues we ran into in question (4). To fix the issue with len, implement the __len__ dunder method. Does it work now?

To fix the indexing issue, implement the __getitem__ dunder method. Test out (but don’t forget to re-run to get an updated lucky_deck):

# make sure to re-create your Deck below this line
# these should both work now
len(lucky_deck) # 52
print(lucky_deck[10]) # q of clubs

This article has examples of both __len__ and __getitem__.

Items to submit
  • Python code used to solve the problem.

  • Output from running your code.