"""
This program explores the non-randomness of the traditional 'faro'
method of card shuffling.  No matter how many cards there are in a
deck, there is always a finite number of faro shuffles that will
restore their original order.  (Of course, a faro shuffle is very hard
to do in practice!)
"""
def faroShuffle(oldDeck):
    """
    performs a 'faro' or 'perfect' shuffle

    `oldDeck` (a sequence) is the original deck.  The shuffled deck is
    returned.  It does an 'out' shuffle (top and bottom cards
    preserved).
 
    >>> # To shuffle two cards...
    >>> faroShuffle([0, 1])
    [0, 1]

    >>> # Four cards...
    >>> faroShuffle([0, 1, 2, 3])
    [0, 2, 1, 3]

    >>> # A deck (range) of 10 cards...
    >>> faroShuffle(range(10))
    [0, 5, 1, 6, 2, 7, 3, 8, 4, 9]

    >>> # Suppose we try to shuffle a list that includes non-integers...
    >>> faroShuffle(['queen', 'jack', 'ace', 7])
    ['queen', 'ace', 'jack', 7]

    >>> # Three cards, being an odd number, should raise an exception...
    >>> faroShuffle([0, 1, 2])
    Traceback (most recent call last):
    ...
    ValueError: attempt to assign sequence of size 2 to extended slice of size 1

    >>> # How about something that's not a sequence?
    >>> faroShuffle(1)
    Traceback (most recent call last):
    ...
    TypeError: object of type 'int' has no len()

    >>> # An empty list should be returned unchanged.
    >>> faroShuffle([])
    []

    >>> # How about an empty argument list?
    >>> faroShuffle()
    Traceback (most recent call last):
       ...
    TypeError: faroShuffle() takes exactly 1 argument (0 given)
    """
    # Look how easy Python makes this!
    n = len(oldDeck)
    newDeck = n*[ 0 ] # initializes result
    newDeck[0:n-1:2] = oldDeck[:n//2] # insert left hand
    newDeck[1:n:2]   = oldDeck[n//2:] # insert right hand
    return newDeck


def countShuffles(nCards):
    """
    counts the number of faro shuffles required to restore a deck

    `nCards` is the number of cards in the deck.  It must be an even
    positive int.
    """
    originalDeck = list(range(nCards))
    currentDeck = originalDeck
    shuffleCount = 0
    while True:
        shuffledDeck = faroShuffle(currentDeck)
        shuffleCount += 1
        if shuffledDeck == originalDeck:
            return shuffleCount
        currentDeck = shuffledDeck


def main():
    nCards = 52
    print(('The ordering of a deck of %d cards is'
           ' restored after %d faro shuffles.'
           % (nCards, countShuffles(nCards))))


if __name__ == '__main__':
    import doctest
    doctest.testmod()
