Making a frozen hand from a mutable hand

If we want to perform a statistical analysis of specific Hand instances, we might want to create a dictionary that maps a Hand instance to a count. We can't use a mutable Hand class as the key in a mapping. We can, however, parallel the design of set and frozenset and create two classes: Hand and FrozenHand. This allows us to freeze a Hand instance by creating FrozenHand; the frozen version is immutable and can be used as a key in a dictionary. 

The following is a simple Hand definition:

class Hand:

def __init__(self, dealer_card: Card2, *cards: Card2) -> None:
self.dealer_card = dealer_card
self.cards = list(cards)

def __str__(self) -> str:
return ", ".join(map(str, self.cards))

def __repr__(self) -> str:
cards_text = ", ".join(map(repr, self.cards))
return f"{self.__class__.__name__}({self.dealer_card!r}, {cards_text})"

def __format__(self, spec: str) -> str:
if spec == "":
return str(self)
return ", ".join(f"{c:{spec}}" for c in self.cards)

def __eq__(self, other: Any) -> bool:
if isinstance(other, int):
return self.total() == cast(int, other)
try:
return (
self.cards == cast(Hand, other).cards
and self.dealer_card == cast(Hand, other).dealer_card
)
except AttributeError:
return NotImplemented

This is a mutable object; it does not compute a hash value, and can't be used in a set or dictionary key. It does have a proper equality test that compares two hands. As with previous examples, the parameter to the __eq__() method has a type hint of Any, and a do-nothing cast() function is used to tell the mypy program that the argument values will always be instances of Hand. The following is a frozen version of Hand:

import sys

class FrozenHand(Hand):

def __init__(self, *args, **kw) -> None:
if len(args) == 1 and isinstance(args[0], Hand):
# Clone a hand
other = cast(Hand, args[0])
self.dealer_card = other.dealer_card
self.cards = other.cards
else:
# Build a fresh Hand from Card instances.
super().__init__(*args, **kw)

def __hash__(self) -> int:
return sum(hash(c) for c in self.cards) % sys.hash_info.modulus

The frozen version has a constructor that will build one Hand class from another Hand class. It defines a __hash__() method that sums the card's hash value, which is limited to the sys.hash_info.modulus value. For the most part, this kind of modulus-based calculation works out well for computing the hashes of composite objects. We can now use these classes for operations such as the following code snippet:

from collections import defaultdict
stats = defaultdict(int) d = Deck() h = Hand(d.pop(), d.pop(), d.pop()) h_f = FrozenHand(h) stats[h_f] += 1

We've initialized a statistics dictionary, stats, as a defaultdict dictionary that can collect integer counts. We could also use a collections.Counter object for this.

By freezing an instance of the Hand class, we can compute a hash and use it as a key in a dictionary. This makes it easy to create a defaultdict for collecting counts of each hand that actually gets dealt.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset