"""
More objected oriented programming
http://openbookproject.net/thinkcs/python/english2e/ch16.html
"""


import random

class Card :
    ranks = ["2", "3", "4" , "5", "6", "7", "8", "9", "10",
             "Jack", "Queen", "King", "Ace"]
    suits = ["Clubs", "Diamonds", "Hearts", "Spades"]
    def __init__(self, rank, suit) :
        if rank not in Card.ranks :
            raise ValueError, "bad rank"
        if suit not in Card.suits :
            raise ValueError, "bad suit"
        self.suit = Card.suits.index(suit)
        self.rank = Card.ranks.index(rank)
    def __repr__(self):
        return (Card.ranks[self.rank] + " of " + Card.suits[self.suit])
    def __cmp__(self, other):
        """Compare cards on the basis of their rank"""
        if self.rank > other.rank: return 1
        if self.rank < other.rank: return -1
        return 0

c1 = Card("Jack", "Spades")
c2 = Card("King", "Hearts")
c1 < c2

# Observations about the class:
# The arrays "ranks" and "suits" are not instance attributes - they are class attributes,
# i.e. variables that are associated with the class itself.  You can access them as
# self.ranks and self.suits, but also as Card.ranks and Card.suits which more explicitly
# shows that they are class attributes.  All instances of the class share the same
# instances of those variables.  Therefore, if you modify class variables, that affects
# all instances of the class.

# The __cmp__ method defines the behavior of the comparison operators == != < > <= >=

class Deck :
    def __init__(self) :
        self.cards = []
        for suit in Card.suits :
            for rank in Card.ranks :
                self.cards.append(Card(rank, suit))
    def shuffle(self) :
        random.shuffle(self.cards)
    def __len__(self) :
        return len(self.cards)
    def __repr__(self):
        s = [str(card) for card in self.cards]
        return "\n".join(s)
    def __getitem__(self, key) :
        return self.cards.__getitem__(key)
    def remove(self, card):
        if card in self.cards:
            self.cards.remove(card)
            return True
        else:
            return False
    def deal_card(self) :
        return self.cards.pop()
    def is_empty(self) :
        return len(self) == 0

# Notes on the Deck class:
# __len__ is the function that gets called when the len function is called.
# Note the use of remove and pop in the remove and deal_card methods.

# Documentation of the set of all special methods (___xxx___) that are used to customize
# the behavior of a class are found at the python language reference manual at:
# http://docs.python.org/reference/datamodel.html#basic-customization


# Comments on list comprehension

# A common task in python is creating lists.
# For example if we wanted to created a list of all the squares of the elements
# in a given list we would do:
a = range(10)
squared = []
for element in a :
    squared.append(a**2)

# Python has a concise way of doing this using a construct called list comprehension.
# List comprehension provides a compact way of mapping a list into another list by
# applying a function to each element of the list.
# Examples:

fruit_list = ['  banana', '  strawberry ', 'passion fruit  ']
print [fruit.strip() for fruit in fruit_list]

vec = [2, 4, 6]
print [3*x for x in vec]
print [3*x for x in vec if x > 3]
print [(x, x**2) for x in vec]

li = ["a", "asa", "foo", "b", "c", "b", "d", "d"]
print [elem for elem in li if len(elem) > 1]