Making a frozen hand from a mutable hand
If we want to perform 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
class via 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, *cards ): self.dealer_card= dealer_card self.cards= list(cards) def __str__( self ): return ", ".join( map(str, self.cards) ) def __repr__( self ): return "{__class__.__name__}({dealer_card!r}, {_cards_str})".format( __class__=self.__class__, _cards_str=", ".join( map(repr, self.cards) ), **self.__dict__ ) def __eq__( self, other ): return self.cards == other.cards and self.dealer_card == other.dealer_card __hash__ = None
This is a mutable object (__hash__
is None
) that has a proper equality test that compares two hands.
The following is a frozen version of Hand
:
import sys class FrozenHand( Hand ): def __init__( self, *args, **kw ): if len(args) == 1 and isinstance(args[0], Hand): # Clone a hand other= args[0] self.dealer_card= other.dealer_card self.cards= other.cards else: # Build a fresh hand super().__init__( *args, **kw ) def __hash__( self ): h= 0 for c in self.cards: h = (h + hash(c)) % sys.hash_info.modulus return h
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 that is limited to the sys.hash_info.modulus
value. For the most part, this kind of modulus-based calculation works out reasonably well for computing hashes of composite objects.
We can now use these classes for operations such as the following code snippet:
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 a Hand
class, we can use it as a key in a dictionary, collecting counts of each hand that actually gets dealt.