On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com

How To Guides
Virtualization
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions

Class Definition Exercises

These exercises are considerably more sophisticated then the exercises in previous parts. Each of these sections describes a small project that requires you to create a number of distinct classes which must collaborate to produce a useful result.

Stock Valuation

A `Block` of stock has a number of attributes, including a purchase price, purchase date, and number of shares. Commonly, methods are needed to compute the total spent to buy the stock, and the current value of the stock. A `Position` is the current ownership of a company reflected by all of the blocks of stock. A `Portfolio` is a collection of `Positions`; it has methods to compute the total value of all `Blocks` of stock.

When we purchase stocks a little at a time, each `Block` has a different price. We want to compute the total value of the entire set of `Block`s, plus an average purchase price for the set of `Block`s.

The StockBlock class. First, define a `StockBlock` class which has the purchase date, price per share and number of shares. Here are the method functions this class should have.

`__init__`

The `__init__` method will populate the individual fields of date, price and number of shares. Don't include the company name or ticker symbol; this is information which is part of the `Position`, not the individual blocks.

`__str__`

The `__str__` method must return a nicely formatted string that shows the date, price and shares.

`getPurchValue`

The `getPurchValue` method should compute the value as purchase price per share × shares.

``` getSaleValue ```( `salePrice` )

The `getSaleValue` method requires a `salePrice` ; it computes the value as sale price per share × shares.

``` getROI ```( `salePrice` )

The `getROI` method requires a `salePrice` ; it computes the return on investment as (sale value - purchase value) ÷ purchase value.

We can load a simple database with a piece of code the looks like the following. The first statement will create a sequence with four blocks of stock. We chose variable name that would remind us that the ticker symbols for all four is 'GM'. The second statement will create another sequence with four blocks.

```blocksGM = [
StockBlock( purchDate='25-Jan-2001', purchPrice=44.89, shares=17 ),
StockBlock( purchDate='25-Apr-2001', purchPrice=46.12, shares=17 ),
StockBlock( purchDate='25-Jul-2001', purchPrice=52.79, shares=15 ),
StockBlock( purchDate='25-Oct-2001', purchPrice=37.73, shares=21 ),
]
blocksEK = [
StockBlock( purchDate='25-Jan-2001', purchPrice=35.86, shares=22 ),
StockBlock( purchDate='25-Apr-2001', purchPrice=37.66, shares=21 ),
StockBlock( purchDate='25-Jul-2001', purchPrice=38.57, shares=20 ),
StockBlock( purchDate='25-Oct-2001', purchPrice=27.61, shares=28 ),
]

```

The Position class. A separate class, `Position`, will have an the name, symbol and a sequence of `StockBlocks` for a given company. Here are some of the method functions this class should have.

`__init__`

The `__init__` method should accept the company name, ticker symbol and a collection of `StockBlock` instances.

`__str__`

The `__str__` method should return a string that contains the symbol, the total number of shares in all blocks and the total purchse price for all blocks.

`getPurchValue`

The `getPurchValue` method sums the purchase values for all of the `StockBlocks` in this `Position`. It delegates the hard part of the work to each `StockBlock`'s `getPurchValue` method.

``` getSaleValue ```( `salePrice` )

The `getSaleValue` method requires a `salePrice` ; it sums the sale values for all of the `StockBlocks` in this `Position`. It delegates the hard part of the work to each `StockBlock`'s `getSaleValue` method.

`getROI`

The `getROI` method requires a `salePrice` ; it computes the return on investment as (sale value - purchase value) ÷ purchase value. This is an ROI based on an overall yield.

We can create our `Position` objects with the following kind of initializer. This creates a sequence of three individual `Position` objects; one has a sequence of GM blocks, one has a sequence of EK blocks and the third has a single CAT block.

```portfolio= [
Position( "General Motors", "GM", blocksGM ),
Position( "Eastman Kodak", "EK", blocksEK )
Position( "Caterpillar", "CAT",
[ StockBlock( purchDate='25-Oct-2001',
purchPrice=42.84, shares=18 ) ] )
]
```

An Analysis Program. You can now write a main program that writes some simple reports on each `Position` object in the `portfolio`. One report should display the individual blocks purchased, and the purchase value of the block. This requires iterating through the `Positions` in the `portfolio`, and then delegating the detailed reporting to the individual `StockBlocks` within each `Position`.

Another report should summarize each position with the symbol, the total number of shares and the total value of the stock purchased. The overall average price paid is the total value divided by the total number of shares.

In addition to the collection of `StockBlock`s that make up a `Position`, one additional piece of information that is useful is the current trading price for the `Position`. First, add a `currentPrice` attribute, and a method to set that attribute. Then, add a `getCurrentValue` method which computes a sum of the `getSaleValue` method of each `StockBlock`. using the trading price of the `Position`.

Annualized Return on Investment. In order to compare portfolios, we might want to compute an annualized ROI. This is ROI as if the stock were held for eactly one year. In this case, since each block has different ownership period, the annualized ROI of each block has to be computed. Then we return an average of each annual ROI weighted by the sale value.

The annualization requires computing the duration of stock ownership. This requires use of the `time` module. We'll cover that in depth in Chapter 32, Dates and Times: the `time` and `datetime` Modules . The essential feature, however, is to parse the date string to create a time object and then get the number of days between two time objects. Here's a code snippet that does most of what we want.

````>>> `

`import time`

`>>> `

`dt1= "25-JAN-2001"`

`>>> `

`timeObj1= time.strptime( dt1, "%d-%b-%Y" )`

`>>> `

`dayNumb1= int(time.mktime( timeObj1 ))/24/60/60`

`>>> `

`dt2= "25-JUN-2001"`

`>>> `

`timeObj2= time.strptime( dt2, "%d-%b-%Y" )`

`>>> `

`dayNumb2= int(time.mktime( timeObj2 ))/24/60/60`

`>>> `

`dayNumb2 - dayNumb1`

`151`
```

In this example, `timeObj1` and `timeObj2` are time structures with details parsed from the date string by `time.strptime`. The `dayNumb1` and `dayNumb2` are a day number that corresponds to this time. Time is measured in seconds after an epoch; typically January 1, 1970. The exact value doesn't matter, what matters is that the epoch is applied consistently by `mktime`. We divide this by 24 hours per day, 60 minutes per hour and 60 seconds per minute to get days after the epoch instead of seconds. Given two day numbers, the difference is the number of days between the two dates. In this case, there are 151 days between the two dates.

All of this processing must be encapsulated into a method that computes the ownership duration.

```def ownedFor(self, saleDate ):```

This method computes the days the stock was owned.

```def annualizedROI(self, salePrice , saleDate ):```

We would need to add an `annualizedROI` method to the `StockBlock` that divides the gross ROI by the duration in years to return the annualized ROI. Similarly, we would add a method to the `Position` to use the `annualizedROI` to compute the a weighted average which is the annualized ROI for the entire position.

Dive Logging and Surface Air Consumption Rate

The Surface Air Consumption Rate is used by SCUBA divers to predict air used at a particular depth. If we have a sequence of `Dive` objects with the details of each dive, we can do some simple calculations to get averages and ranges for our air consumption rate.

For each dive, we convert our air consumption at that dive's depth to a normalized air consumption at the surface. Given depth (in feet), d , starting tank pressure (psi), s , final tank pressure (psi), f , and time (in minutes) of t , the SACR, c , is given by the following formula.

Typically, you will average the SACR over a number of similar dives.

The `Dive` Class. You will want to create a `Dive` class that contains attributes which include start pressure, finish pressure, time and depth. Typical values are a starting pressure of 3000, ending pressure of 700, depth of 30 to 80 feet and times of 30 minutes (at 80 feet) to 60 minutes (at 30 feet). SACR's are typically between 10 and 20. Your `Dive` class should have a function named `getSACR` which returns the SACR for that dive.

To make life a little simpler putting the data in, we'll treat time as string of “HH:MM”, and use string functions to pick this apart into hours and minutes. We can save this as tuple of two intgers: hours and minutes. To compute the duration of a dive, we need to normalize our times to minutes past midnight, by doing `hh*60+mm`. Once we have our times in minutes past midnight, we can easily subtract to get the number of minutes of duration for the dive. You'll want to create a function `getDuration` to do just this computation for each dive.

`__init__`

The `__init__` method will initialize a `Dive` with the start and finish pressure in PSI, the in and out time as a string, and the depth as an integer. This method should parse both the `in` string and `out` string and normalize each to be minutes after midnight so that it can compute the duration of the dive. Note that a practical dive log would have additional information like the date, the location, the air and water temperature, sea state, equipment used and other comments on the dive.

`__str__`

The `__str__` method should return a nice string representation of the dive information.

`getSACR`

The `getSACR` method can then compute the SACR value from the starting pressure, final pressure, time and depth information.

The `Dive` Log. We'll want to initialize our dive log as follows:

```log = [
Dive( start=3100, finish=1300, in="11:52", out="12:45", depth=35 ),
Dive( start=2700, finish=1000, in="11:16", out="12:06", depth=40 ),
Dive( start=2800, finish=1200, in="11:26", out="12:06", depth=60 ),
Dive( start=2800, finish=1150, in="11:54", out="12:16", depth=95 ),
]
```

Rather than use a simple sequence of `Dive`s, you can create a `DiveLog` class which has a sequence of `Dive`s plus a `getAvgSACR` method. Your `DiveLog` method can be initiatlized with a sequence of dives, and can have an append method to put another dive into the sequence.

Exercising the Dive and DiveLog Classes. Here's how the final application could look. Note that we're using an arbitrary number of argument values to the `__init__` function, therefore, it has to be declared as `def __init__( self, *listOfDives )`

```log= DiveLog(
Dive( start=3100, finish=1300, in="11:52", out="12:45", depth=35 ),
Dive( start=2700, finish=1000, in="11:16", out="12:06", depth=40 ),
Dive( start=2800, finish=1200, in="11:26", out="12:06", depth=60 ),
Dive( start=2800, finish=1150, in="11:54", out="12:16", depth=95 ),
)
print log.getAvgSACR()
for d in log.dives:
print d```

Multi-Dice

If we want to simulate multi-dice games like Yacht, Kismet, Yatzee, Zilch, Zork, Greed or Ten Thousand, we'll need a collection that holds more than two dice. The most common configuration is a five-dice collection. In order to be flexible, we'll need to define a `Dice` object which will use a `tuple`, `list` or `Set` of individual `Die` instances. Since the number of dice in a game rarely varies, we can also use a `FrozenSet`.

Once you have a `Dice` class which can hold a collection of dice, you can gather some statistics on various multi-dice games. These games fall into two types. In both cases, the player's turn starts with rolling all the dice, the player can then elect to re-roll or preserve selected dice.

• Scorecard Games. In Yacht, Kismet and Yatzee, five dice are used. The first step in a player's turn is a roll of all five dice. This can be followed by up to two additional steps in which the player decides which dice to preserve and which dice to roll. The player is trying to make a scoring hand. A typical scorecard for these games lists a dozen or more "hands" with associated point values. The player attempts to make one of each of the various kinds of hands listed on the scorecard. Each turn must fill in one line of the scorecard; if the dice match a hand which has not been scored, the player enters a score. If a turn does not result in a hand that matches an unscored hand, then a score of zero is entered.

• Point Games. In Zilch, Zork, Green or Ten Thousand, five dice are typical, but there are some variations. The player in this game has no limit on the number of steps in their turn. The first step is to roll all the dice and determine a score. Their turn ends when they perceive the risk of another step to be too high, or they've made a roll which gives them a score of zero (or zilch) for the turn. Typically, if the newly rolled dice are non-scoring, their turn is over with a score of zero. At each step, the player is looking at newly rolled dice which improve their score. A straight scores 1000. Three-of-a-kind scores 100╳ the die's value (except three ones is 1000 points). After removing any three-of-a-kinds, each die showing 1 scores 100, each die showing 5 scores 50. Additionally, some folks will score 1000╳ the die's value for five-of-a-kind.

Our `MultiDice` class will be based on the example of `Dice` in this chapter. In addition to a collection of `Die` instances (a sequence, `Set` or `FrozenSet`), the class will have the following methods.

`__init__`

When initializing an instance of `MultiDice`, you'll create a collection of five individual `Die` instances. You can use a sequence of some kind, a `Set` or a `FrozenSet`.

`roll`

The `roll` method will roll all dice in the sequence or Set. Note that your choice of collection doesn't materially alter this method. That's a cool feature of Python.

`getDice`

This method returns the collection of dice so that a client class can examine them and potentialy re-roll some or all of the dice.

score

This method will score the hand, returning a `list` of two-tuples. Each two-tuple will have the name of the hand and the point value for the particular game. In some cases, there will be multiple ways to score a hand, and the `list` will reflect all possible scorings of the hand, in order from most valuable to least valuable. In other cases, the `list` will only have a single element.

It isn't practical to attempt to write a universal `MultiDice` class that covers all variations of dice games. Rather than write a gigantic does-everything class, the better policy is to create a family of classes that build on each other using inheritance. We'll look at this in the section called “Inheritance”. For this exercise, you'll have to pick one of the two families of games and compute the score for that particular game. Later, we'll see how to create an inheritance hierarchy that can cover the two-dice game of Craps as well as these multi-dice games.

For the scorecard games (Yacht, Kismet, Yatzee), we want to know if this set of dice matches any of the scorecard hands. In many cases, a set of dice can match a number of potential hands. A hand of all five dice showing the same value (e.g, a 6) is matches the sixes, three of a kind, four of a kind, five of a kind and wild-card rows on most game score-sheets. A sequence of five dice will match both a long straight and a short straight.

Common Scoring Methods. No matter which family of games you elect to pursue, you'll need some common method functions to help score a hand. The following methods will help to evaluate a set of dice to see which hand it might be.

• `matchDie`. This function will take a `Die` and a `Dice` set. It uses `matchValue` to partition the dice based on the value of the given `Die`.

• `matchValue`. This function is like `matchDie`, but it uses a numeric value instead of a `Die`. It partitions the dice into two sets: the dice in the `Dice` set which have a value that matches the given `Die`, and the remaining `Die` which do not match the value.

• ``` threeOfAKind ```, ``` fourOfAKind ```, ``` fiveOfAKind ```. These three functions will compute the `matchDie` for each `Die` in the `Dice` set. If any given `Die` has a `matchDie` with 3 (or 4 or 5) matching dice, the hand as a whole matches the template.

• ``` largeStraight ```. This function must establish that all five dice form a sequence of values from 1 to 5 or 2 to 6. There must be no gaps and no duplicated values.

• ``` smallStraight ```. This function must establish that four of the five dice form a sequence of values. There are a variety of ways of approaching this; it is actually a challenging algorithm. If we create a sequence of dice, and sort them into order, we're looking for an ascending sequence with one "irrelevant" die in it: this could be a gap before or after the sequence (1, 3, 4, 5, 6; 1, 2, 3, 4, 6 ) or a duplicated value (1, 2, 2, 3, 4, 5) within the sequence.

• ``` chance ```. The chance hand is simply the sum of the dice values. It is a number between 5 and 30.

This isn't necessarily the best way to do this. In many cases, a better way is to define a series of classes for the various kinds of hands, following the Strategy design pattern. The Dice would then have a collection of Strategy objects, each of which has a `match` method that compares the actual roll to a kind of hand. The Strategy objects would have a `score` method as well as a `name` method. This is something we'll look at in the section called “Strategy”.

Scoring Yacht, Kismet and Yatzee. For scoring these hands, you'll use the common scoring method functions. Your overall `score` method function will step through the candidate hands in a specific order. Generally, you'll want to check for `fiveOfAKind` first, since `fourOfAKind` and `threeOfAKind` will also be true for this hand. Similarly, you'll have to check for `largeStraight` before `smallStraight`.

Your `score` method will evaluate each of the scoring methods. If the method matches, your method will append a two-tuple with the name and points to the list of scores.

Scoring Zilch, Zork and 10,000. For scoring these dice throws, you'll need to expand on the basic `threeOfAKind` method. Your `score` method will make use of the two results sets created by the `threeOfAKind` method.

Note that the hand's description can be relatively complex. For example, you may have a hand with three 2's, a 1 and a 5. This is worth 350. The description has two parts: the three-of-a-kind and the extra 1's and 5's. Here are the steps for scoring this game.

• Evaluate the `largeStraight` method. If the hand matches, then return a `list` with an appropriate 2-tuple.

• If you're building a game variation where five of a kind is a scoring hand, then evaluate `fiveOfAKind`. If the hand matches, then return a list with an appropriate 2-tuple.

• 3K. Evaluate the `threeOfAKind` method. This will create the first part of the hand's description.

• If a `Die` created a matching set with exactly three dice, then the set of unmatched dice must be examined for additional 1's and 5's. The first part of the hand's description string is three-of-a-kind.

• If a `Die` created a matching with four or five dice, then one or two dice must be popped from the matching set and added to the non-matching set. The set of unmatched dice must be examined for addtional 1's and 5's. The first part of the hand's description string is three-of-a-kind.

• If there was no set of three matching dice, then all the dice are in the non-matching set, which is checked for 1's and 5's. The string which describes the hand has no first part, since there was no three-of-a-kind.

• 1-5's. Any non-matching dice from the `threeOfAKind` test are then checked using `matchValue` to see if there are 1's or 5's. If there are any, this is the second part of the hand's description. If there are none, then there's no second part of the description.

• The final step is to assemble the description. There are four cases: nothing, 3K with no 1-5's, 1-5's with no 3K, and 3K plus 1-5's. In the nothing case, this is a non-scoring hand. In the other cases, it is a scoring hand.

Exercising The Dice. Your main script should create a `Dice` set, execute an initial roll and score the result. It should then pick three dice to re-roll and score the result. Finally, it should pick one die, re-roll this die and score the result. This doesn't make sophisticated strategic decisions, but it does exercise your `Dice` and `Die` objects thoroughly.

When playing a scorecard game, the list of potential hands is examined to fill in another line on the scorecard. When playing a points game, each throw must result in a higher score than the previous throw or the turn is over.

Strategy. When playing these games, a person will be able to glance at the dice, form a pattern, and decide if the dice are "close" to one of the given hands. This is a challenging judgement, and requires some fairly sophisticated software to make a proper odd-based judgement of possible outcomes. Given that there are only 7,776 possible ways to roll 5 dice, it's a matter of exhaustively enumerating all of the potential outcomes of the various kinds of rerolls. This is an interesting, but quite advanced exercise.

Rational Numbers

A Rational number is a ratio of two integers. Examples include 1/2, 2/3, 22/7, 355/113, etc. We can do arithmetic operations on rational numbers. We can display them as proper fractions (```3 1/7```), improper fractions (`22/7`) or decimal expansions (`3.1428571428571428`).

The essence of this class is to perform arithmetic operations. We'll start by defining methods to add and multiply two rational values. Later, we'll delve into the additional methods you'd need to write to create a robust, complete implementation.

Your `add` and ``` mul ``` methods will perform their processing with two `Rational` values: `self` and `other`. In both cases, the variable `other` has to be another `Rational` number instance. You can check this by using the `type` function: if ```type(self) != type(other)```, you should raise a `TypeException`.

It's also important to note that all arithmetic operations will create a new `Rational` number computed from the inputs.

A Rational class has two attributes: the numerator and the denominator of the value. These are both integers. Here are the various methods you should created.

`__init__`

The `__init__` constructor accepts the numerator and denominator values. It can have a default value for the denominator of 1. This gives us two constructors: `Rational(2,3)` and `Rational(4)`. The first creates the fraction 2/3. The second creates the fraction 4/1.

This method should call the `reduce` method to assure that the fraction is properly reduced. For example, `Rational(8,4)` should automatically reduce to a numerator of 2 and a denominator of 1.

`__str__`

The `__str__` method returns a nice string representation for the rational number, typically as an improper fraction. This gives you the most direct view of your `Rational` number.

You should provide a separate method to provide a proper fraction string with a whole number and a fraction. This other method would do additional processing to extract a whole name and remainder.

`__float__`

If you provide a method named `__float__`, this can return the floating-point value for the fraction. This method is called when a program does ```float( rationalValue )```.

``` add ```( `self` , `other` )

The `add` method creates and returns a new `Rational` number. This new fraction that has a numerator of (self.numerator × other.denominator + other.numerator × self.denominator), and a denominator of ( self.denominator × other.denominator ).

Example: 3/5 + 7/11 = (33 + 35)/55 = 71/55.

``` mul ```( `self` , `other` )

The `mul` method creates and returns a new `Rational` number. This new fraction that has a numerator of (self.numerator × other.numerator), and a denominator of ( self.denominator × other.denominator ).

Equation 21.2. Multiplying Fractions

Example: 3/5 × 7/11 = 21/55.

`reduce`

In addition to adding and multiplying two `Rational` numbers, you'll also need to provide a `reduce` method which will reduce a fraction by removing the greatest common divisor from the numerator and the denominator. This should be called by `__init__` to assure that all fractions are reduced.

To implement `reduce`, we find the greatest common divisor between the numerator and denominator and then divide both by this divisor. For example 8/4 has a GCD of 4, and reduces to 2/1. The Greatest Common Divisor (GCD) algorithm is given in Greatest Common Divisor and Greatest Common Divisor. If the GCD of `self.numerator` and `self.denominator` is 1, the `reduced` function can return `self`. Otherwise, `reduced` must create a new `Rational` with the reduced fraction.

Playing Cards and Decks

Standard playing cards have a rank (ace, two through ten, jack, queen and king) and suit (clubs, diamonds, hearts, spades). These form a nifty `Card` object with two simple attributes. We can add a few generally useful functions.

Here are the methods for your `Card` class.

``` __init__ ```( `self` , `rank` )

The `__init__` method sets the rank and suit of the card. The suits can be coded with a single character ("C", "D", "H", "S"), and the ranks can be coded with a number from 1 to 13. The number 1 is an ace. The numbers 11, 12, 13 are Jack, Queen and King, respectively. These are the ranks, not the point values.

`__str__`

The `__str__` method can return the rank and suit in the form "2C" or "AS" or "JD". A rank of 1 would become "A", a rank of 11, 12 or 13 would become "J", "Q" or "K", respectively.

``` __cmp__ ```( `self` , `other` )

If you define a `__cmp__` method, this will used by the `cmp` function; the `cmp` function is used by the `sort` method of a `list` unless you provide an overriding function used for comparison. By providing a `__cmp__` method in your class you can assure that cards are sorted by rank in preference to suit. You can also use `<`, `>`, `>=` and `<=` operations among cards.

Sometime as simple as ```cmp(self.rank,other.rank) or cmp(self.suit,other.suit)``` works surprisingly well.

Dealing and Decks.  `Card`s are dealt from a `Deck`; a collection of `Card`s that includes some methods for shuffling and dealing. Here are the methods that comprise a `Deck`.

`__init__`

The `__init__` method creates all 52 cards. It can use two loops to iterate through the sequence of suits `("C", "D", "H", "S")` and iterate through the ranks `range(1,14)`. After creating each `Card`, it can append each `Card` to a sequence of `Card`s.

`deal`

The `deal` method should do two things: iterate through the sequence, exchanging each card with a randomly selected card. It turns out the `random` module has a `shuffle` function which does precisely this.

Dealing is best done with a generator method function. The `deal` method function should have a simple for-loop that yields each individual `Card`; this can be used by a client application to generate hands. The presence of the yield statement will make this method function usable by a for statement in a client application script.

Basic Testing. You should do some basic tests of your Card objects to be sure that they respond appropriately to comparison operations. For example,

````>>>`
`x1= Card(11,"C")`

`>>>`
`x1`

` JC`
`>>>`
`x2= Card(12,"D")`

`>>>`
`x1 < x2`

`True`
```

You can write a simple test script which can the do the following to deal `Cards` from a `Deck`. In this example, the variable `dealer` will be the iterator object that the for statement uses internally.

```d= Deck()
dealer= d.deal()
c1= dealer.next()
c2= dealer.next()```

Hands. Many card games involve collecting a hand of cards. A Hand is a collection of `Card`s plus some addition methods to score the hand in way that's appropriate to the given game. We have a number of collection classes that we can use: `list`, `tuple`, `dictionary` and `set`.

Consider Blackjack. The `Hand` will have two `Card`s assigned initially; it will be scored. Then the player must choose among accepting another card (a hit), using this hand against the dealer (standing), doubling the bet and taking one more card, or splitting the hand into two hands. Ignoring the split option for now, it's clear that the collection of `Card`s has to grow and then get scored again. What are the pros and cons of `list`, `tuple`, `set` and `dictionary`?

Consider Poker. There are innumerable variations on poker; we'll look at simple five-card draw poker. Games like seven-card stud require you to score potential hands given only two cards, and as many as 21 alternative five-card hands made from seven cards. Texas Hold-Em has from three to five common cards plus two private cards, making the scoring rather complex. For five-card draw, the `Hand` will have five cards assigned initially, and it will be scored. Then some cards can be removed and replaced, and the hand scored again. Since a valid poker hand is an ascending sequence of cards, called a straight, it is handy to sort the collection of cards. What are the pros and cons of `list`, `tuple`, `set` and `dictionary`?

Blackjack Hands

Changes to the Card class. We'll extend our `Card` class to score hands in Blackjack, where the rank is used to determine the hand that is held. When used in Blackjack, a `Card` has a point value in addition to a rank and suit. Aces are either 1 or 11; two through ten are worth 2-10; the face cards are all worth 10 points. When an ace is counted as 1 point, the total is called the hard total. When an ace is counted as 11 points, the total is called a soft total.

You can add a point attribute to your card class. This can be set as part of `__init__` processing. In that case, the following methods simple return the point value.

As an alternative, you can compute the point value each time it is requested. This has the obvious disadvantage of being slower. However, it is considerably simpler to add methods to a class without revising the existing `__init__ `method.

Here are the methods you'll need to add to your `Card` class in order to handle Blackjack hands.

`getHardValue`

The `getHardValue` method returns the rank, with the following exceptions: ranks of 11, 12 and 13 return a point value of 10.

`getSoftValue`

The `getSoftValue` method returns the rank, with the following exceptions: ranks of 11, 12 and 13 return a point value of 10; a rank of 1 returns a point value of 11.

As a teaser for the next chapter, we'll note that these methods should be part of a Blackjack-specific subclass of the generic `Card` class. For now, however, we'll just update the `Card` class definition.When we look at inheritance in the section called “Inheritance”, we'll see that a class hierarchy can be simpler than the if-statements in the `getHardValue` and `getSoftValue` methods.

Scoring Blackjack Hands. The objective of Blackjack is to accumulate a `Hand` with a total point value that is less than or equal to 21. Since an ace can count as 1 or 11, it's clear that only one of the aces in a hand can have a value of 11, and any other aces must have a value of 1.

Each `Card` produces a hard and soft point total. The `Hand` as a whole also has hard and soft point totals. Often, both hard and soft total are equal. When there is an ace, however, the hard and soft totals for the hand will be different. We have to look at two cases.

• No Aces. The hard and soft total of the hand will be the same; it's the total of the hard value of each card. If the hard total is less than 21 the hand is in play. If it is equal to 21, it is a potential winner. If it is over 21, the hand has gone bust. Both totals will be computed as the hard value of all cards.

• One or more Aces. The hard and soft total of the hand are different. The hard total for the hand is the sum of the hard point values of all cards. The soft total for the hand is the soft value of one ace plus the hard total of the rest of the cards. If the hard or soft total is 21, the hand is a potential winner. If the hard total is less than 21 the hand is in play. If the hard total is over 21, the hand has gone bust.

The `Hand` class has a collection of `Cards`, usually a sequence, but a `Set` will also work. Here are the methods of the `Hand` class.

`__init__`

The `__init__` method should be given two instances of `Card` to represent the initial deal. It should create a sequence or `Set` with these two initial cards.

`__str__`

The `__str__` method a string with all of the individual cards. A construct like the following works out well: ```",".join( map(str,self.cards)```. This gets the string representation of each card in the `self.cards` collection, and then uses the `string`'s `join` method to assemble the final display of cards.

`hardTotal`

The `hardTotal` method sums the hard value of each `Card`.

`softTotal`

The `softTotal` method is more complex. It needs to partition the cards into two sets. If there are any cards with a different hard and soft point value (this will be an ace), then one of these cards forms the `softSet`. The remaining cards form the `hardSet`. It's entirely possible that the `softSet` will be empty. It's also entirely possible that there are multiple cards which could be part of the `softSet`. The value of this function is the total of the hard values for all of the cards in the `hardSet` plus the soft value of the card in the `softSet`.

`add`

The `add` method will add another `Card` to the `Hand`.

Exercising Card, Deck and Hand. Once you have the `Card`, `Deck` and `Hand` classes, you can exercise these with a simple function to play one hand of blackjack. This program will create a `Deck` and a `Hand`; it will deal two `Card`s into the `Hand`. While the `Hand`'s total is soft 16 or less, it will add `Card`s. Finally, it will print the resulting `Hand`.

There are two sets of rules for how to fill a `Hand`. The dealer is tightly constrained, but players are more free to make their own decisions. Note that the player's hands which go bust are settled immediately, irrespective of what happens to the dealer. On the other hand, the player's hands which total 21 aren't resolved until the dealer finishes taking cards.

The dealer must add cards to a hand with a soft 16 or less. If the dealer has a soft total between 17 and 21, they stop. If the dealer has a soft total which is over 21, but a hard total of 16 or less, they will take cards. If the dealer has a hard total of 17 or more, they will stop.

A player may add cards freely until their hard total is 21 or more. Typically, a player will stop at a soft 21; other than that, almost anything is possible.

Additional Plays. We've avoided discussing the options to split a hand or double the bet. These are more advanced topics that don't have much bearing on the basics of defining `Card`, `Deck` and `Hand`. Splitting simply creates additional `Hand`s. Doubling down changes the bet and gets just one additional card.

Poker Hands

We'll extend the `Card` class we created in the section called “Playing Cards and Decks” to score hands in Poker, where both the rank and suit are used to determine the hand that is held.

Poker hands are ranked in the following order, from most desirable (and least likely) down to least desirable (and all too common).

1. Straight Flush. Five cards of adjacent ranks, all of the same suit.

2. Four of a Kind. Four cards of the same rank, plus another card.

3. Full House. Three cards of the same rank, plus two cards of the same rank.

4. Flush. Five cards of the same suit.

5. Straight. Five cards of adjacent ranks. In this case, Ace can be above King or below 2.

6. Three of a Kind. Three cards of the same rank, plus two cards of other ranks.

7. Two Pair. Two cards of one rank, plus two cards of another rank, plus one card of a third rank.

8. Pair. Two cards of one rank, plus three cards of other ranks.

9. High Card. The highest ranking card in the hand.

Note that a straight flush is both a straight and a flush; four of a kind is also two pair as well as one pair; a full house is also two pair, as well as a one pair. It is important, then, to evaluate poker hands in decreasing order of importance in order to find the best hand possible.

In order to distinguish between two straights or two full-houses, it is important to also record the highest scoring card. A straight with a high card of a Queen, beats a straight with a high card of a 10. Similarly, a full house or two pair is described as “queens over threes”, meaning there are three queens and two threes comprising the hand. We'll need a numeric ranking that includes the hand's rank from 9 down to 1, plus the cards in order of “importance” to the scoring of the hand.

The importance of a card depends on the hand. For a straight or straight flush, the most important card is the highest-ranking card. For a full house, the most important cards are the three-of-a kind cards, followed by the pair of cards. For two pair, however, the most important cards are the high-ranking pair, followed by the low-ranking pair. This allows us to compare “two pair 10's and 4's” against “two pair 10's and 9s'”. Both hands have a pair of 10's, meaning we need to look at the third card in order of importance to determine the winner.

Scoring Poker Hands. The `Hand` class should look like the following. This definition provides a number of methods to check for straight, flush and the patterns of matching cards. These functions are used by the `score` method, shown below.

```class PokerHand:
def __init__( self, cards ):
self.cards= cards
self.rankCount= {}
def straight( self ):

all in sequence

def straight( self ):

all of one suit

def matches( self ):

tuple with counts of each rank in the hand

def sortByRank( self ):

sort into rank order

def sortByMatch( self ):

sort into order by count of each rank, then rank

```

This function to score a hand checks each of the poker hand rules in descending order.

```    def score( self ):
if self.straight() and self.flush():
self.sortByRank()
return 9
elif self.matches() == ( 4, 1 ):
self.sortByMatch()
return 8
elif self.matches() == ( 3, 2 ):
self.sortByMatch()
return 7
elif self.flush():
self.sortByRank()
return 6
elif self.straight():
self.sortByRank()
return 5
elif self.matches() == ( 3, 1, 1 ):
self.sortByMatch()
return 4
elif self.matches() == ( 2, 2, 1 ):
self.sortByMatchAndRank()
return 3
elif self.matches() == ( 2, 1, 1, 1 ):
self.sortByMatch()
return 2
else:
self.sortByRank()
return 1
```

You'll need to add the following methods to the PokerHand class.

• `straight` returns True if the cards form a straight. This can be tackled easily by sorting the cards into descending order by rank and then checking to see if the ranks all differ by exactly one.

• `flush` returns True if all cards have the same suit.

• `matches` returns a tuple of the counts of cards grouped by rank. This can be done iterating through each card, using the card's rank as a key to the `self.rankCount` dictionary; the value for that dictionary entry is the count of the number of times that rank has been seen. The values of the dictionary can be sorted, and form six distinct patterns, five of which are shown above. The sixth is simply `(1, 1, 1, 1, 1)`, which means no two cards had the same rank.

• `sortByRank` sorts the cards by rank.

• `sortByMatch` uses the counts in the `self.rankCount` dictionary to update each card with its match count, and then sorts the cards by match count.

• `sortByMatchAndRank` uses the counts in the `self.rankCount` dictionary to update each card with its match count, and then sorts the cards by match count and rank as two separate keys.

Exercising Card, Deck and Hand. Once you have the `Card`, `Deck` and `Hand` classes, you can exercise these with a simple function to play one hand of poker. This program will create a `Deck` and a `Hand`; it will deal five `Card`s into the `Hand`. It can score the hand. It can replace from zero to three cards and score the resulting hand.

 Published under the terms of the Open Publication License Design by Interspire