[Edu-sig] Cards n stuff...

Seth David Schoen schoen@loyalty.org
Tue, 13 Mar 2001 12:08:21 -0800


Kirby Urner writes:

> Hi Jeff --
> 
> Here's a slightly modified version of your code, including
> some ideas about shuffling:
> 
> import random
> 
> class Card:
>   
>   def __init__(self, suite, rank):
>     self.suite = suite
>     self.rank = rank
> 
>   def __repr__(self):
>     return self.rank + ' of ' + self.suite
> 
> class Deck:
> 
>   suites = ['Hearts','Diamonds','Spades','Clubs']
>   ranks  =  ['Ace']+map(str,range(2,11))+['Jack','Queen','King']
>     
>   def __init__(self):
>     self.cards = []
>     for suite in Deck.suites:
>       for rank in Deck.ranks:
>         newCard = Card(suite, rank)
>         self.cards.append(newCard)
> 
>   def shuffle(self):
>       newdeck = []
>       while len(newdeck)<52:
>           randomcard = random.choice(self.cards)
>           newdeck.append(randomcard)
>           self.cards.remove(randomcard)
>       self.cards = newdeck
>       
> 
> 
> Note:  I manually truncated/edited the list of cards to save
> space.
> 
> >>> mydeck = cards.Deck()
> >>> mydeck.cards
> [Ace of Hearts, 2 of Hearts, 3 of Hearts, 4 of Hearts, 5 of Hearts, 
> 6 of Hearts, 7 of Hearts, 8 of Hearts, 9 of Hearts, 10 of Hearts, 
> Jack of Hearts, Queen of Hearts, King of Hearts, Ace of Diamonds...]
> 
> >>> mydeck.shuffle()
> >>> mydeck.cards
> [Ace of Spades, 9 of Hearts, 7 of Spades, Queen of Diamonds, 3 of Hearts, 
> Jack of Spades, 10 of Clubs, 4 of Spades, 10 of Hearts, Queen of Spades, 
> 7 of Diamonds, Ace of Hearts, Queen of Hearts, Queen of Clubs, 6 of Spades, 
> King of Hearts...]
> >>> len(mydeck.cards)
> 52

An interesting question is whether methods of a Deck which modify the
order of the deck should return None (and modify that Deck object) or
return a new Deck to which the change has been applied.  In general,
I think the latter is better.

But there is an argument that the programmer who wants to keep an old
Deck object should assume the responsibility of making a copy.

Here's your code with methods added to do in-shuffles and out-shuffles
on an existing deck:

import random

class Card:
  
  def __init__(self, suite, rank):
    self.suite = suite
    self.rank = rank

  def __repr__(self):
    return self.rank + ' of ' + self.suite

class Deck:

  suites = ['Hearts','Diamonds','Spades','Clubs']
  ranks  =  ['Ace']+map(str,range(2,11))+['Jack','Queen','King']
    
  def __init__(self):
    self.cards = []
    for suite in Deck.suites:
      for rank in Deck.ranks:
        newCard = Card(suite, rank)
        self.cards.append(newCard)

  def shuffle(self):
      newdeck = []
      while len(newdeck)<52:
          randomcard = random.choice(self.cards)
          newdeck.append(randomcard)
          self.cards.remove(randomcard)
      self.cards = newdeck

  def interleave(self, a, b):
      c = []
      while b and a:
          c.append(b.pop())
          c.append(a.pop())
      if b:
          c.append(b.pop())
      if a:
          c.append(a.pop())
      c.reverse()
      return c

  def out_shuffle(self):
      L = len(self.cards)
      self.cards = self.interleave(self.cards[:L/2], self.cards[L/2:])

  def in_shuffle(self):
      L = len(self.cards)
      self.cards = self.interleave(self.cards[L/2:], self.cards[:L/2])

(I'm not actually sure this works properly if there are an odd number
of cards in the deck.)

For fun, you can then try

d = Deck()
for i in range(52):
	d.in_shuffle()
	print d.cards


d = Deck()
for i in range(8):
	d.out_shuffle()
	print d.cards


I would really very much like to have a function that simulates a
typical human shuffle -- I and most of the people I know can't do
anything like perfect faro shuffles -- but it's hard to think of a
good model.  We know that most people make an imperfect division of the
deck which is _approximately_ event, and then let fall a certain number
of cards at a time (a "run") from one half, followed by a run from the
other side, and so on back and forth until one side runs out.  The
biggest question is the lengths of the runs.  They aren't all the
same; a more experienced shuffler will tend to have shorter average
runs.  How are run lengths distributed (for a given shuffler), and how
are they correlated to previous run length or to distance into the
deck?

Are the runs from one half of the deck (say, the half held in the
shuffler's non-dominant hand) typically longer than the runs from the
other half?

You can get dealing easily by

   def deal(self):
       if self.cards:
          card = self.cards[0]
          self.cards.remove(card)
          return card
       else:
          return None

-- 
Seth David Schoen <schoen@loyalty.org>  | And do not say, I will study when I
Temp.  http://www.loyalty.org/~schoen/  | have leisure; for perhaps you will
down:  http://www.loyalty.org/   (CAF)  | not have leisure.  -- Pirke Avot 2:5