<jupyter_start><jupyter_text>Exercise 3  Write a `Deck` method called `deal_hands` that takes two parameters, the number of hands and the number of cards per hand, and that creates new `Hand` objects, deals the appropriate number of cards per hand, and returns a list of `Hand` objects.<jupyter_code>def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        res = []
        for i in range(num_hands):
            new_hand = Hand('hand ' + str(i))
            self.move_cards(new_hand, num_cards)
            res.append(new_hand)
        return res   # TODO: Implement (Exercise 3)
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        hands = []
        for i in range(num_hands):
            # create a hand and give it a name
            hand_name = "Hand number" + str(i)
            hand = Hand(hand_name)
            print 
            print "Created a hand called", hand_name
            
            for i in range(num_cards):
                # pops cards out of deack and adds them to the hand
                hand.add_card(self.pop_card())
                
            hands.append(hand)
            print "This hand has cards:"
            print hand
                
        return hands   # TODO: Implement (Exercise 3)
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        self.num_hands = num_hands
        self.num_cards = num_cards
        
        hands_list = []
        for i in range(num_hands):
            new_hand = Hand('new Hand')
            self.move_cards(new_hand, num_cards)
            hands_list.append(new_hand)
        return hands_list
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        hands = [Hand("Hand Number {}".format(i)) for i in range(num_hands)]
        for k in range(num_cards):
            for hand in hands:
                self.move_cards(hand, 1)
        return hands
"""This module contains code from
Think Python by <NAME>
http://thinkpython.com

Copyright 2012 <NAME>
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html

"""

import random


class Card(object):
    """Represents a standard playing card.
    
    Attributes:
      suit: integer 0-3
      rank: integer 1-13
    """

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]

    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        """Returns a human-readable string representation."""
        return "{0} of {1}".format(Card.rank_names[self.rank],
                                   Card.suit_names[self.suit])

    def __cmp__(self, other):
        """Compares this card to other, first by suit, then rank.

        Returns a positive number if this > other; negative if other > this;
        and 0 if they are equivalent.
        """
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return cmp(t1, t2)


class Deck(object):
    """Represents a deck of cards.

    Attributes:
      cards: list of Card objects.
    """
    
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(1, 14):
                card = Card(suit, rank)
                self.cards.append(card)

    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)

    def add_card(self, card):
        """Adds a card to the deck."""
        self.cards.append(card)

    def remove_card(self, card):
        """Removes a card from the deck."""
        self.cards.remove(card)

    def pop_card(self, i=-1):
        """Removes and returns a card from the deck.

        i: index of the card to pop; by default, pops the last card.
        """
        return self.cards.pop(i)

    def shuffle(self):
        """Shuffles the cards in this deck."""
        random.shuffle(self.cards)

    def sort(self):
        """Sorts the cards in ascending order."""
        self.cards.sort()

    def move_cards(self, hand, num):
        """Moves the given number of cards from the deck into the Hand.

        hand: destination Hand object
        num: integer number of cards to move
        """
        for i in range(num):
            hand.add_card(self.pop_card())
    
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        hands = []
        for i in range(num_hands):
            hhh = Hand()
            self.move_cards(hhh, num_cards)
            hands.append(hhh)
        return hands


class Hand(Deck):
    """Represents a hand of playing cards."""
    
    def __init__(self, label=''):
        self.cards = []
        self.label = label


def find_defining_class(obj, method_name):
    """Finds and returns the class object that will provide 
    the definition of method_name (as a string) if it is
    invoked on obj.

    obj: any python object
    method_name: string method name
    """
    for ty in type(obj).mro():
        if method_name in ty.__dict__:
            return ty
    return None


if __name__ == '__main__':
    # Example of using find_defining_class to probe object inheritance
    test_hand = Hand()
    print "'shuffle' is a method of", find_defining_class(test_hand, 'shuffle')

    # TODO: The following test code won't fully work until you define the deal_hands Deck method
    deck = Deck()
    deck.shuffle()
    hands = deck.deal_hands(4, 5)
    for i, hand in enumerate(hands):
        print "Hand {}:".format(i)
        hand.sort()
        print hand
"""This module contains code from
Think Python by <NAME>
http://thinkpython.com

Copyright 2012 <NAME>
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html

"""

import random


class Card(object):
    """Represents a standard playing card.
    
    Attributes:
      suit: integer 0-3
      rank: integer 1-13
    """

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]

    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        """Returns a human-readable string representation."""
        return "{0} of {1}".format(Card.rank_names[self.rank],
                                   Card.suit_names[self.suit])

    def __cmp__(self, other):
        """Compares this card to other, first by suit, then rank.

        Returns a positive number if this > other; negative if other > this;
        and 0 if they are equivalent.
        """
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return cmp(t1, t2)


class Deck(object):
    """Represents a deck of cards.

    Attributes:
      cards: list of Card objects.
    """
    
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(1, 14):
                card = Card(suit, rank)
                self.cards.append(card)

    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)

    def add_card(self, card):
        """Adds a card to the deck."""
        self.cards.append(card)

    def remove_card(self, card):
        """Removes a card from the deck."""
        self.cards.remove(card)

    def pop_card(self, i=-1):
        """Removes and returns a card from the deck.

        i: index of the card to pop; by default, pops the last card.
        """
        return self.cards.pop(i)

    def shuffle(self):
        """Shuffles the cards in this deck."""
        random.shuffle(self.cards)

    def sort(self):
        """Sorts the cards in ascending order."""
        self.cards.sort()

    def move_cards(self, hand, num):
        """Moves the given number of cards from the deck into the Hand.

        hand: destination Hand object
        num: integer number of cards to move
        """
        for i in range(num):
            hand.add_card(self.pop_card())
    
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        list_hands = []
        for i in range(num_hands):
            list_hands.append(Hand())
        for n in range(num_cards):
            for x in list_hands:
                Deck.move_cards(self, x, 1)
        return list_hands
        # this deals one card to a hand at a time, like most card games.
        # for x in list_hands:
        #     Deck.move_cards(self, x, num_cards)
        # would deal the number of cards to one hand, then the next, which is
        # more efficient but seems less accurate?


class Hand(Deck):
    """Represents a hand of playing cards."""
    
    def __init__(self, label=''):
        self.cards = []
        self.label = label


def find_defining_class(obj, method_name):
    """Finds and returns the class object that will provide 
    the definition of method_name (as a string) if it is
    invoked on obj.

    obj: any python object
    method_name: string method name
    """
    for ty in type(obj).mro():
        if method_name in ty.__dict__:
            return ty
    return None


if __name__ == '__main__':
    # Example of using find_defining_class to probe object inheritance
    test_hand = Hand()
    print "'shuffle' is a method of", find_defining_class(test_hand, 'shuffle')

    # TODO: The following test code won't fully work until you define the deal_hands Deck method
    deck = Deck()
    deck.shuffle()
    hands = deck.deal_hands(4, 5)
    for i, hand in enumerate(hands):
        print "Hand {}:".format(i)
        hand.sort()
        print hand
"""This module contains code from
Think Python by <NAME>
http://thinkpython.com

Copyright 2012 <NAME>
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html

"""

import random


class Card(object):
    """Represents a standard playing card.
    
    Attributes:
      suit: integer 0-3
      rank: integer 1-13
    """

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]

    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        """Returns a human-readable string representation."""
        return "{0} of {1}".format(Card.rank_names[self.rank],
                                   Card.suit_names[self.suit])

    def __cmp__(self, other):
        """Compares this card to other, first by suit, then rank.

        Returns a positive number if this > other; negative if other > this;
        and 0 if they are equivalent.
        """
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return cmp(t1, t2)


class Deck(object):
    """Represents a deck of cards.

    Attributes:
      cards: list of Card objects.
    """
    
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(1, 14):
                card = Card(suit, rank)
                self.cards.append(card)

    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)

    def add_card(self, card):
        """Adds a card to the deck."""
        self.cards.append(card)

    def remove_card(self, card):
        """Removes a card from the deck."""
        self.cards.remove(card)

    def pop_card(self, i=-1):
        """Removes and returns a card from the deck.

        i: index of the card to pop; by default, pops the last card.
        """
        return self.cards.pop(i)

    def shuffle(self):
        """Shuffles the cards in this deck."""
        random.shuffle(self.cards)

    def sort(self):
        """Sorts the cards in ascending order."""
        self.cards.sort()

    def move_cards(self, hand, num):
        """Moves the given number of cards from the deck into the Hand.

        hand: destination Hand object
        num: integer number of cards to move
        """
        for i in range(num):
            hand.add_card(self.pop_card())
    
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        hands = []
        for i in range(num_hands):
            hand = Hand()
            self.move_cards(hand, num_cards)
            hands.append(hand)
        return hands
    
    def deal_poker_hands(self, num_hands, num_cards):
        """Return a list of new Poker Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        hands = []
        for i in range(num_hands):
            hand = PokerHand()
            self.move_cards(hand, num_cards)
            hands.append(hand)
        return hands


class Hand(Deck):
    """Represents a hand of playing cards."""
    
    def __init__(self, label=''):
        self.cards = []
        self.label = label


def find_defining_class(obj, method_name):
    """Finds and returns the class object that will provide 
    the definition of method_name (as a string) if it is
    invoked on obj.

    obj: any python object
    method_name: string method name
    """
    for ty in type(obj).mro():
        if method_name in ty.__dict__:
            return ty
    return None


if __name__ == '__main__':
    # Example of using find_defining_class to probe object inheritance
    test_hand = Hand()
    print "'shuffle' is a method of", find_defining_class(test_hand, 'shuffle')

    # TODO: The following test code won't fully work until you define the deal_hands Deck method
    deck = Deck()
    deck.shuffle()
    hands = deck.deal_hands(4, 5)
    for i, hand in enumerate(hands):
        print "Hand {}:".format(i)
        hand.sort()
        print hand
"""This module contains code from
Think Python by <NAME>
http://thinkpython.com

Copyright 2012 <NAME>
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html

"""

import random


class Card(object):
    """Represents a standard playing card.
    
    Attributes:
      suit: integer 0-3
      rank: integer 1-13
    """

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]

    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        """Returns a human-readable string representation."""
        return "{0} of {1}".format(Card.rank_names[self.rank],
                                   Card.suit_names[self.suit])

    def __cmp__(self, other):
        """Compares this card to other, first by suit, then rank.

        Returns a positive number if this > other; negative if other > this;
        and 0 if they are equivalent.
        """
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return cmp(t1, t2)


class Deck(object):
    """Represents a deck of cards.

    Attributes:
      cards: list of Card objects.
    """
    
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(1, 14):
                card = Card(suit, rank)
                self.cards.append(card)

    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)

    def add_card(self, card):
        """Adds a card to the deck."""
        self.cards.append(card)

    def remove_card(self, card):
        """Removes a card from the deck."""
        self.cards.remove(card)

    def pop_card(self, i=-1):
        """Removes and returns a card from the deck.

        i: index of the card to pop; by default, pops the last card.
        """
        return self.cards.pop(i)

    def shuffle(self):
        """Shuffles the cards in this deck."""
        random.shuffle(self.cards)

    def sort(self):
        """Sorts the cards in ascending order."""
        self.cards.sort()

    def move_cards(self, hand, num):
        """Moves the given number of cards from the deck into the Hand.

        hand: destination Hand object
        num: integer number of cards to move
        """
        for i in range(num):
            hand.add_card(self.pop_card())
    
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        list_Hand = []
        for i  in range(num_hands):
            temp = Hand('{}'.format(i))
            self.move_cards(temp, num_cards)
            list_Hand.append(temp)
        
        
        
        return list_Hand  
    
    


class Hand(Deck):
    """Represents a hand of playing cards."""
    
    def __init__(self, label=''):
        self.cards = []
        self.label = label


def find_defining_class(obj, method_name):
    """Finds and returns the class object that will provide 
    the definition of method_name (as a string) if it is
    invoked on obj.

    obj: any python object
    method_name: string method name
    """
    for ty in type(obj).mro():
        if method_name in ty.__dict__:
            return ty
    return None


if __name__ == '__main__':
    # Example of using find_defining_class to probe object inheritance
    test_hand = Hand()
    print "'shuffle' is a method of", find_defining_class(test_hand, 'shuffle')
    print '\n'

    # TODO: The following test code won't fully work until you define the deal_hands Deck method
    deck = Deck()
    deck.shuffle()
    hands = deck.deal_hands(4, 5)
    for i, hand in enumerate(hands):
        print "Hand {}:".format(i)
        hand.sort()
        print hand
        print '\n'
"""This module contains code from
Think Python by <NAME>
http://thinkpython.com

Copyright 2012 <NAME>
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html

"""

import random


class Card(object):
    """Represents a standard playing card.
    
    Attributes:
      suit: integer 0-3
      rank: integer 1-13
    """

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]

    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        """Returns a human-readable string representation."""
        return "{0} of {1}".format(Card.rank_names[self.rank],
                                   Card.suit_names[self.suit])

    def __cmp__(self, other):
        """Compares this card to other, first by suit, then rank.

        Returns a positive number if this > other; negative if other > this;
        and 0 if they are equivalent.
        """
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return cmp(t1, t2)


class Deck(object):
    """Represents a deck of cards.

    Attributes:
      cards: list of Card objects.
    """
    
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(1, 14):
                card = Card(suit, rank)
                self.cards.append(card)

    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)

    def add_card(self, card):
        """Adds a card to the deck."""
        self.cards.append(card)

    def remove_card(self, card):
        """Removes a card from the deck."""
        self.cards.remove(card)

    def pop_card(self, i=-1):
        """Removes and returns a card from the deck.

        i: index of the card to pop; by default, pops the last card.
        """
        return self.cards.pop(i)

    def shuffle(self):
        """Shuffles the cards in this deck."""
        random.shuffle(self.cards)

    def sort(self):
        """Sorts the cards in ascending order."""
        self.cards.sort()

    def move_cards(self, hand, num):
        """Moves the given number of cards from the deck into the Hand.

        hand: destination Hand object
        num: integer number of cards to move
        """
        for i in range(num):
            hand.add_card(self.pop_card())
    
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        for hands in range(num_hands):
            individualHand = 'hand_num ' + str(hands)
            hand = Hand(individualHand)
            print "Created hand called:", individualHand, "Hand has cards:"
            for card_count in range(num_cards):
                hand.add_card(self.pop_card())
            print hand
        return []


class Hand(Deck):
    """Represents a hand of playing cards."""
    
    def __init__(self, label=''):
        self.cards = []
        self.label = label


def find_defining_class(obj, method_name):
    """Finds and returns the class object that will provide 
    the definition of method_name (as a string) if it is
    invoked on obj.

    obj: any python object
    method_name: string method name
    """
    for ty in type(obj).mro():
        if method_name in ty.__dict__:
            return ty
    return None


if __name__ == '__main__':
    # Example of using find_defining_class to probe object inheritance
    test_hand = Hand()
    print "'shuffle' is a method of", find_defining_class(test_hand, 'shuffle')

    # TODO: The following test code won't fully work until you define the deal_hands Deck method
    deck = Deck()
    deck.shuffle()
    hands = deck.deal_hands(4, 5)
    for i, hand in enumerate(hands):
        print "Hand {}:".format(i)
        hand.sort()
        print hand
"""This module contains code from
Think Python by <NAME>
http://thinkpython.com

Copyright 2012 <NAME>
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html

"""

import random


class Card(object):
    """Represents a standard playing card.
    
    Attributes:
      suit: integer 0-3
      rank: integer 1-13
    """

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]

    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        """Returns a human-readable string representation."""
        return "{0} of {1}".format(Card.rank_names[self.rank],
                                   Card.suit_names[self.suit])

    def __cmp__(self, other):
        """Compares this card to other, first by suit, then rank.

        Returns a positive number if this > other; negative if other > this;
        and 0 if they are equivalent.
        """
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return cmp(t1, t2)


class Deck(object):
    """Represents a deck of cards.

    Attributes:
      cards: list of Card objects.
    """
    
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(1, 14):
                card = Card(suit, rank)
                self.cards.append(card)

    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)

    def add_card(self, card):
        """Adds a card to the deck."""
        self.cards.append(card)

    def remove_card(self, card):
        """Removes a card from the deck."""
        self.cards.remove(card)

    def pop_card(self, i=-1):
        """Removes and returns a card from the deck.

        i: index of the card to pop; by default, pops the last card.
        """
        return self.cards.pop(i)

    def shuffle(self):
        """Shuffles the cards in this deck."""
        random.shuffle(self.cards)

    def sort(self):
        """Sorts the cards in ascending order."""
        self.cards.sort()

    def move_cards(self, hand, num):
        """Moves the given number of cards from the deck into the Hand.

        hand: destination Hand object
        num: integer number of cards to move
        """
        for i in range(num):
            hand.add_card(self.pop_card())
    
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        hands=[]
        for i in range(num_hands):
            i+=1
            hands.append(Hand("Player{} Hand".format(i)))
            
        for hand in hands:
            self.move_cards(hand,num_cards)
        
        return hands


class Hand(Deck):
    """Represents a hand of playing cards."""
    
    def __init__(self, label=''):
        self.cards = []
        self.label = label


def find_defining_class(obj, method_name):
    """Finds and returns the class object that will provide 
    the definition of method_name (as a string) if it is
    invoked on obj.

    obj: any python object
    method_name: string method name
    """
    for ty in type(obj).mro():
        if method_name in ty.__dict__:
            return ty
    return None


if __name__ == '__main__':
    # Example of using find_defining_class to probe object inheritance
    test_hand = Hand()
    print "'shuffle' is a method of", find_defining_class(test_hand, 'shuffle')

    deck = Deck()
    deck.shuffle()
    hands = deck.deal_hands(4, 5)
    print hands
    for i, hand in enumerate(hands):
        i+=1
        print "Hand {}:".format(i)
        hand.sort()
        print hand
"""This module contains code from
Think Python by <NAME>
http://thinkpython.com

Copyright 2012 <NAME>
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html

"""

import random


class Card(object):
    """Represents a standard playing card.
    
    Attributes:
      suit: integer 0-3
      rank: integer 1-13
    """

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]

    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        """Returns a human-readable string representation."""
        return "{0} of {1}".format(Card.rank_names[self.rank],
                                   Card.suit_names[self.suit])

    def __cmp__(self, other):
        """Compares this card to other, first by suit, then rank.

        Returns a positive number if this > other; negative if other > this;
        and 0 if they are equivalent.
        """
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return cmp(t1, t2)


class Deck(object):
    """Represents a deck of cards.

    Attributes:
      cards: list of Card objects.
    """
    
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(1, 14):
                card = Card(suit, rank)
                self.cards.append(card)

    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)

    def add_card(self, card):
        """Adds a card to the deck."""
        self.cards.append(card)

    def remove_card(self, card):
        """Removes a card from the deck."""
        self.cards.remove(card)

    def pop_card(self, i=-1):
        """Removes and returns a card from the deck.

        i: index of the card to pop; by default, pops the last card.
        """
        return self.cards.pop(i)

    def shuffle(self):
        """Shuffles the cards in this deck."""
        random.shuffle(self.cards)

    def sort(self):
        """Sorts the cards in ascending order."""
        self.cards.sort()

    def move_cards(self, hand, num):
        """Moves the given number of cards from the deck into the Hand.

        hand: destination Hand object
        num: integer number of cards to move
        """
        for i in range(num):
            hand.add_card(self.pop_card())
    
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        hands = []
        for i in range(num_hands):
            new_hand = PokerHand()
            self.move_cards(new_hand, num_cards)
            hands.append(new_hand)
        return hands


class Hand(Deck):
    """Represents a hand of playing cards."""
    
    def __init__(self, label=''):
        self.cards = []
        self.label = label


def find_defining_class(obj, method_name):
    """Finds and returns the class object that will provide 
    the definition of method_name (as a string) if it is
    invoked on obj.

    obj: any python object
    method_name: string method name
    """
    for ty in type(obj).mro():
        if method_name in ty.__dict__:
            return ty
    return None


if __name__ == '__main__':
    # Example of using find_defining_class to probe object inheritance
    test_hand = Hand()
    print "'shuffle' is a method of", find_defining_class(test_hand, 'shuffle')

    # TODO: The following test code won't fully work until you define the deal_hands Deck method
    deck = Deck()
    deck.shuffle()
    hands = deck.deal_hands(4, 5)
    for i, hand in enumerate(hands):
        print "Hand {}:".format(i)
        hand.sort()
        print hand
"""This module contains code from
Think Python by <NAME>
http://thinkpython.com

Copyright 2012 <NAME>
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html

"""

import random


class Card(object):
    """Represents a standard playing card.
    
    Attributes:
      suit: integer 0-3
      rank: integer 1-13
    """

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]

    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        """Returns a human-readable string representation."""
        return "{0} of {1}".format(Card.rank_names[self.rank],
                                   Card.suit_names[self.suit])

    def __cmp__(self, other):
        """Compares this card to other, first by suit, then rank.

        Returns a positive number if this > other; negative if other > this;
        and 0 if they are equivalent.
        """
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return cmp(t1, t2)


class Deck(object):
    """Represents a deck of cards.

    Attributes:
      cards: list of Card objects.
    """
    
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(1, 14):
                card = Card(suit, rank)
                self.cards.append(card)

    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)

    def add_card(self, card):
        """Adds a card to the deck."""
        self.cards.append(card)

    def remove_card(self, card):
        """Removes a card from the deck."""
        self.cards.remove(card)

    def pop_card(self, i=-1):
        """Removes and returns a card from the deck.

        i: index of the card to pop; by default, pops the last card.
        """
        return self.cards.pop(i)

    def shuffle(self):
        """Shuffles the cards in this deck."""
        random.shuffle(self.cards)

    def sort(self):
        """Sorts the cards in ascending order."""
        self.cards.sort()

    def move_cards(self, hand, num):
        """Moves the given number of cards from the deck into the Hand.

        hand: destination Hand object
        num: integer number of cards to move
        """
        for i in range(num):
            hand.add_card(self.pop_card())
    
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        lista = []
        for j in range(num_hands):
            temp_hand = Hand()
            self.move_cards(temp_hand, num_cards) 
            lista.append(temp_hand)
        return lista
                         
            # TODO: Implement (Exercise 3)


class Hand(Deck):
    """Represents a hand of playing cards."""
    
    def __init__(self, label=''):
        self.cards = []
        self.label = label


def find_defining_class(obj, method_name):
    """Finds and returns the class object that will provide 
    the definition of method_name (as a string) if it is
    invoked on obj.

    obj: any python object
    method_name: string method name
    """
    for ty in type(obj).mro():
        if method_name in ty.__dict__:
            return ty
    return None


if __name__ == '__main__':
    # Example of using find_defining_class to probe object inheritance
    test_hand = Hand()
    print "'shuffle' is a method of", find_defining_class(test_hand, 'shuffle')

    # TODO: The following test code won't fully work until you define the deal_hands Deck method
    deck = Deck()
    deck.shuffle()
    hands = deck.deal_hands(4, 5)
    for i, hand in enumerate(hands):
        print "Hand {}:".format(i)
        hand.sort()
        print hand
        
"""This module contains code from
Think Python by <NAME>
http://thinkpython.com

Copyright 2012 <NAME>
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html

"""

import random


class Card(object):
    """Represents a standard playing card.
    
    Attributes:
      suit: integer 0-3
      rank: integer 1-13
    """

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]

    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        """Returns a human-readable string representation."""
        return "{0} of {1}".format(Card.rank_names[self.rank],
                                   Card.suit_names[self.suit])

    def __cmp__(self, other):
        """Compares this card to other, first by suit, then rank.

        Returns a positive number if this > other; negative if other > this;
        and 0 if they are equivalent.
        """
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return cmp(t1, t2)


class Deck(object):
    """Represents a deck of cards.

    Attributes:
      cards: list of Card objects.
    """
    
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(1, 14):
                card = Card(suit, rank)
                self.cards.append(card)

    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)

    def add_card(self, card):
        """Adds a card to the deck."""
        self.cards.append(card)

    def remove_card(self, card):
        """Removes a card from the deck."""
        self.cards.remove(card)

    def pop_card(self, i=-1):
        """Removes and returns a card from the deck.

        i: index of the card to pop; by default, pops the last card.
        """
        return self.cards.pop(i)

    def shuffle(self):
        """Shuffles the cards in this deck."""
        random.shuffle(self.cards)

    def sort(self):
        """Sorts the cards in ascending order."""
        self.cards.sort()

    def move_cards(self, hand, num):
        """Moves the given number of cards from the deck into the Hand.

        hand: destination Hand object
        num: integer number of cards to move
        """
        for i in range(num):
            hand.add_card(self.pop_card())
    
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        hand_list = []
        for item in range(num_hands):
            hand = Hand()
            deck.move_cards(hand, num_cards)
            hand_list.append(hand)
            # print hand, "\n"
        return hand_list


class Hand(Deck):
    """Represents a hand of playing cards."""
    
    def __init__(self, label=''):
        self.cards = []
        self.label = label


def find_defining_class(obj, method_name):
    """Finds and returns the class object that will provide 
    the definition of method_name (as a string) if it is
    invoked on obj.

    obj: any python object
    method_name: string method name
    """
    for ty in type(obj).mro():
        if method_name in ty.__dict__:
            return ty
    return None


# if __name__ == '__main__':
#     # Example of using find_defining_class to probe object inheritance
#     test_hand = Hand()
#     print "'shuffle' is a method of", find_defining_class(test_hand, 'shuffle')

deck = Deck()
deck.shuffle()
hands = deck.deal_hands(4, 5)
print hands
for i, hand in enumerate(hands):
    print "Hand {}:".format(i)
    hand.sort()
    print hand
"""This module contains code from
Think Python by <NAME>
http://thinkpython.com

Copyright 2012 <NAME>
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html

"""

import random


class Card(object):
    """Represents a standard playing card.
    
    Attributes:
      suit: integer 0-3
      rank: integer 1-13
    """

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]

    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        """Returns a human-readable string representation."""
        return "{0} of {1}".format(Card.rank_names[self.rank],
                                   Card.suit_names[self.suit])

    def __cmp__(self, other):
        """Compares this card to other, first by suit, then rank.

        Returns a positive number if this > other; negative if other > this;
        and 0 if they are equivalent.
        """
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return cmp(t1, t2)


class Deck(object):
    """Represents a deck of cards.

    Attributes:
      cards: list of Card objects.
    """
    
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(1, 14):
                card = Card(suit, rank)
                self.cards.append(card)

    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)

    def add_card(self, card):
        """Adds a card to the deck."""
        self.cards.append(card)

    def remove_card(self, card):
        """Removes a card from the deck."""
        self.cards.remove(card)

    def pop_card(self, i=-1):
        """Removes and returns a card from the deck.

        i: index of the card to pop; by default, pops the last card.
        """
        return self.cards.pop(i)

    def shuffle(self):
        """Shuffles the cards in this deck."""
        random.shuffle(self.cards)

    def sort(self):
        """Sorts the cards in ascending order."""
        self.cards.sort()

    def move_cards(self, hand, num):
        """Moves the given number of cards from the deck into the Hand.

        hand: destination Hand object
        num: integer number of cards to move
        """
        for i in range(num):
            hand.add_card(self.pop_card())
    
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        hands = []
        for hand in range(num_hands):
            temp_hand = Hand()
            self.move_cards(temp_hand, num_cards)
            hands.append(temp_hand)
        return hands


class Hand(Deck):
    """Represents a hand of playing cards."""
    
    def __init__(self, label=''):
        self.cards = []
        self.label = label


def find_defining_class(obj, method_name):
    """Finds and returns the class object that will provide 
    the definition of method_name (as a string) if it is
    invoked on obj.

    obj: any python object
    method_name: string method name
    """
    for ty in type(obj).mro():
        if method_name in ty.__dict__:
            return ty
    return None


if __name__ == '__main__':
    # Example of using find_defining_class to probe object inheritance
    test_hand = Hand()
    print "'shuffle' is a method of", find_defining_class(test_hand, 'shuffle')

    # TODO: The following test code won't fully work until you define the deal_hands Deck method
    deck = Deck()
    deck.shuffle()
    hands = deck.deal_hands(4, 5)
    for i, hand in enumerate(hands):
        print "Hand {}:".format(i)
        hand.sort()
        print hand
"""This module contains code from
Think Python by <NAME>
http://thinkpython.com

Copyright 2012 <NAME>
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html

"""

import random


class Card(object):
    """Represents a standard playing card.
    
    Attributes:
      suit: integer 0-3
      rank: integer 1-13
    """

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]

    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        """Returns a human-readable string representation."""
        return "{0} of {1}".format(Card.rank_names[self.rank],
                                   Card.suit_names[self.suit])

    def __cmp__(self, other):
        """Compares this card to other, first by suit, then rank.

        Returns a positive number if this > other; negative if other > this;
        and 0 if they are equivalent.
        """
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return cmp(t1, t2)


class Deck(object):
    """Represents a deck of cards.

    Attributes:
      cards: list of Card objects.
    """
    
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(1, 14):
                card = Card(suit, rank)
                self.cards.append(card)

    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)

    def add_card(self, card):
        """Adds a card to the deck."""
        self.cards.append(card)

    def remove_card(self, card):
        """Removes a card from the deck."""
        self.cards.remove(card)

    def pop_card(self, i=-1):
        """Removes and returns a card from the deck.

        i: index of the card to pop; by default, pops the last card.
        """
        return self.cards.pop(i)

    def shuffle(self):
        """Shuffles the cards in this deck."""
        random.shuffle(self.cards)

    def sort(self):
        """Sorts the cards in ascending order."""
        self.cards.sort()

    def move_cards(self, hand, num):
        """Moves the given number of cards from the deck into the Hand.

        hand: destination Hand object
        num: integer number of cards to move
        """
        for i in range(num):
            hand.add_card(self.pop_card())
    
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        hands_list = []
        for i in range(num_hands):
            h = Hand()
            self.move_cards(h,num_cards)
            hands_list.append(h)
        return hands_list


class Hand(Deck):
    """Represents a hand of playing cards."""
    
    def __init__(self, label=''):
        self.cards = []
        self.label = label


def find_defining_class(obj, method_name):
    """Finds and returns the class object that will provide 
    the definition of method_name (as a string) if it is
    invoked on obj.

    obj: any python object
    method_name: string method name
    """
    for ty in type(obj).mro():
        if method_name in ty.__dict__:
            return ty
    return None


if __name__ == '__main__':
    # Example of using find_defining_class to probe object inheritance
    test_hand = Hand()
    print "'shuffle' is a method of", find_defining_class(test_hand, 'shuffle')

    # TODO: The following test code won't fully work until you define the deal_hands Deck method
    deck = Deck()
    deck.shuffle()
    hands = deck.deal_hands(4, 5)
    for i, hand in enumerate(hands):
        print "Hand {}:".format(i)
        hand.sort()
        print hand
"""This module contains code from
Think Python by <NAME>
http://thinkpython.com

Copyright 2012 <NAME>
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html

"""

import random


class Card(object):
    """Represents a standard playing card.
    
    Attributes:
      suit: integer 0-3
      rank: integer 1-13
    """

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]

    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        """Returns a human-readable string representation."""
        return "{0} of {1}".format(Card.rank_names[self.rank],
                                   Card.suit_names[self.suit])

    def __cmp__(self, other):
        """Compares this card to other, first by suit, then rank.

        Returns a positive number if this > other; negative if other > this;
        and 0 if they are equivalent.
        """
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return cmp(t1, t2)


class Deck(object):
    """Represents a deck of cards.

    Attributes:
      cards: list of Card objects.
    """
    
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(1, 14):
                card = Card(suit, rank)
                self.cards.append(card)

    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)

    def add_card(self, card):
        """Adds a card to the deck."""
        self.cards.append(card)

    def remove_card(self, card):
        """Removes a card from the deck."""
        self.cards.remove(card)

    def pop_card(self, i=-1):
        """Removes and returns a card from the deck.

        i: index of the card to pop; by default, pops the last card.
        """
        return self.cards.pop(i)

    def shuffle(self):
        """Shuffles the cards in this deck."""
        random.shuffle(self.cards)

    def sort(self):
        """Sorts the cards in ascending order."""
        self.cards.sort()

    def move_cards(self, hand, num):
        """Moves the given number of cards from the deck into the Hand.

        hand: destination Hand object
        num: integer number of cards to move
        """
        for i in range(num):
            hand.add_card(self.pop_card())
    
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        new_hands=[]
        for i in range(num_hands):
            current_hand=Hand('Hand'+str(i))
            for j in range(num_cards):
                current_card=self.pop_card()
                current_hand.add_card(current_card)
                new_hands.append(current_hand)
        return new_hands


class Hand(Deck):
    """Represents a hand of playing cards."""
    
    def __init__(self, label=''):
        self.cards = []
        self.label = label


def find_defining_class(obj, method_name):
    """Finds and returns the class object that will provide 
    the definition of method_name (as a string) if it is
    invoked on obj.

    obj: any python object
    method_name: string method name
    """
    for ty in type(obj).mro():
        if method_name in ty.__dict__:
            return ty
    return None


if __name__ == '__main__':
    # Example of using find_defining_class to probe object inheritance
    test_hand = Hand()
    print "'shuffle' is a method of", find_defining_class(test_hand, 'shuffle')

    # TODO: The following test code won't fully work until you define the deal_hands Deck method
    deck = Deck()
    deck.shuffle()
    hands = deck.deal_hands(4, 5)
    for i, hand in enumerate(hands):
        print "Hand {}:".format(i)
        hand.sort()
        print hand
"""This module contains code from
Think Python by <NAME>
http://thinkpython.com

Copyright 2012 <NAME>
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html

"""

import random


class Card(object):
    """Represents a standard playing card.
    
    Attributes:
      suit: integer 0-3
      rank: integer 1-13
    """

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]

    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        """Returns a human-readable string representation."""
        return "{0} of {1}".format(Card.rank_names[self.rank],
                                   Card.suit_names[self.suit])

    def __cmp__(self, other):
        """Compares this card to other, first by suit, then rank.

        Returns a positive number if this > other; negative if other > this;
        and 0 if they are equivalent.
        """
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return cmp(t1, t2)


class Deck(object):
    """Represents a deck of cards.

    Attributes:
      cards: list of Card objects.
    """
    
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(1, 14):
                card = Card(suit, rank)
                self.cards.append(card)

    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)

    def add_card(self, card):
        """Adds a card to the deck."""
        self.cards.append(card)

    def remove_card(self, card):
        """Removes a card from the deck."""
        self.cards.remove(card)

    def pop_card(self, i=-1):
        """Removes and returns a card from the deck.

        i: index of the card to pop; by default, pops the last card.
        """
        return self.cards.pop(i)

    def shuffle(self):
        """Shuffles the cards in this deck."""
        random.shuffle(self.cards)

    def sort(self):
        """Sorts the cards in ascending order."""
        self.cards.sort()

    def move_cards(self, hand, num):
        """Moves the given number of cards from the deck into the Hand.

        hand: destination Hand object
        num: integer number of cards to move
        """
        for i in range(num):
            hand.add_card(self.pop_card())
    
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        hands_list = []
        for i in range(num_hands):
            hand = Hand("Hand {}".format(i))
            self.move_cards(hand, num_cards)
            hands_list.append(hand)
        return hands_list


class Hand(Deck):
    """Represents a hand of playing cards."""
    
    def __init__(self, label=''):
        self.cards = []
        self.label = label


def find_defining_class(obj, method_name):
    """Finds and returns the class object that will provide 
    the definition of method_name (as a string) if it is
    invoked on obj.

    obj: any python object
    method_name: string method name
    """
    for ty in type(obj).mro():
        if method_name in ty.__dict__:
            return ty
    return None


if __name__ == '__main__':
    # Example of using find_defining_class to probe object inheritance
    test_hand = Hand()
    print "'shuffle' is a method of", find_defining_class(test_hand, 'shuffle')

    # TODO: The following test code won't fully work until you define the deal_hands Deck method
    deck = Deck()
    deck.shuffle()
    hands = deck.deal_hands(4, 5)
    for i, hand in enumerate(hands):
        print "Hand {}:".format(i)
        hand.sort()
        print hand
"""This module contains code from
Think Python by <NAME>
http://thinkpython.com

Copyright 2012 <NAME>
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html

"""

import random


class Card(object):
    """Represents a standard playing card.
    
    Attributes:
      suit: integer 0-3
      rank: integer 1-13
    """

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]

    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        """Returns a human-readable string representation."""
        return "{0} of {1}".format(Card.rank_names[self.rank],
                                   Card.suit_names[self.suit])

    def __cmp__(self, other):
        """Compares this card to other, first by suit, then rank.

        Returns a positive number if this > other; negative if other > this;
        and 0 if they are equivalent.
        """
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return cmp(t1, t2)


class Deck(object):
    """Represents a deck of cards.

    Attributes:
      cards: list of Card objects.
    """
    
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(1, 14):
                card = Card(suit, rank)
                self.cards.append(card)

    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)

    def add_card(self, card):
        """Adds a card to the deck."""
        self.cards.append(card)

    def remove_card(self, card):
        """Removes a card from the deck."""
        self.cards.remove(card)

    def pop_card(self, i=-1):
        """Removes and returns a card from the deck.

        i: index of the card to pop; by default, pops the last card.
        """
        return self.cards.pop(i)

    def shuffle(self):
        """Shuffles the cards in this deck."""
        random.shuffle(self.cards)

    def sort(self):
        """Sorts the cards in ascending order."""
        self.cards.sort()

    def move_cards(self, hand, num):
        """Moves the given number of cards from the deck into the Hand.

        hand: destination Hand object
        num: integer number of cards to move
        """
        for i in range(num):
            hand.add_card(self.pop_card())
    
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        hands=[]
        for current_hand in range(num_hands):
            hand= Hand()
            self.move_cards(hand, num_cards)
            hands.append(hand)
        return hands


class Hand(Deck):
    """Represents a hand of playing cards."""
    
    def __init__(self, label=''):
        self.cards = []
        self.label = label


def find_defining_class(obj, method_name):
    """Finds and returns the class object that will provide 
    the definition of method_name (as a string) if it is
    invoked on obj.

    obj: any python object
    method_name: string method name
    """
    for ty in type(obj).mro():
        if method_name in ty.__dict__:
            return ty
    return None


if __name__ == '__main__':
    # Example of using find_defining_class to probe object inheritance
    test_hand = Hand()
    print "'shuffle' is a method of", find_defining_class(test_hand, 'shuffle')

    # TODO: The following test code won't fully work until you define the deal_hands Deck method
    deck = Deck()
    deck.shuffle()
    hands = deck.deal_hands(4, 5)
    for i, hand in enumerate(hands):
        print "Hand {}:".format(i)
        hand.sort()
        print hand
"""This module contains code from
Think Python by <NAME>
http://thinkpython.com

Copyright 2012 <NAME>
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html

"""

import random


class Card(object):
    """Represents a standard playing card.
    
    Attributes:
      suit: integer 0-3
      rank: integer 1-13
    """

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]

    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        """Returns a human-readable string representation."""
        return "{0} of {1}".format(Card.rank_names[self.rank],
                                   Card.suit_names[self.suit])

    def __cmp__(self, other):
        """Compares this card to other, first by suit, then rank.

        Returns a positive number if this > other; negative if other > this;
        and 0 if they are equivalent.
        """
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return cmp(t1, t2)


class Deck(object):
    """Represents a deck of cards.

    Attributes:
      cards: list of Card objects.
    """
    
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(1, 14):
                card = Card(suit, rank)
                self.cards.append(card)

    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)

    def add_card(self, card):
        """Adds a card to the deck."""
        self.cards.append(card)

    def remove_card(self, card):
        """Removes a card from the deck."""
        self.cards.remove(card)

    def pop_card(self, i=-1):
        """Removes and returns a card from the deck.

        i: index of the card to pop; by default, pops the last card.
        """
        return self.cards.pop(i)

    def shuffle(self):
        """Shuffles the cards in this deck."""
        random.shuffle(self.cards)

    def sort(self):
        """Sorts the cards in ascending order."""
        self.cards.sort()

    def move_cards(self, hand, num):
        """Moves the given number of cards from the deck into the Hand.

        hand: destination Hand object
        num: integer number of cards to move
        """
        for i in range(num):
            hand.add_card(self.pop_card())
    
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        hand_list = []
        for hand in range(num_hands):
            hand_name = "Hand" + str(hand)
            new_hand = Hand(hand_name)
            self.move_cards(new_hand, num_cards)
            hand_list.append(new_hand)
        return hand_list
    
class Hand(Deck):
    """Represents a hand of playing cards."""
    
    def __init__(self, label=''):
        self.cards = []
        self.label = label
    
    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)


def find_defining_class(obj, method_name):
    """Finds and returns the class object that will provide 
    the definition of method_name (as a string) if it is
    invoked on obj.

    obj: any python object
    method_name: string method name
    """
    for ty in type(obj).mro():
        if method_name in ty.__dict__:
            return ty
    return None


if __name__ == '__main__':
    # Example of using find_defining_class to probe object inheritance
    test_hand = Hand()
    print "'shuffle' is a method of", find_defining_class(test_hand, 'shuffle')

    # TODO: The following test code won't fully work until you define the deal_hands Deck method
    deck = Deck()
    deck.shuffle()
    hands = deck.deal_hands(4, 5)
    for i, hand in enumerate(hands):
        print "Hand {}:".format(i)
        hand.sort()
        print hand

"""This module contains code from
Think Python by <NAME>
http://thinkpython.com

Copyright 2012 <NAME>
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html

"""

import random


class Card(object):
    """Represents a standard playing card.
    
    Attributes:
      suit: integer 0-3
      rank: integer 1-13
    """

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]

    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        """Returns a human-readable string representation."""
        return "{0} of {1}".format(Card.rank_names[self.rank],
                                   Card.suit_names[self.suit])

    def __cmp__(self, other):
        """Compares this card to other, first by suit, then rank.

        Returns a positive number if this > other; negative if other > this;
        and 0 if they are equivalent.
        """
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return cmp(t1, t2)


class Deck(object):
    """Represents a deck of cards.

    Attributes:
      cards: list of Card objects.
    """
    
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(1, 14):
                card = Card(suit, rank)
                self.cards.append(card)

    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)

    def add_card(self, card):
        """Adds a card to the deck."""
        self.cards.append(card)

    def remove_card(self, card):
        """Removes a card from the deck."""
        self.cards.remove(card)

    def pop_card(self, i=-1):
        """Removes and returns a card from the deck.

        i: index of the card to pop; by default, pops the last card.
        """
        return self.cards.pop(i)

    def shuffle(self):
        """Shuffles the cards in this deck."""
        random.shuffle(self.cards)

    def sort(self):
        """Sorts the cards in ascending order."""
        self.cards.sort(key = lambda card: card.rank)

    def move_cards(self, hand, num):
        """Moves the given number of cards from the deck into the Hand.

        hand: destination Hand object
        num: integer number of cards to move
        """
        for i in range(num):
            hand.add_card(self.pop_card())
    
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        hands = []
        for i in range(num_hands):
            hand = Hand('new hand')
            self.move_cards(hand, num_cards)
            hands.append(hand)
        return hands   # TODO: Implement (Exercise 3)


class Hand(Deck):
    """Represents a hand of playing cards."""
    
    def __init__(self, label='none'):
        self.cards = []
        self.label = label


def find_defining_class(obj, method_name):
    """Finds and returns the class object that will provide 
    the definition of method_name (as a string) if it is
    invoked on obj.

    obj: any python object
    method_name: string method name
    """
    for ty in type(obj).mro():
        if method_name in ty.__dict__:
            return ty
    return None


if __name__ == '__main__':
    # Example of using find_defining_class to probe object inheritance
    test_hand = Hand()
    print "'shuffle' is a method of", find_defining_class(test_hand, 'shuffle')

    # TODO: The following test code won't fully work until you define the deal_hands Deck method
    deck = Deck()
    deck.shuffle()
    hands = deck.deal_hands(4, 5)
    for i, hand in enumerate(hands):
        print "Hand {}:".format(i)
        hand.sort()
        print hand
"""This module contains code from
Think Python by <NAME>
http://thinkpython.com

Copyright 2012 <NAME>
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html

"""

import random


class Card(object):
    """Represents a standard playing card.
    
    Attributes:
      suit: integer 0-3
      rank: integer 1-13
    """

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]

    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        """Returns a human-readable string representation."""
        return "{0} of {1}".format(Card.rank_names[self.rank],
                                   Card.suit_names[self.suit])

    def __cmp__(self, other):
        """Compares this card to other, first by suit, then rank.

        Returns a positive number if this > other; negative if other > this;
        and 0 if they are equivalent.
        """
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return cmp(t1, t2)


class Deck(object):
    """Represents a deck of cards.

    Attributes:
      cards: list of Card objects.
    """
    
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(1, 14):
                card = Card(suit, rank)
                self.cards.append(card)

    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)

    def add_card(self, card):
        """Adds a card to the deck."""
        self.cards.append(card)

    def remove_card(self, card):
        """Removes a card from the deck."""
        self.cards.remove(card)

    def pop_card(self, i=-1):
        """Removes and returns a card from the deck.

        i: index of the card to pop; by default, pops the last card.
        """
        return self.cards.pop(i)

    def shuffle(self):
        """Shuffles the cards in this deck."""
        random.shuffle(self.cards)

    def sort(self):
        """Sorts the cards in ascending order."""
        self.cards.sort()

    def move_cards(self, hand, num):
        """Moves the given number of cards from the deck into the Hand.

        hand: destination Hand object
        num: integer number of cards to move
        """
        for i in range(num):
            hand.add_card(self.pop_card())
    
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        hand_list = []
        for x in range(0, num_hands):
            hand = Hand()
            self.move_cards(hand, num_cards)
            hand_list.append(hand)
                            
        return hand_list

class Hand(Deck):
    """Represents a hand of playing cards."""
    
    def __init__(self, label=''):
        self.cards = []
        self.label = label


def find_defining_class(obj, method_name):
    """Finds and returns the class object that will provide 
    the definition of method_name (as a string) if it is
    invoked on obj.

    obj: any python object
    method_name: string method name
    """
    for ty in type(obj).mro():
        if method_name in ty.__dict__:
            return ty
    return None


if __name__ == '__main__':
    # Example of using find_defining_class to probe object inheritance
    test_hand = Hand()
    print "'shuffle' is a method of", find_defining_class(test_hand, 'shuffle')

    # TODO: The following test code won't fully work until you define the deal_hands Deck method
    deck = Deck()
    deck.shuffle()
    hands = deck.deal_hands(4, 5)
    print hands
    for i, hand in enumerate(hands):
        print "Hand {}:".format(i)
        hand.sort()
        print hand
"""This module contains code from
Think Python by <NAME>
http://thinkpython.com

Copyright 2012 <NAME>
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html

"""

import random


class Card(object):
    """Represents a standard playing card.
    
    Attributes:
      suit: integer 0-3
      rank: integer 1-13
    """

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]

    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        """Returns a human-readable string representation."""
        return "{0} of {1}".format(Card.rank_names[self.rank],
                                   Card.suit_names[self.suit])

    def __cmp__(self, other):
        """Compares this card to other, first by suit, then rank.

        Returns a positive number if this > other; negative if other > this;
        and 0 if they are equivalent.
        """
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return cmp(t1, t2)


class Deck(object):
    """Represents a deck of cards.

    Attributes:
      cards: list of Card objects.
    """
    
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(1, 14):
                card = Card(suit, rank)
                self.cards.append(card)

    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)

    def add_card(self, card):
        """Adds a card to the deck."""
        self.cards.append(card)

    def remove_card(self, card):
        """Removes a card from the deck."""
        self.cards.remove(card)

    def pop_card(self, i=-1):
        """Removes and returns a card from the deck.

        i: index of the card to pop; by default, pops the last card.
        """
        return self.cards.pop(i)

    def shuffle(self):
        """Shuffles the cards in this deck."""
        random.shuffle(self.cards)

    def sort(self):
        """Sorts the cards in ascending order."""
        self.cards.sort()

    def move_cards(self, hand, num):
        """Moves the given number of cards from the deck into the Hand.

        hand: destination Hand object
        num: integer number of cards to move
        """
        for i in range(num):
            hand.add_card(self.pop_card())
    
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        returnhands = []
        for i in range(num_hands):
            hand = Hand(str(i))
            for j in range(num_cards):
                hand.cards.append(self.pop_card())
            returnhands.append(hand)
        return returnhands   # TODO: Implement (Exercise 3)


class Hand(Deck):
    """Represents a hand of playing cards."""
    
    def __init__(self, label=''):
        self.cards = []
        self.label = label


def find_defining_class(obj, method_name):
    """Finds and returns the class object that will provide 
    the definition of method_name (as a string) if it is
    invoked on obj.

    obj: any python object
    method_name: string method name
    """
    for ty in type(obj).mro():
        if method_name in ty.__dict__:
            return ty
    return None


if __name__ == '__main__':
    # Example of using find_defining_class to probe object inheritance
    test_hand = Hand()
    print "'shuffle' is a method of", find_defining_class(test_hand, 'shuffle')

    # TODO: The following test code won't fully work until you define the deal_hands Deck method
    deck = Deck()
    deck.shuffle()
    hands = deck.deal_hands(4, 5)
    for i, hand in enumerate(hands):
        print "Hand {}:".format(i)
        hand.sort()
        print hand
"""This module contains code from
Think Python by <NAME>
http://thinkpython.com

Copyright 2012 <NAME>
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html

"""

import random


class Card(object):
    """Represents a standard playing card.
    
    Attributes:
      suit: integer 0-3
      rank: integer 1-13
    """

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]

    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        """Returns a human-readable string representation."""
        return "{0} of {1}".format(Card.rank_names[self.rank],
                                   Card.suit_names[self.suit])

    def __cmp__(self, other):
        """Compares this card to other, first by suit, then rank.

        Returns a positive number if this > other; negative if other > this;
        and 0 if they are equivalent.
        """
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return cmp(t1, t2)


class Deck(object):
    """Represents a deck of cards.

    Attributes:
      cards: list of Card objects.
    """
    
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(1, 14):
                card = Card(suit, rank)
                self.cards.append(card)

    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)

    def add_card(self, card):
        """Adds a card to the deck."""
        self.cards.append(card)

    def remove_card(self, card):
        """Removes a card from the deck."""
        self.cards.remove(card)

    def pop_card(self, i=-1):
        """Removes and returns a card from the deck.

        i: index of the card to pop; by default, pops the last card.
        """
        return self.cards.pop(i)

    def shuffle(self):
        """Shuffles the cards in this deck."""
        random.shuffle(self.cards)

    def sort(self):
        """Sorts the cards in ascending order."""
        self.cards.sort()

    def move_cards(self, hand, num):
        """Moves the given number of cards from the deck into the Hand.

        hand: destination Hand object
        num: integer number of cards to move
        """
        for i in range(num):
            hand.add_card(self.pop_card())
    
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        output = [Hand() for i in range(num_hands)]
        for h in output:
            self.move_cards(h, num_cards)
        return output


class Hand(Deck):
    """Represents a hand of playing cards."""
    
    def __init__(self, label=''):
        self.cards = []
        self.label = label


def find_defining_class(obj, method_name):
    """Finds and returns the class object that will provide 
    the definition of method_name (as a string) if it is
    invoked on obj.

    obj: any python object
    method_name: string method name
    """
    for ty in type(obj).mro():
        if method_name in ty.__dict__:
            return ty
    return None


if __name__ == '__main__':
    # Example of using find_defining_class to probe object inheritance
    test_hand = Hand()
    print "'shuffle' is a method of", find_defining_class(test_hand, 'shuffle')

    # TODO: The following test code won't fully work until you define the deal_hands Deck method
    deck = Deck()
    deck.shuffle()
    hands = deck.deal_hands(4, 5)
    for i, hand in enumerate(hands):
        print "Hand {}:".format(i)
        hand.sort()
        print hand
"""This module contains code from
Think Python by <NAME>
http://thinkpython.com

Copyright 2012 <NAME>
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html

"""

import random


class Card(object):
    """Represents a standard playing card.
    
    Attributes:
      suit: integer 0-3
      rank: integer 1-13
    """

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]

    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        """Returns a human-readable string representation."""
        return "{0} of {1}".format(Card.rank_names[self.rank],
                                   Card.suit_names[self.suit])

    def __cmp__(self, other):
        """Compares this card to other, first by suit, then rank.

        Returns a positive number if this > other; negative if other > this;
        and 0 if they are equivalent.
        """
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return cmp(t1, t2)


class Deck(object):
    """Represents a deck of cards.

    Attributes:
      cards: list of Card objects.
    """
    
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(1, 14):
                card = Card(suit, rank)
                self.cards.append(card)

    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)

    def add_card(self, card):
        """Adds a card to the deck."""
        self.cards.append(card)

    def remove_card(self, card):
        """Removes a card from the deck."""
        self.cards.remove(card)

    def pop_card(self, i=-1):
        """Removes and returns a card from the deck.

        i: index of the card to pop; by default, pops the last card.
        """
        return self.cards.pop(i)

    def shuffle(self):
        """Shuffles the cards in this deck."""
        random.shuffle(self.cards)

    def sort(self):
        """Sorts the cards in ascending order."""
        self.cards.sort()

    def move_cards(self, hand, num):
        """Moves the given number of cards from the deck into the Hand.

        hand: destination Hand object
        num: integer number of cards to move
        """
        for i in range(num):
            hand.add_card(self.pop_card())
    
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        hands = []
        for each_hand in range(num_hands):
            hand = Hand()
            self.move_cards(hand,num_cards)
            hands.append(hand)
        return hands

class Hand(Deck):
    """Represents a hand of playing cards."""
    
    def __init__(self, label=''):
        self.cards = []
        self.label = label


def find_defining_class(obj, method_name):
    """Finds and returns the class object that will provide 
    the definition of method_name (as a string) if it is
    invoked on obj.

    obj: any python object
    method_name: string method name
    """
    for ty in type(obj).mro():
        if method_name in ty.__dict__:
            return ty
    return None


if __name__ == '__main__':
    # Example of using find_defining_class to probe object inheritance
    test_hand = Hand()
    print "'shuffle' is a method of", find_defining_class(test_hand, 'shuffle')

    # TODO: The following test code won't fully work until you define the deal_hands Deck method
    deck = Deck()
    deck.shuffle()
    hands = deck.deal_hands(4, 5)
    for i, hand in enumerate(hands):
        print "Hand {}:".format(i)
        hand.sort()
        print hand
"""This module contains code from
Think Python by <NAME>
http://thinkpython.com

Copyright 2012 <NAME>
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html

"""

import random


class Card(object):
    """Represents a standard playing card.
    
    Attributes:
      suit: integer 0-3
      rank: integer 1-13
    """

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]

    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        """Returns a human-readable string representation."""
        return "{0} of {1}".format(Card.rank_names[self.rank],
                                   Card.suit_names[self.suit])

    def __cmp__(self, other):
        """Compares this card to other, first by suit, then rank.

        Returns a positive number if this > other; negative if other > this;
        and 0 if they are equivalent.
        """
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return cmp(t1, t2)


class Deck(object):
    """Represents a deck of cards.

    Attributes:
      cards: list of Card objects.
    """
    
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(1, 14):
                card = Card(suit, rank)
                self.cards.append(card)

    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)

    def add_card(self, card):
        """Adds a card to the deck."""
        self.cards.append(card)

    def remove_card(self, card):
        """Removes a card from the deck."""
        self.cards.remove(card)

    def pop_card(self, i=-1):
        """Removes and returns a card from the deck.

        i: index of the card to pop; by default, pops the last card.
        """
        return self.cards.pop(i)

    def shuffle(self):
        """Shuffles the cards in this deck."""
        random.shuffle(self.cards)

    def sort(self):
        """Sorts the cards in ascending order."""
        self.cards.sort()

    def move_cards(self, hand, num):
        """Moves the given number of cards from the deck into the Hand.

        hand: destination Hand object
        num: integer number of cards to move
        """
        for i in range(num):
            hand.add_card(self.pop_card())
    
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        new_hand = []
        for x in range(0, num_hands):
            hand_object = Hand()
            self.move_cards(hand_object, num_cards)
            new_hand.append(hand_object)
        return new_hand           

class Hand(Deck):
    """Represents a hand of playing cards."""
    
    def __init__(self, label=''):
        self.cards = []
        self.label = label


def find_defining_class(obj, method_name):
    """Finds and returns the class object that will provide 
    the definition of method_name (as a string) if it is
    invoked on obj.

    obj: any python object
    method_name: string method name
    """
    for ty in type(obj).mro():
        if method_name in ty.__dict__:
            return ty
    return None


if __name__ == '__main__':
    # Example of using find_defining_class to probe object inheritance
    test_hand = Hand()
    print "'shuffle' is a method of", find_defining_class(test_hand, 'shuffle')

    # TODO: The following test code won't fully work until you define the deal_hands Deck method
    deck = Deck()
    deck.shuffle()
    hands = deck.deal_hands(4, 5)
    for i, hand in enumerate(hands):
        print "Hand {}:".format(i)
        hand.sort()
        print hand
"""This module contains code from
Think Python by <NAME>
http://thinkpython.com

Copyright 2012 <NAME>
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html

"""

import random


class Card(object):
    """Represents a standard playing card.
    
    Attributes:
      suit: integer 0-3
      rank: integer 1-13
    """

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]

    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        """Returns a human-readable string representation."""
        return "{0} of {1}".format(Card.rank_names[self.rank],
                                   Card.suit_names[self.suit])

    def __cmp__(self, other):
        """Compares this card to other, first by suit, then rank.

        Returns a positive number if this > other; negative if other > this;
        and 0 if they are equivalent.
        """
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return cmp(t1, t2)


class Deck(object):
    """Represents a deck of cards.

    Attributes:
      cards: list of Card objects.
    """
    
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(1, 14):
                card = Card(suit, rank)
                self.cards.append(card)

    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)

    def add_card(self, card):
        """Adds a card to the deck."""
        self.cards.append(card)

    def remove_card(self, card):
        """Removes a card from the deck."""
        self.cards.remove(card)

    def pop_card(self, i=-1):
        """Removes and returns a card from the deck.

        i: index of the card to pop; by default, pops the last card.
        """
        return self.cards.pop(i)

    def shuffle(self):
        """Shuffles the cards in this deck."""
        random.shuffle(self.cards)

    def sort(self):
        """Sorts the cards in ascending order."""
        self.cards.sort()

    def move_cards(self, hand, num):
        """Moves the given number of cards from the deck into the Hand.

        hand: destination Hand object
        num: integer number of cards to move
        """
        for i in range(num):
            hand.add_card(self.pop_card())
    
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        hand_list = []
        for i in range(num_hands):
            hand = Hand()
            self.move_cards(hand, num_cards)
            hand_list.append(hand)
        return hand_list   


class Hand(Deck):
    """Represents a hand of playing cards."""
    
    def __init__(self, label=''):
        self.cards = []
        self.label = label


def find_defining_class(obj, method_name):
    """Finds and returns the class object that will provide 
    the definition of method_name (as a string) if it is
    invoked on obj.

    obj: any python object
    method_name: string method name
    """
    for ty in type(obj).mro():
        if method_name in ty.__dict__:
            return ty
    return None


if __name__ == '__main__':
    # Example of using find_defining_class to probe object inheritance
    test_hand = Hand()
    print "'shuffle' is a method of", find_defining_class(test_hand, 'shuffle')

    # TODO: The following test code won't fully work until you define the deal_hands Deck method
    deck = Deck()
    deck.shuffle()
    hands = deck.deal_hands(4, 5)
    for i, hand in enumerate(hands):
        print "Hand {}:".format(i+1)
        hand.sort()
        print hand
"""This module contains code from
Think Python by <NAME>
http://thinkpython.com

Copyright 2012 <NAME>
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html

"""

import random


class Card(object):
    """Represents a standard playing card.
    
    Attributes:
      suit: integer 0-3
      rank: integer 1-13
    """

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]

    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        """Returns a human-readable string representation."""
        return "{0} of {1}".format(Card.rank_names[self.rank],
                                   Card.suit_names[self.suit])

    def __cmp__(self, other):
        """Compares this card to other, first by suit, then rank.

        Returns a positive number if this > other; negative if other > this;
        and 0 if they are equivalent.
        """
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return cmp(t1, t2)


class Deck(object):
    """Represents a deck of cards.

    Attributes:
      cards: list of Card objects.
    """
    
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(1, 14):
                card = Card(suit, rank)
                self.cards.append(card)

    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)

    def add_card(self, card):
        """Adds a card to the deck."""
        self.cards.append(card)

    def remove_card(self, card):
        """Removes a card from the deck."""
        self.cards.remove(card)

    def pop_card(self, i=-1):
        """Removes and returns a card from the deck.

        i: index of the card to pop; by default, pops the last card.
        """
        return self.cards.pop(i)

    def shuffle(self):
        """Shuffles the cards in this deck."""
        random.shuffle(self.cards)

    def sort(self):
        """Sorts the cards in ascending order."""
        self.cards.sort()

    def move_cards(self, hand, num):
        """Moves the given number of cards from the deck into the Hand.

        hand: destination Hand object
        num: integer number of cards to move
        """
        for i in range(num):
            hand.add_card(self.pop_card())
    
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        hand_list = []
        deck = Deck()
        for i in range(num_hands):
            for i in range(num_cards):
                card = deck.pop_card()
                self.add_card(card)
            hand_list.append(self)
        return hand_list   # TODO: Implement (Exercise 3)


class Hand(Deck):
    """Represents a hand of playing cards."""
    
    def __init__(self, label=''):
        self.cards = []
        self.label = label


def find_defining_class(obj, method_name):
    """Finds and returns the class object that will provide 
    the definition of method_name (as a string) if it is
    invoked on obj.

    obj: any python object
    method_name: string method name
    """
    for ty in type(obj).mro():
        if method_name in ty.__dict__:
            return ty
    return None


if __name__ == '__main__':
    # Example of using find_defining_class to probe object inheritance
    test_hand = Hand()
    print "'shuffle' is a method of", find_defining_class(test_hand, 'shuffle')

    # TODO: The following test code won't fully work until you define the deal_hands Deck method
    deck = Deck()
    deck.shuffle()
    hands = deck.deal_hands(4, 5)
    for i, hand in enumerate(hands):
        print "Hand {}:".format(i)
        hand.sort()
        print hand
"""This module contains code from
Think Python by <NAME>
http://thinkpython.com

Copyright 2012 <NAME>
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html

"""

import random


class Card(object):
    """Represents a standard playing card.
    
    Attributes:
      suit: integer 0-3
      rank: integer 1-13
    """

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]

    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        """Returns a human-readable string representation."""
        return "{0} of {1}".format(Card.rank_names[self.rank],
                                   Card.suit_names[self.suit])

    def __cmp__(self, other):
        """Compares this card to other, first by suit, then rank.

        Returns a positive number if this > other; negative if other > this;
        and 0 if they are equivalent.
        """
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return cmp(t1, t2)


class Deck(object):
    """Represents a deck of cards.

    Attributes:
      cards: list of Card objects.
    """
    
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(1, 14):
                card = Card(suit, rank)
                self.cards.append(card)

    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)

    def add_card(self, card):
        """Adds a card to the deck."""
        self.cards.append(card)

    def remove_card(self, card):
        """Removes a card from the deck."""
        self.cards.remove(card)

    def pop_card(self, i=-1):
        """Removes and returns a card from the deck.

        i: index of the card to pop; by default, pops the last card.
        """
        return self.cards.pop(i)

    def shuffle(self):
        """Shuffles the cards in this deck."""
        random.shuffle(self.cards)

    def sort(self):
        """Sorts the cards in ascending order."""
        self.cards.sort()

    def move_cards(self, hand, num):
        """Moves the given number of cards from the deck into the Hand.

        hand: destination Hand object
        num: integer number of cards to move
        """
        for i in range(num):
            hand.add_card(self.pop_card())
    
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        hands = []
        for i in range(num_hands):
            new_hand = Hand(name='Player ' + str(i + 1))
            for aCard in range(num_cards):
                new_hand.cards.append(self.pop_card())
            hands.append(new_hand)
        
        return hands   # DONE


class Hand(Deck):
    """Represents a hand of playing cards."""
    
    def __init__(self, name='', label=''):
        self.cards = []
        self.label = label
        self.name = name


def find_defining_class(obj, method_name):
    """Finds and returns the class object that will provide 
    the definition of method_name (as a string) if it is
    invoked on obj.

    obj: any python object
    method_name: string method name
    """
    for ty in type(obj).mro():
        if method_name in ty.__dict__:
            return ty
    return None


if __name__ == '__main__':
    # Example of using find_defining_class to probe object inheritance
    test_hand = Hand()
    print "'shuffle' is a method of", find_defining_class(test_hand, 'shuffle')

    # TODO: The following test code won't fully work until you define the deal_hands Deck method
    deck = Deck()
    deck.shuffle()
    hands = deck.deal_hands(4, 5)
    for i, hand in enumerate(hands):
        #print "Hand {}:".format(i)
        print hand.name # Gotchu fam
        hand.sort()
        print hand, '\n'
"""This module contains code from
Think Python by <NAME>
http://thinkpython.com

Copyright 2012 <NAME>
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html

"""

import random


class Card(object):
    """Represents a standard playing card.
    
    Attributes:
      suit: integer 0-3
      rank: integer 1-13
    """

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]

    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        """Returns a human-readable string representation."""
        return "{0} of {1}".format(Card.rank_names[self.rank],
                                   Card.suit_names[self.suit])

    def __cmp__(self, other):
        """Compares this card to other, first by suit, then rank.

        Returns a positive number if this > other; negative if other > this;
        and 0 if they are equivalent.
        """
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return cmp(t1, t2)


class Deck(object):
    """Represents a deck of cards.

    Attributes:
      cards: list of Card objects.
    """
    
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(1, 14):
                card = Card(suit, rank)
                self.cards.append(card)

    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)

    def add_card(self, card):
        """Adds a card to the deck."""
        self.cards.append(card)

    def remove_card(self, card):
        """Removes a card from the deck."""
        self.cards.remove(card)

    def pop_card(self, i=-1):
        """Removes and returns a card from the deck.

        i: index of the card to pop; by default, pops the last card.
        """
        return self.cards.pop(i)

    def shuffle(self):
        """Shuffles the cards in this deck."""
        random.shuffle(self.cards)

    def sort(self):
        """Sorts the cards in ascending order."""
        self.cards.sort()

    def move_cards(self, hand, num):
        """Moves the given number of cards from the deck into the Hand.

        hand: destination Hand object
        num: integer number of cards to move
        """
        for i in range(num):
            hand.add_card(self.pop_card())
    
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        hand_list = []
        for i in range (num_hands):
            hand_list.append(Hand(''))
        for i in range (num_hands):
            for j in range (num_cards):
                hand_list[i].cards.append(self.cards.pop(-1))
        return hand_list


class Hand(Deck):
    """Represents a hand of playing cards."""
    
    def __init__(self, label=''):
        self.cards = []
        self.label = label
        
    def __str__(self):
        hand_list = ""
        for i in range (len(self.cards)):
            hand_list = hand_list + str(self.cards[i]) + "\n"
        return hand_list

def find_defining_class(obj, method_name):
    """Finds and returns the class object that will provide 
    the definition of method_name (as a string) if it is
    invoked on obj.

    obj: any python object
    method_name: string method name
    """
    for ty in type(obj).mro():
        if method_name in ty.__dict__:
            return ty
    return None


if __name__ == '__main__':
    # Example of using find_defining_class to probe object inheritance
    test_hand = Hand()
    print "'shuffle' is a method of", find_defining_class(test_hand, 'shuffle')

    # TODO: The following test code won't fully work until you define the deal_hands Deck method
    deck = Deck()
    deck.shuffle()
    hands = deck.deal_hands(4, 5)
    for i, hand in enumerate(hands):
        print "Hand {}:".format(i)
        hand.sort()
        print hand
"""This module contains code from
Think Python by <NAME>
http://thinkpython.com

Copyright 2012 <NAME>
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html

"""

import random


class Card(object):
    """Represents a standard playing card.
    
    Attributes:
      suit: integer 0-3
      rank: integer 1-13
    """

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]

    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        """Returns a human-readable string representation."""
        return "{0} of {1}".format(Card.rank_names[self.rank],
                                   Card.suit_names[self.suit])

    def __cmp__(self, other):
        """Compares this card to other, first by suit, then rank.

        Returns a positive number if this > other; negative if other > this;
        and 0 if they are equivalent.
        """
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return cmp(t1, t2)


class Deck(object):
    """Represents a deck of cards.

    Attributes:
      cards: list of Card objects.
    """
    
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(1, 14):
                card = Card(suit, rank)
                self.cards.append(card)

    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)

    def add_card(self, card):
        """Adds a card to the deck."""
        self.cards.append(card)

    def remove_card(self, card):
        """Removes a card from the deck."""
        self.cards.remove(card)

    def pop_card(self, i=-1):
        """Removes and returns a card from the deck.

        i: index of the card to pop; by default, pops the last card.
        """
        return self.cards.pop(i)

    def shuffle(self):
        """Shuffles the cards in this deck."""
        random.shuffle(self.cards)

    def sort(self):
        """Sorts the cards in ascending order."""
        self.cards.sort()

    def move_cards(self, hand, num):
        """Moves the given number of cards from the deck into the Hand.

        hand: destination Hand object
        num: integer number of cards to move
        """
        for i in range(num):
            hand.add_card(self.pop_card())
    
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        deal = []
        
        for hands in range (num_hands):
            self.shuffle()
            new_hand = Hand('hand ' + str(hands + 1))
            self.move_cards(new_hand, num_cards)
            deal.append(new_hand)

        return deal   # TODO: Implement (Exercise 3)

class Hand(Deck):
    """Represents a hand of playing cards."""
    
    def __init__(self, label=''):
        self.cards = []
        self.label = label


def find_defining_class(obj, method_name):
    """Finds and returns the class object that will provide 
    the definition of method_name (as a string) if it is
    invoked on obj.

    obj: any python object
    method_name: string method name
    """
    for ty in type(obj).mro():
        if method_name in ty.__dict__:
            return ty
    return None


if __name__ == '__main__':
    # Example of using find_defining_class to probe object inheritance
    test_hand = Hand()
    print "'shuffle' is a method of", find_defining_class(test_hand, 'shuffle')

    # TODO: The following test code won't fully work until you define the deal_hands Deck method
    deck = Deck()
    deck.shuffle()
    hands = deck.deal_hands(4, 5)
    for i, hand in enumerate(hands):
        print "Hand {}:".format(i)
        hand.sort()
        print hand
"""This module contains code from
Think Python by <NAME>
http://thinkpython.com

Copyright 2012 <NAME>
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html

"""

import random


class Card(object):
    """Represents a standard playing card.
    
    Attributes:
      suit: integer 0-3
      rank: integer 1-13
    """

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]

    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        """Returns a human-readable string representation."""
        return "{0} of {1}".format(Card.rank_names[self.rank],
                                   Card.suit_names[self.suit])

    def __cmp__(self, other):
        """Compares this card to other, first by suit, then rank.

        Returns a positive number if this > other; negative if other > this;
        and 0 if they are equivalent.
        """
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return cmp(t1, t2)


class Deck(object):
    """Represents a deck of cards.

    Attributes:
      cards: list of Card objects.
    """
    
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(1, 14):
                card = Card(suit, rank)
                self.cards.append(card)

    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)

    def add_card(self, card):
        """Adds a card to the deck."""
        self.cards.append(card)

    def remove_card(self, card):
        """Removes a card from the deck."""
        self.cards.remove(card)

    def pop_card(self, i=-1):
        """Removes and returns a card from the deck.

        i: index of the card to pop; by default, pops the last card.
        """
        return self.cards.pop(i)

    def shuffle(self):
        """Shuffles the cards in this deck."""
        random.shuffle(self.cards)

    def sort(self):
        """Sorts the cards in ascending order."""
        self.cards.sort()

    def move_cards(self, hand, num):
        """Moves the given number of cards from the deck into the Hand.

        hand: destination Hand object
        num: integer number of cards to move
        """
        for i in range(num):
            hand.add_card(self.pop_card())
    
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        hand = Hand()
        
        for i in range(num_hands):
            self.cards.append(self.move_cards(hand, num_cards))
        return self.cards   # TODO: Implement (Exercise 3)


class Hand(Deck):
    """Represents a hand of playing cards."""
    def __init__(self, label=''):
        self.cards = []
        self.label = label


def find_defining_class(obj, method_name):
    """Finds and returns the class object that will provide 
    the definition of method_name (as a string) if it is
    invoked on obj.

    obj: any python object
    method_name: string method name
    """
    for ty in type(obj).mro():
        if method_name in ty.__dict__:
            return ty
    return None


if __name__ == '__main__':
    # Example of using find_defining_class to probe object inheritance
    test_hand = Hand()
    print "'shuffle' is a method of", find_defining_class(test_hand, 'shuffle')

    deck = Deck()
    deck.shuffle()
    hands = deck.deal_hands(4, 5)
    print hands
    for i, hand in enumerate(hands):
        print "Hand {}:".format(i)
        #hand.sort()
        print hand<jupyter_output>'shuffle' is a method of <class '__main__.Deck'>
[<__main__.Card object at 0x7f431c15c250>, <__main__.Card object at 0x7f431c156b50>, <__main__.Card object at 0x7f431c156850>, <__main__.Card object at 0x7f431c156d50>, <__main__.Card object at 0x7f431c156c10>, <__main__.Card object at 0x7f431c156890>, <__main__.Card object at 0x7f431c1567d0>, <__main__.Card object at 0x7f431c15c150>, <__main__.Card object at 0x7f431c156f50>, <__main__.Card object at 0x7f431c156d90>, <__main__.Card object at 0x7f431c15c190>, <__main__.Card object at 0x7f431c15c2d0>, <__main__.Card object at 0x7f431c156e10>, <__main__.Card object at 0x7f431c1566d0>, <__main__.Card object at 0x7f431c156650>, <__main__.Card object at 0x7f431c156dd0>, <__main__.Card object at 0x7f431c1569d0>, <__main__.Card object at 0x7f431c15c290>, <__main__.Card object at 0x7f431c156d10>, <__main__.Card object at 0x7f431c156b10>, <__main__.Card object at 0x7f431c156bd0>, <__main__.Card object at 0x7f431c156a50>, <__main__.Card object at 0[...]<jupyter_text>Exercise 6  **Note:** Jupyter notebooks can access code in other cells, so as long as you have run the cell above then the `PokerHand` class above will be able to reference your previous definition of the `Hand` class.The following are the possible hands in poker, in increasing order of value (and decreasing order of probability): 1. **pair:** two cards with the same rank  2. **two pair:** two pairs of cards with the same rank  3. **three of a kind:** three cards with the same rank  4. **straight:** five cards with ranks in sequence (aces can be high or low, so Ace-2-3-4-5 is a straight and so is 10-Jack-Queen-King-Ace, but Queen-King-Ace-2-3 is not.)  5. **flush:** five cards with the same suit  6. **full house:** three cards with one rank, two cards with another  7. **four of a kind:** four cards with the same rank  8. **straight flush:** five cards in sequence (as defined above) and with the same suit The goal of these exercises is to estimate the probability of drawing these various hands. You can go as far as you like with this exercise, but you should at least attempt parts 2 and 3. 1. If you run the code below, it deals seven 7-card poker hands and checks to see if any of them contains a flush. Read this code carefully before you go on. 2. Add methods to `PokerHand` named `has_pair`, `has_twopair`, etc. that return `True` or `False` according to whether or not the hand meets the relevant criteria. Your code should work correctly for “hands” that contain any number of cards (although 5 and 7 are the most common sizes). 3. Write a method named `classify` that figures out the highest-value classification for a hand and sets the label attribute accordingly. For example, a 7-card hand might contain a flush and a pair; it should be labeled “flush”. 4. When you are convinced that your classification methods are working, the next step is to estimate the probabilities of the various hands. Write a function below that shuffles a deck of cards, divides it into hands, classifies the hands, and counts the number of times various classifications appear. 5. Print a table of the classifications and their probabilities. Run your program with larger and larger numbers of hands until the output values converge to a reasonable degree of accuracy. Compare your results to the values at http://en.wikipedia.org/wiki/Hand_rankings.Allen's solution: http://thinkpython.com/code/PokerHandSoln.py.<jupyter_code>class PokerHand(Hand):

    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1
            
    def num_hist(self):
        self.nums = []
        for card in self.cards:
            self.nums[card.rank] = self.nums.get(card.rank, 0) + 1
        

    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    
    def has_pair(self):
        self.num_hist()
        for val in self.nums.values():
            if val>=2:
                return True
        return False
    
    def has_twopair(self):
        self.num_hist()
        twocount = 0
        for val in self.nums.values():
            if val>=2:
                twocount = twocount + 1
            if twocount == 2:
                return True
        return False
    
    def has_triple(self):
        self.num_hist()
        for val in self.nums.values():
            if val>=3:
                return True
        return False
    
    def has_four(self):
        self.num_hist()
        for val in self.nums.values():
            if val>=4:
                return True
        return False
    
    def has_straight(self):
        self.sort()
        for i in range(len(self.cards)-5):
            if self.cards[i].rank+4 == self.cards[i+5].rank:
                return True
        return False
    
    def has_straightflush(self):
        self.sort()
        return self.has_straight() and self.has_flush()
            
    
    def has_fullhouse(self):
        self.num_hist()
        hastriple,haspair = 0,0
        for val in self.nums.values():
            if val>=3:
                hastriple = True
            if val == 2:
                haspair = True
        return hastriple and haspair
    
    def classify(self):
        if self.has_straightflush():
            return 'straight flush'
        elif self.has_four():
            return 'four of a kind'
        elif self.has_fullhouse():
            return 'full house'
        elif self.has_flush():
            return 'flush'
        elif self.has_straight():
            return 'straight'
        elif self.has_triple():
            return 'three of a kind'
        elif self.twopair():
            return 'two pairs'
        elif self.pair():
            return 'pair'
        else:
            return 'high card'
        
if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(7):
        print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 7)  
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        hand.sort()
        print hand
        print "Contains flush?", hand.has_flush()
        print ''

class PokerHand(Hand):
    
    funcList = ['has_pair', 'has_twopair', 'has_threekind', 'has_straight', 
                'has_flush', 'has_fullhouse', 'has_fourkind', 'has_straightflush']
    
    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1

    def rank_hist(self):
        """Builds a histogram of the ranks that appear in the hand.

        Stores the result in attribute ranks.
        """
        self.ranks = {}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1

    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    
    def has_pair(self):
        """
        Document here
        """
        self.rank_hist()
        for val in self.ranks.values():
            if val >= 2:
                return True
        return False
    
    def has_twopair(self):
        """
        Document here
        """
        self.rank_hist()
        numPairs = 0
        for val in self.ranks.values():
            if val >= 2:
                numPairs += 1
        if numPairs >= 2:
            return True
        return False
    
    def has_threekind(self):
        """
        Document here
        """
        self.rank_hist()
        for val in self.ranks.values():
            if val >= 3:
                return True
        return False   
    
    def has_fourkind(self):
        """
        Document here
        """
        self.rank_hist()
        for val in self.ranks.values():
            if val >= 4:
                return True
        return False  
    
    def has_fullhouse(self):
        """
        Document here
        """
        self.rank_hist()
        numPairs = 0
        numTrips = 0
        for val in self.ranks.values():
            if val >= 3:
                numTrips += 1
            if val == 2:
                numPairs += 1         
        if (numTrips >=2) or (numTrips == 1 and numPairs >= 1):
            return True
        return False
    
    def has_straight(self):
        self.rank_hist()
        rankList = sorted(list(self.ranks))
        for i in xrange(len(rankList) - 5): #Length of straight
            for j in xrange(5):
                if rankList[i + j] != rankList[i] + j:
                    break
            else: return True
        return False 

    def has_straightflush(self):
        #Taken from Allan. Banging head agaist wall, good implementation.
        d = {}
        for c in self.cards:
            d.setdefault(c.suit, PokerHand()).add_card(c) 
        for hand in d.values():
            if len(hand.cards) < 5:
                continue            
            if hand.has_straight():
                return True
        return False
    
    def classify(self):
        label = 'has_none'
        for func in self.funcList:
            f = getattr(self, func)
            if f():
                label = func
        self.Label = label[4:]
            
def test(deck):
    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(7):
        print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 7)  
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        hand.sort()
        hand.classify()
        print hand
        print "Contains flush?", hand.has_flush()
        print "Contains pair?", hand.has_pair()
        print "Contains two pair?", hand.has_twopair()
        print "Contains three of a kind?", hand.has_threekind()
        print "Contains four of a kind?", hand.has_fourkind()
        print "Contains full house?", hand.has_fullhouse()
        print 'Contains straight?', hand.has_straight()
        print 'Contains straightflush?', hand.has_straightflush()
        print 'Label:', hand.Label
        print ''   

def probCalc(deck, numTrials):
    probs = {}
    for i in xrange(numTrials):
        hand = PokerHand()
        deck.move_cards(hand, 7)
        hand.sort()
        hand.classify()
        probs[hand.Label] = probs.get(hand.Label, 0) + 1
        hand.move_cards(deck, 7)
        deck.shuffle()
    for label in probs:
        print label + ':', probs[label]/float(numTrials) * 100 , "%"
    

if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()
    #test(deck)
    probCalc(deck,5000)
    
    
    

class PokerHand(Hand):
    
    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1
#         print self.suits

    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    
    def rank_hist(self):
        """Builds a histogram of the ranks that appear in the hand.

        Stores the result in attribute ranks.
        """
        self.ranks = {}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1
#         print self.ranks
    
    def has_pair(self):
        """Returns True if hand has a pair or more, False otherwise.
        Note that his works correctly for hands with any number of cards."""
        self.rank_hist()
        for val in self.ranks.values():
            if val >= 2:
                return True
        return False
        
    def has_twopair(self):
        """Returns True if hand has two pairs or more, False otherwise.
        Note that his works correctly for hands with any number of cards."""
        self.rank_hist()
        pairs = 0
        for val in self.ranks.values():
            if val >=2:
                pairs += 1
        if pairs >= 2:
            return True
        return False
    
    def has_threepair(self):
        """Returns True if hand has three pairs or more, False otherwise.
        Note that his works correctly for hands with any number of cards."""
        self.rank_hist()
        pairs = 0
        for val in self.ranks.values():
            if val >=2:
                pairs += 1
        if pairs >= 3:
            return True
        return False
    
    def classify(self):
        self.label = ""
        
        if self.has_flush() == True:
            self.label = "Flush"
        elif self.has_threepair() == True:
            self.label = "Three Pairs"
        elif self.has_twopair() == True:
            self.label = "Two Pairs"
        elif self.has_pair() == True:
            self.label = "One Pair"
        else:
            self.label = "No good hand!"
        return self.label

if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(7):
        print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 7)  
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        hand.sort()
#         print hand.rank_hist()
        print "Contains a pair?", hand.has_pair()
        print "Contains two pairs?", hand.has_twopair()
        print "Contains three pairs?", hand.has_threepair()
        print "Contains flush?", hand.has_flush()
        print "Best hand?", hand.classify()
#         print hand.suit_hist()
#         print hand
        print ''

class PokerHand(Hand):

    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1
            
    def val_hist(self):
        """Builds a histogram of the values of the cards that appear in the hand.Stores the result in attribute numbers."""
        self.vals = {}
        for card in self.cards:
            self.vals[card.rank] = self.rank.get(card.suit, 0) + 1

        def check_sets(self, *t): ## from the solution provided, couldn't figure that out
        """Checks whether self.sets contains sets that are
        at least as big as the requirements in t.

        t: list of int
        """
            for need, have in zip(t, self.sets):
                if need > have: return False
            return True



    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    
    
    
    def has_full_house(self):
            return self.check_sets(3,2)




    
    def has_pair(self):
                """Returns True if the hand has a pair, False otherwise. """
        return self.check_sets(2)

    
    def has_three_kind(self):
        return self.check_sets (3)
    
    def has_four_kind(self):
        return self.check_sets (4)


    
    def has_two_pair(self):
        #self.suit_hist()
        #counter = 0
        #for val in self.suits.values():
         #   if val = 2:#
                #counter = counter + 1
            #if counter = 2:
            #    return True
            #else: 
             #   return False
        return self.check_sets(2,2)


            
    def has_straight(self):
        # from solution
        ranks = self.ranks.copy()
        ranks[14] = ranks.get(1, 0)

        # see if we have 5 in a row
        return self.in_a_row(ranks, 5)
        
        
        
    def has_straight_flush(self):
        if has_straight(self) == True:
            if has_flush(self) == True:
                return True
        return False

    def classify(self):
        
        """ Figures out what is the highest value hand within the hand that is analyzing ex flush vs straight flush"""
        if has_straight_flush(self) == True:
            return "Straight Flush"
        elif has_four_kind(self) == True:
            return "Four of a Kind"
        elif has_full_house(self) ==True:
            return "Full House"
        elif has_flush(self) == True:
            return "Flush"
        elif has_straight(self) == True:
            return "Straight"
        elif has_three_kind(self) == True:
            return "Three of a Kind"
        elif has_two_pair(self) == True:
            return "Two Pairs"
        elif has_pair(self) == True:
            return "Pair"


if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(7):
        print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 7)  
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        hand.sort()
        print hand
        print "Contains flush?", hand.has_flush()
        print ''

class PokerHand(Hand):

    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1

    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    
    def rank_hist(self):
        """Builds a histogram of the numbers that appear in the hand
        Stores the result in attribute ranks. 
        """
        self.ranks = {}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1 
            
    def has_pair(self):
        self.rank_hist()
        for val in self.ranks.values():
            if val == 2:
                return True
        return False
    
    def has_twopair(self):
        self.rank_hist()
        num_pairs = 0
        for val in self.ranks.values():
            if val == 2:
                num_pairs += 1
        
        if num_pairs == 2:
            return True
        return False
    
    def has_three_of_kind(self):
        self.rank_hist()
        for val in self.ranks.values():
            if val == 3:
                return True
        return False
    
    def has_four_of_kind(self):
        self.rank_hist()
        for val in self.ranks.values():
            if val == 4:
                return True
        return False
    
    def has_full_house(self):
        self.rank_hist()
        three = self.has_three_of_kind()
        two = self.has_pair()
        if two and three:
            return True
        return False
        
    def has_straight(self):
        ranks = self.ranks.copy()
        try:
            ranks[14] = ranks[1] #puts aces at both beginning and end
        except:
            ranks[14] = 0
        count = 0
        for i in range(1, 15):
            if ranks.get(i, 0):
                count += 1
                if count == 5: 
                    return True
            else:
                count = 0
        return False
            

if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(7):
        print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 12)  
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        hand.sort()
        print hand
        print "Contains flush?", hand.has_flush()
        print "Contains pair?", hand.has_pair()
        print "Containts two pair?", hand.has_twopair()
        print "Contains three of a kind?", hand.has_three_of_kind()
        print "Contains four of a kind?", hand.has_four_of_kind()
        print "Contains full house?", hand.has_full_house()
        print "Contains straight?", hand.has_straight()
        print ''

class PokerHand(Hand):

    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1
            
    def rank_hist(self):
        """
            Builds a histogram of the ranks that appear in the hand. 
            Stores the result in attritube ranks
        """
        self.ranks = {}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1

    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    
    def has_pair(self):
        """
            Returns True if the hand has a pair, False otherwise
        """
        self.rank_hist()
        for val in self.ranks.values():
            if val == 2:
                return True
        return False
    
    def has_twopair(self):
        """
            Returns True if the hand has two or more pairs, False otherwise
        """
        self.rank_hist()
        pair_counter = 0
        for val in self.ranks.values():
            if val == 2:
                pair_counter += 1
        if pair_counter >= 2:
            return True
        return False
    
    def has_trip(self):
        """
            Returns True if the hand has the three of a kind, False otherwise
        """
        self.rank_hist()
        for val in self.ranks.values():
            if val == 3:
                return True
        return False
    
    def has_straight(self):
        """
            Returns True if the hand has the straight, False otherwise
        """
        self.rank_hist()
        for i in range(1,10): #traverse between Ace to 10 which is possible start of the straight
            for j in range(5):
                if i+j in self.ranks.keys():
                    if self.ranks[i+j] < 1:
                        break
                    if j < 4:
                        continue
                    else:
                        return True
                else:
                    break
        return False
    
    def has_fullhouse(self):
        """
            Returns True if the hand has fullhouse, False otherwise
        """
        self.rank_hist()
        has_pair = False
        has_trip = False
        for val in self.ranks.values():
            if val == 2:
                has_pair = True
            if val == 3:
                has_trip = True
        return has_pair and has_trip
    
    def has_quad(self):
        """
            Returns True if the hand has four of a kind, False otherwise
        """
        self.rank_hist()
        for val in self.ranks.values():
            if val == 4:
                return True
        return False
    
    def has_straight_flush(self):
        """
            Returns True if the hand has the straight, False otherwise
        """
        self.sort()
        if len(self.cards) == 5:
            for j in range(1,5):
                if self.cards[0].suit == self.cards[j].suit and self.cards[0].rank + j == self.cards[j].rank:
                    if j == 4:
                        return True
                    else:
                        continue
                else:
                    break
            return False
        
        for i in range(len(self.cards)-5):
            for j in range(1,5):
                if self.cards[i].suit == self.cards[i+j].suit and self.cards[i].rank + j == self.cards[i+j].rank:
                    if j == 4:
                        return True
                    else:
                        continue
                else:
                    break
        return False
    
    def classify(self):
        if self.has_straight_flush():
            return "Straight Flush"
        elif self.has_quad():
            return "Four of a kind"
        elif self.has_fullhouse():
            return "Full House"
        elif self.has_flush():
            return "Flush"
        elif self.has_straight():
            return "Straight"
        elif self.has_trip():
            return "Three of a kind"
        elif self.has_twopair():
            return "Two pair"
        elif self.has_pair():
            return "Pair"
        else:
            return "High card"

def hand_prob(n):
    """
    Function that splits a deck into 7 hands for n decks (trials). It then maps the occurence of each hand into a dictionary
    """
    prob = {}
    for i in range(n):
        deck = Deck()
        deck.shuffle()
        
        for i in range(7):
            hand = PokerHand()
            deck.move_cards(hand,7)
            prob[hand.classify()] = prob.get(hand.classify(),0) + 1
    for key in prob.keys():
        print key, float(prob[key]) / (n*7)
    #print prob
if __name__ == '__main__':
    """for j in range(1):
        #print "New Deck \n\n"

        # Create a new Deck
        deck = Deck()
        deck.shuffle()

        # Deal the cards and classify the hands. You'll need to add more tests as you create methods
        for i in range(7):
            print "Trial {}:".format(i)
            hand = PokerHand()
            deck.move_cards(hand, 7)  
            # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
            hand.sort()
            print hand
            print hand.classify()
            #print "Contains flush?", hand.has_flush()
            #print "Contains straight_flush?", hand.has_straight_flush()
            #if hand.has_straight_flush():
            #    print hand
            #    print
            print ''
    """
    hand_prob(2000)

class PokerHand(Deck):
    
    def __init__(self, label=''):
        self.cards = []
        self.label = label
        
    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1
    def rank_hist(self):
        """Builds a histogram of the ranks that appear in the hand.

        Stores the result in attribute ranks.
        """
        self.ranks = {}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1
        
    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    
    def num_pairs(self):
        '''Returns the number of pairs a hand has.'''
        pair_counter=0
        for val in self.ranks.values():
            if val>=2:
                pair_counter+=1
        return pair_counter
    
    def has_pairs(self):
        '''Returns True if the hand has a pair, False otherwise.'''
        return self.num_pairs()>=1
        
    def has_twopairs(self):
        '''Returns True if the hand has two pairs, False otherwise.'''
        return self.num_pairs()>=2

    def has_straight(self):
        ranks_list=self.ranks.keys()
        ranks_list.sort()
        if len(ranks_list)>=5:
            for i,rank in enumerate(ranks_list):
                if ranks_list[i:i+5]==[range(rank,rank+5)]:
                    return True 
            else: return False 
        else:
            return False 
        
    def has_three_of_a_kind(self):
        for val in self.ranks.values():
            if val >= 3:
                return True
        return False
    
    def has_four_of_a_kind(self):
        for val in self.ranks.values():
            if val >= 4:
                return True
        return False
        
    def has_full_house(self):
        return 2 in self.ranks.values() and 3 in self.ranks.values()
    
    def has_straight_flush(self):
        return self.has_straight() and self.has_flush()
    
    def classify_hand(self):
        if self.has_straight_flush():
            return 'Staight flush'
        elif self.has_four_of_a_kind():
            return "four of a kind"
        elif self.has_full_house():
            return 'full house'
        elif self.has_flush():
            return 'flush'
        elif self.has_straight():
            return 'staight'
        elif self.has_three_of_a_kind():
            return 'Three of a kind'
        elif self.has_twopairs():
            return 'two pairs'
        elif self.has_pairs():
            return 'pairs'
        
if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()
    
    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(7):
        print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 7)  
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        # because we want to create PokerHand instead of Hand (we can give it class type?)
        hand.sort()
        hand.suit_hist()
        hand.rank_hist()
        print hand
#         print 'Contains pairs?', hand.has_pairs()
#         print 'Contains two pairs?', hand.has_twopairs()
#         print "Contains three of a kind?", hand.has_three_of_a_kind()
#         print 'Contains straight?', hand.has_straight()
#         print "Contains flush?", hand.has_flush()
#         print 'Contains full house?', hand.has_full_house()
#         print "Contains four of a kind?", hand.has_four_of_a_kind()
#         print 'Contains straight flush?', hand.has_straight_flush()
        print 'this hand is', hand.classify_hand()
        

class PokerHand(Hand):

    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1
    
    def rank_hist(self):
        """Builds a histogram of the ranks that appear in the hand.

        Stores the result in attribute suits.
        """
        self.ranks = {}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1

    def has_pair(self):
        """Returns True if hand has a pair, False otherwise.
        
        """
        self.rank_hist()
        for val in self.ranks.values():
            if val == 2:
                return True
        return False
    
    def has_twopair(self):
        """Returns True if hand has2 or more pairs, False otherwise.
        
        """
        self.rank_hist()
        counter = 0
        for val in self.ranks.values():
            if val == 2:
                counter +=1 
        if counter >=2:
            return True
        return False
    
    def has_3ofkind(self):
        """Returns True if hand has 3 cards of same rank, 
        False otherwise.
        """
        self.rank_hist()
        for val in self.ranks.values():
            if val == 3:
                return True
        return False
    
##################    Problem Child    #######################    
    def has_straight(self):
        """Returns True if hand has 5 cards with ranks in sequence, 
        False otherwise.
        """
        self.rank_hist()
        sequence_check = self.ranks.copy()

        #ace is highest and lowest, so append ace to end (no wrapping from high to low!)
        sequence_check[len(sequence_check)] = sequence_check.get(1,0)

        #tracks number of cards in sequence
        counter = 0
        
        #Note: must use 15 (known number) rather than length of sequence_check (6)
        for i in range(1, 15): 
            #if card rank exists at the index will be true
            if sequence_check.get(i,0):
                counter +=1
                if counter == 5:
                    return True
            else:
                counter = 0
        return False
                
##############################################################        
    
    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    
    def has_fullhouse(self):
        """Returns True if the hand has three cards with one rank, 
        two cards with another, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        if self.has_pair() and self.has_3ofkind():
            return True
        return False
    
    def has_4ofkind(self):
        """Returns True if hand has 4 cards of same rank, False otherwise.
        
        """
        self.rank_hist()
        for val in self.ranks.values():
            if val == 4:
                return True
        return False
    
    def has_straightflush(self):
        #there are some loopholes in the following logic for hands > 5
        # gets less accurate the larger the hand is
        if self.has_straight() and self.has_flush():
            return True
        return False
    
    def classify(self):
        
        if self.has_straightflush():
            self.label = 'straight flush'
            return 'straight flush'
        elif self.has_4ofkind():
            self.label = '4 of a kind'
            return '4 of a kind'
        elif self.has_fullhouse():
            self.label = 'full house'
            return 'full house'
        elif self.has_flush():
            self.label = 'flush'
            return 'flush'
        elif self.has_straight():
            self.label = 'straight'
            return 'straight'
        elif self.has_3ofkind():
            self.label = '3 of a kind'
            return '3 of a kind'
        elif self.has_twopair():
            self.label = 'two pairs'
            return 'two pairs'
        elif self.has_pair():
            self.label = 'pair'
            return 'pair'

        
if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(7):
        hand = PokerHand()
        deck.move_cards(hand, 7)  
        print "Trial {}, Type {}:".format(i, hand.classify())
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        # Because it creates a list of hands, and this looks at individual hands. 
        # To use, modify this list to loop through the list of hands,
        # rather than loop through a number of trials that creates a new hand each trial.
        hand.sort()
        print hand
        print "Contains a pair?          ", hand.has_pair()
        print "Contains 2 or more pairs? ", hand.has_twopair()
        print "Contains 3 of a kind?     ", hand.has_3ofkind()
        print "Contains a straight?      ", hand.has_straight()        
        print "Contains a flush?         ", hand.has_flush()
        print "Contains a full house?    ", hand.has_fullhouse()
        print "Contains 4 of a kind?     ", hand.has_4ofkind()
        print "Contains a straight flush?", hand.has_straightflush()
        print ''
    
    
        
#     # Build your own hand (for testing purposes)
#     hand = PokerHand()
#     # suit [0:3] rank [1:13] (0 is None)
#     hand.add_card(Card(0,11))
#     hand.add_card(Card(0,12))
#     hand.add_card(Card(0,13))
#     hand.add_card(Card(0,10))
#     hand.add_card(Card(0,8))
    
#     hand.sort()
#     print hand
#     print "Contains 1 pair?          ", hand.has_pair()
#     print "Contains 2 or more pairs? ", hand.has_twopair()
#     print "Contains 3 of a kind?     ", hand.has_3ofkind()
#     print "Contains a straight?      ", hand.has_straight()        
#     print "Contains a flush?         ", hand.has_flush()
#     print "Contains a full house?    ", hand.has_fullhouse()
#     print "Contains 4 of a kind?     ", hand.has_4ofkind()
#     print "Contains a straight flush?", hand.has_straightflush()
                

class PokerHand(Hand):

    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1

    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False


if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(7):
        print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 7)  
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        hand.sort()
        print hand
        print "Contains flush?", hand.has_flush()
        print ''
class PokerHand(Hand):

    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1
            
    def rank_hist(self):
        """
        Builds a histogram of the ranks that appear in the hand.
        Stores the result in the attribute ranks.
        """
        self.ranks = {}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1

    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    
    def has_onepair(self):
        """
        Returns True if the hand has a pair, and returns False otherwise.
        """
        self.rank_hist()
        for val in self.ranks.values():
            if val > 1:
                return True
        return False
    
    def has_twopair(self):
        """
        Returns True if the hand has a two pair (or more pairs), 
        and returns False otherwise
        """
        self.rank_hist()
        count = 0
        for val in self.ranks.values():
            if val > 1:
                count += 1
        if (count > 2):
            return True
        return False

    def has_three(self):
        """Returns True if the hand has a three of a kind, False otherwise
        """
        self.rank_hist()
        for val in self.ranks.values():
            if val > 2:
                return True
        return False
    
    def has_four(self):
        """
        Returns True if the hand has a four of a kind, False otherwise
        """
        self.rank_hist()
        for val in self.ranks.values():
            if val >= 4:
                return True
        return False
    
    def has_straight(self):
        """Returns True if the hand has a straight, False otherwise
        """
        self.rank_hist()
        for x in self.ranks:
            count = 0
            for i in range(5):
                if x + i in self.ranks.keys():
                    count += 1
            if count > 4:
                return True
        return False
    
    def has_fullhouse(self):
        """Returns True if the hand has a full house, False otherwise
        """
        if self.has_three() and self.has_twopair():
            return True
        return False
        
    def has_straightflush(self):
        """Returns True if the hand has a straight flush, False otherwise
        """
        self.sort()
        for i in range(len(self.cards)):
            count = 0
            rank = self.cards[i].rank
            suit = self.cards[i].suit
            for j in range(1, 5):
                if i + j < len(self.cards):
                    rank2 = self.cards[i + j].rank
                    suit2 = self.cards[i + j].suit 
                    if rank + j == rank2 and suit == suit2:
                        count += 1
            if count >= 4:
                return True
        return False
        
    def classify(self):
        if(self.has_straightflush()):
            return "Straight Flush"
        elif(self.has_four()):
            return "Four of a Kind"
        elif(self.has_fullhouse()):
            return "Full House"
        elif(self.has_flush()):
            return "Flush"
        elif(self.has_straight()):
            return "Straight"
        elif(self.has_three()):
            return "Three of a kind"
        elif(self.has_twopair()):
            return "Two pairs"
        elif (self.has_onepair()):
            return "Pair"
        return "Nothing ;-;"
          
        
if __name__ == '__main__':
    

    # Create a new Deck
    deck = Deck()
    deck.shuffle()

    # Deal the cards and classify the hands. 
    #You'll need to add more tests as you create methods
    for i in range(7):
        print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 7)  
        # Note: Why aren't we using the deal_hands Deck method you wrote? 
        #How could we modify it so that we _could_ use it?
        hand.sort()
        print hand
        print "Contains flush?", hand.has_flush()
        print "Contains one pair?" , hand.has_onepair()
        print "Contains two pair?", hand.has_twopair()
        print "Contains three of a kind?", hand.has_three()
        print "Contains four of a kind?" , hand.has_four()
        print "Contains a straight?", hand.has_straight()
        print "Contains a full house?", hand.has_fullhouse()
        print "Contains a straight flush?", hand.has_straightflush()
        
        print "Classify", hand.classify()


class PokerHand(Hand):

    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1
            
    def rank_hist(self):
        """Builds a histogram of the ranks that appear in the hand.
        
        Stores the reult in an attribute ranks.
        """
        self.ranks = {}
        for card in self.cards:
            self.rank[card.rank] = self.rank.get(card.rank, 0) + 1

    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    
    def has_pair(self):
        """Returns True if the hand has a pair, False otherwise"""
        self.rank_hist()
        self.ranks.sort()
        for i in range len(self.ranks):
            if self.ranks[i] == self.ranks[i +1]:
                return True
            return False

    def has_twopair(self):
        """Returns True if the hand has two pairs, False otherwise"""
        self.rank_hist()
        self.ranks.sort()
        pairs = 0
        for i in range len(self.ranks):
            if self.ranks[i] == self.ranks[i +1]:
                pairs += 1
            if pairs >= 2: return True
            return False
        
    def classify(self):
        """Determines the highest-value classification for a hand and sets the label attribute accordingly"""
        values = {1: 'pair', 2: 'two pair', 3: 'three of a kind', 4: 'straight', 5: 'flush', 6: 'full house', 7: 'four of a kind', 8: 'straight flush'}
        hand_value = 0
        
        #checks for each special hand and assigns value to hand_value
        if has_pair() == True
        hand_value = 1
        if has_twopair() == True
        hand_value = 2
        if has_threeofkind() == True
        hand_value = 3
        
        if hand_value in values:
            print values(hand_value)
        

if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(7):
        print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 7)  
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        hand.sort()
        print hand
        print "Contains flush?", hand.has_flush()
        print ''

class PokerHand(Hand):

    def best_combination(self):
        numbers = [0]*14    #the first one is disregarded, because there's no card with rank 0
        suits = {}
        for card in self.cards:
            suits[card.suit] = suits.get(card.suit, 0) + 1
            numbers[card.rank] += 1
        streak = 0
        self.combos = {}
        for rank_number in numbers:
            if rank_number == 0:
                streak = 0
            if rank_number > 0:
                streak += 1
            if rank_number > 1:
                self.combos['Pair'] = self.combos.get('Pair', 0) + 1
            if rank_number > 2:
                self.combos['Three of a Kind'] = self.combos.get('Three of a Kind', 0) + 1
            if rank_number > 3:
                self.combos['Four of a Kind'] = self.combos.get('Four of a Kind', 0) + 1
            if streak > 4:
                self.combos['Straight'] = self.combos.get('Straight', 0) + 1
                streak = 0
        for suit_number in suits.values():
            if suit_number > 4:
                self.combos['Flush'] = self.combos.get('Flush', 0) + 1
        best_hands = [lambda a: 'Flush' in a and 'Straight' in a, lambda a: 'Four of a Kind' in a, 
                     lambda a: 'Three of a Kind' in a and 'Pair' in a, lambda a: 'Flush' in a, lambda a: 'Straight' in a,
                     lambda a: 'Three of a Kind' in a, lambda a: 'Pair' in a and a['Pair'] > 1, lambda a: 'Pair' in a]
        hand_names = ['Straight Flush', 'Four of a Kind', 'Full House', 'Flush', 'Straight', 'Three of a Kind', 'Two Pair',
                     'Pair']
        
        for i in range(len(best_hands)):
            if best_hands[i](self.combos):
                return hand_names[i]
        return 'No Combos'

def classify_hands(num_cards):
    deck = Deck()
    deck.shuffle()
    results = {}
    for i in range(52/num_cards):
        hand = PokerHand()
        deck.move_cards(hand, 7)  
        best = hand.best_combination()
        results[best] = results.get(best, 0) + 1
    return results
    
if __name__ == '__main__':
    hand_names = ['Straight Flush', 'Four of a Kind', 'Full House', 'Flush', 'Straight', 'Three of a Kind', 'Two Pair',
                  'Pair']
    hand_names.reverse()
    distribution = classify_hands(7)
    for hand in hand_names:
        print hand, ':', distribution.get(hand, 0)

class PokerHand(Hand):

    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1
    
    def rank_hist(self):
        self.ranks = {}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1

    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False

    def has_pair(self):
        self.ranks = {}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1
            if self.ranks[card.rank] > 1:
                return True
        return False
    
    def has_twopair(self):
        self.ranks = {}
        num_pairs = 0
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1
            if self.ranks[card.rank] == 2:
                num_pairs += 1
        return num_pairs > 1
    def has_threekind(self):
        self.ranks = {}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1
            return self.ranks[card.rank] == 3
    
    def has_straight(self):
        streak = 0
        for card in sorted(self.cards):
            for num in range(1,13):
                if card.rank == num:
                    streak += 1
                else:
                    streak = 0
        return streak >= 5
        
    def has_fullhouse(self):
        self.rank_hist()
        for val in self.ranks.values():
            if val == 3:
                for val in self.ranks.values():
                    if val == 2:
                        return True
        return False
        
        
    def has_fourkind(self):
        self.rank_hist()
        for val in self.ranks.values():
            if val == 4:
                return True
        return False
        
    def has_straightflush(self):
        pass
    
    def classify(self):
        highest = "Nothing here"
        if self.has_pair():
            highest = "pair"
        if self.has_twopair():
            highest = "two pairs"
        if self.has_threekind():
            highest = "three of a kind"
        if self.has_straight():
            highest = "straight"
        if self.has_flush():
            highest = "flush"
        if self.has_fullhouse():
            highest = "full house"
        if self.has_fourkind():
            highest = "four of a kind"
        if self.has_straightflush():
            highest = "straight flush"
        return highest
        
        
    
if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(7):
        print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 7)  
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        hand.sort()
        print hand
        print "Contains flush?", hand.has_flush()
        print "Contains pair?", hand.has_pair()
        print "Contains two pairs?", hand.has_twopair()
        print "Contains three of a kind?", hand.has_threekind()
        print "Contains full house?", hand.has_fullhouse()
        print "Contains four of a kind?", hand.has_fourkind()
        print hand.classify()
        print ''

class PokerHand(Hand):

    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1
    
    def rank_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.ranks = {}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1

    
    def has_pair(self): #two cards with the same rank
        self.rank_hist()
        for i in self.ranks.values():
            if i==2:
                return True
        return False
    
    def has_two_pair(self): #two pairs of cards with the same rank
        self.rank_hist()
        count = 0
        for i in self.ranks.values():
            if i==2:
                count +=1
            if count==2:
                return True
        return False
    
    def has_three_kind(self): #three cards with the same rank
        self.rank_hist()
        for i in self.ranks.values():
            if i==3:
                return True
        return False
    
    def has_straight(self): #five cards with ranks in sequence (aces can be high or low, so Ace-2-3-4-5 is a straight and so is 10-Jack-Queen-King-Ace, but Queen-King-Ace-2-3 is not.)
        self.rank_hist()
        for i in range(0,len(self.ranks.keys())):
            if self.ranks.get(i+1) is not None:
                if self.ranks.get(i+2) is not None:
                    if self.ranks.get(i+3) is not None:
                        if self.ranks.get(i+4) is not None:
                            if self.ranks.get(i+5) is not None:
                                return True
            #elif (self.ranks.get(i+5) - 5 == self.ranks.get(i)):
            #    return True
        return False
    
    def has_flush(self): #five cards with the same suit
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    
    def has_full_house(self): #three cards with one rank, two cards with another
        self.rank_hist()
        for i in self.ranks.values():
            if i==2:
                two = True
            if i==3:
                three = True
        return three and two
    
    def has_four_kind(self): #four cards with the same rank
        self.rank_hist()
        for i in sorted(self.ranks.values()):
            if i==4:
                return True
        return False
    
    def has_straight_flush(self): #five cards in sequence (as defined above) and with the same suit
        return has_straight() and has_flush()
        
    def classify(self):
        if has_straight_flush():
            return 'Straight Flush'
        elif has_four_kind():
            return 'Four of a Kind'
        elif has_full_house():
            return 'Full House'
        elif has_flush():
            return 'Flush'
        elif has_straight():
            return 'Straight'
        elif has_three_kind():
            return 'Three of a Kind'
        elif has_two_pair():
            return 'Two Pair'
        elif has_pair():
            return 'Pair'
        return 'Lol you tried'


if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(7):
        print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 7)  
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        hand.sort()
        print hand
        print hand.classify()
        print ''

class PokerHand(Hand):
    
    labels = ['four_of_a_kind', 'full_house', 'flush',
              'straight', 'three_of_a_kind', 'two_pair', 'pair']

    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1

    def rank_hist(self):
        """Builds a histogram of the ranks that appear in the hand.
        
        Stores the result in attribute ranks.
        """
        self.ranks = {}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1
    
    def has_pair(self):
        """Returns True if the hand has a pair, False otherwise.
        
        Note that this works correctly for hands with at least 2 cards.
        """
        self.rank_hist()
        for val in self.ranks.values():
            if val >= 2:
                return True
        return False
    
    def has_two_pair(self):
        """Returns True if the hand has a two pair, False otherwise.
        
        Note that this works correctly for hands with more than 2 cards.
        """
        self.rank_hist()
        list_multiples = []
        for val in self.ranks.values():
            if val >= 2:
                list_multiples.append(val)
        return len(list_multiples) >= 2
    
    def has_three_of_a_kind(self):
        """Returns True if the hand has three of a kind, False otherwise.
        
        Note that this works correctly for hands with at least three cards.
        """
        self.rank_hist()
        for val in self.ranks.values():
            if val >= 3:
                return True
        return False
    
    def has_straight(self):
        """Returns True if the hand has a straight, False otherwise.
        
        Note that this works correctly for hands with at least five cards.
        """
        self.rank_hist()
        rank_list = list(self.ranks)
        rank_list.sort()
        index = 0
        for i in range(1,14):
            if i in rank_list:
                index += 1
                if index == 5:
                    return True
            else:
                index = 0
        return False
    
    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    
    def has_full_house(self):
        """Returns True if the hand has a full house, False otherwise.
        
        Note that this works correctly for hands with at least five cards.
        """
        self.rank_hist()
        rank_list = list(self.ranks)
        rank_list.sort()
        if self.has_three_of_a_kind():
            rank_list.pop()
            for val in rank_list:
                if val >= 2:
                    return True
        return False
    
    def has_four_of_a_kind(self):
        """Returns True if the hand has a full house, False otherwise.
        
        Note that this works correctly for hands with at least four cards.
        """
        self.rank_hist()
        for val in self.ranks.values():
            if val >= 4:
                return True
        return False
    
    def has_straight_flush(self):
        """Returns True if the hand has a straight flush, False otherwise.
        
        Note that this works correctly for hands with at least five cards.
        """
        pass #TODO: implement this
    
    def classify(self):
        """Classifies the list to the highest-value classification.
        
        Returns a list of classifications.
        """
        self.suit_hist()
        self.rank_hist()
        self.labels = []
        for label in PokerHand.labels:
            x = getattr(self, 'has_' + label)
            if x():
                self.labels.append(label)
        if self.labels:
            self.label = self.labels[0]
            
def chances(self, num_hands, num_cards):
    """Returns a histogram of highest-value classifications.
    """
    pass
            
        
    

if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(7):
        print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 7)  
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        hand.sort()
        hand.classify()
        print hand.label
        print hand
        print "Contains pair?", hand.has_pair()
        print 'Contains two pair?', hand.has_two_pair()
        print 'Contains three_of_a_kind?', hand.has_three_of_a_kind()
        print 'Contains straight?', hand.has_straight()
        print "Contains flush?", hand.has_flush()
        print "Contains full house?", hand.has_full_house()
        print 'Contains four of a kind?', hand.has_four_of_a_kind()
        print 'Contains straight flush?', hand.has_straight_flush()
        print ''

class PokerHand(Hand):

        
    def has_pair(self):
        self.rank_hist()
        for val in self.ranks.values():
            if val > 1:
                return True
        return False
            
    def has_two_pair(self):
        self.rank_hist()
        count = 0
        for val in self.ranks.values():
            if val > 1:
                count += 1
        if count > 1:
            return True
        return False
        
    def has_three_of_a_kind(self):
        self.rank_hist()
        for val in self.ranks.values():
            if val > 2:
                return True
        return False
    
    def has_straight(self):
        self.rank_hist()
        for card in self.ranks:
            count = 0
            for i in range(5):
                if card + i in self.ranks.keys():
                    count += 1
            if count > 4:
                return True
        return False
            
        
    def has_full_house(self):
        if self.has_three_of_a_kind() and self.has_two_pair():
            return True
        return False
    
    def has_four_of_a_kind(self):
        self.rank_hist()
        for val in self.ranks.values():
            if val >= 4:
                return True
        return False
    
    def has_straight_flush(self):
        self.sort()
        for i in range(len(self.cards)):
            count = 0
            rank = self.cards[i].rank
            suit = self.cards[i].suit
            for j in range(1, 5):
                if i + j < len(self.cards):
                    rank2 = self.cards[i + j].rank
                    suit2 = self.cards[i + j].suit 
                    if rank + j == rank2 and suit == suit2:
                        count += 1
            if count >= 4:
                return True
        return False
    
    def rank_hist(self):
        self.ranks = {}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1
            
    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1

    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    
    def classify(self):
        if (self.has_straight_flush()):
            return "Straight Flush"
        elif (self.has_four_of_a_kind()):
            return "Four of a Kind"
        elif (self.has_full_house()):
            return "Full House"
        elif (self.has_flush()):
            return "Flush"
        elif (self.has_straight()):
            return "Straight"
        elif (self.has_three_of_a_kind()):
            return "Three of a kind"
        elif (self.has_two_pair()):
            return "Two pairs"
        elif (self.has_pair()):
            return "Pair"
        return "None"
            
        
def estimate_probabilities(num_tests, num_people, num_cards):
    probabilities = {}
    count = 0
    for i in range(num_tests):
        deck = Deck()
        deck.shuffle()
        hands = deck.deal_poker_hands(num_people, num_cards)
        for hand in hands:
            highest_hand = hand.classify()
            probabilities[highest_hand] = probabilities.get(highest_hand, 0) + 1
            count += 1
    for highest_hand in probabilities:
        probabilities[highest_hand] = 1.0 * probabilities[highest_hand] / count
    return probabilities
    
def print_table(probabilities):
    for hand in probabilities:
        print hand, probabilities[hand] * 100, "%"
    
        
if __name__ == '__main__':
    # Create a new Deck
    probabilities = estimate_probabilities(1000000, 5, 7)
    print_table(probabilities)
    """deck = Deck()
    deck.shuffle()
    hands = deck.deal_poker_hands(5, 7)
    for hand in hands:
        highest_hand = hand.classify()"""
    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    """for i in range(7):
        print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 7)  
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        hand.sort()
        print hand
        print ''"""
    
#work on straight flush + full house
class DeckTest(object):
    """
    A deck created for testing different hands. The Cards are hardcoded with that purpose in mind.
    """
    def __init__(self):
        self.cards = [Card(1,1), Card(1,2),Card(1,3), Card(1,4), Card(1,5)]
    
    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)
    
    def add_card(self, card):
        """Adds a card to the deck."""
        self.cards.append(card)

    def remove_card(self, card):
        """Removes a card from the deck."""
        self.cards.remove(card)

    def pop_card(self, i=-1):
        """Removes and returns a card from the deck.

        i: index of the card to pop; by default, pops the last card.
        """
        return self.cards.pop(i)

    def shuffle(self):
        """Shuffles the cards in this deck."""
        random.shuffle(self.cards)

    def sort(self):
        """Sorts the cards in ascending order."""
        self.cards.sort()

    def move_cards(self, hand, num):
        """Moves the given number of cards from the deck into the Hand.

        hand: destination Hand object
        num: integer number of cards to move
        """
        for i in range(num):
            hand.add_card(self.pop_card())
    



class PokerHand(Hand):

    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1

    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    def rank_hist(self):
        """
        Build a histogram of the ranks that appear in the hand
        
        Stores the result in attribute ranks.
        """
        self.ranks = {}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1
    
    def has_pair(self):
        """
        Returns true if a hand has a pair in it, false otherwise.
        """
        self.rank_hist()
        for val in self.ranks.values():
            if val == 2:
                return True
        return False    
    def has_twopair(self):
        """
        Returns true if a hand has two pairs in it, false otherwise.
        """
        self.rank_hist()
        count = 0
        for val in self.ranks.values():
            if val == 2:
                count +=1
        return count == 2        
    def has_three(self):
        """
        Returns true if a hand has three of a kind, false otherwise.
        """
        self.rank_hist()
        for val in self.ranks.values():
            if val == 3:
                return True
        return False
    def has_straight(self):
        """
        Returns true if a hand has a straight, false otherwise.
        """
        is_consecutive = True
        for i in range(0,len(self.cards)-4, 5):
            for j in range(1,5):
                if((self.cards[i].rank + j) != self.cards[i+j].rank):
                    is_consecutive = False
                    
            if is_consecutive:
                return True
            else:
                is_consecutive = True
        return False        
        
    def has_fullhouse(self):
        """
        Returns true if a hand has a fullhouse, false otherwise.
        """
        self.rank_hist()
        is_three = False
        is_two = False
        for val in self.ranks.values():
            if val == 3:
                is_three = True
            if val == 2:
                is_two = True
        return is_three and is_two        
    def has_four(self):
        """
        Returns true if a hand has a four of a kind, false otherwise.
        """
        self.rank_hist()
        for val in self.ranks.values():
            if val == 4:
                return True
            
        return False    
    def has_straight_flush(self):
        """
        Returns true if a hand has a straight flush, false otherwise.
        """
        is_consecutive = True
        is_flushly = False
        for i in range(0,len(self.cards)-4, 5):
            for j in range(1,5):
                if((self.cards[i].rank + j) != self.cards[i+j].rank):
                    is_consecutive = False
        
            if is_consecutive:
                is_flushly = True
                for j in range(1,5):
                    if(self.cards[i].suit != self.cards[i+j].suit):
                        is_flushly = False
            else:
                is_consecutive = True
        
        return is_flushly
    def classify(self):
        """
        Find the highest order hand, using dictionaries
        """
        
        value = {}
        if self.has_pair():
            value[1] = 'pair'
        if self.has_twopair():
            value[2] = 'two pair'
        if self.has_three():
            value[3] = 'three of a kind'
        if self.has_straight():
            value[4] = 'straight'
        if self.has_fullhouse():
            value[5] = 'full house'
        if self.has_four():
            value[6] = 'four of a kind'    
        if self.has_straight_flush():
            value[7] = 'straight flush'
        max_value = 0    
        for val, type in value.items():
            if val > max_value:
                max_value = val
                
        self.label = value[max_value]        
    


if __name__ == '__main__':
    # Create a new Deck
    deck = DeckTest()
    deck.shuffle()

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(1):
        print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 5)  
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        hand.sort()
        print hand
        print ''
        print "Contains flush?", hand.has_flush()
        print ''
        print "Contains pair?", hand.has_pair()
        print ''
        print "Contains two pair?", hand.has_twopair()
        print ''
        print "Contains three of a kind?", hand.has_three()
        print ''
        print "Contains straight?", hand.has_straight()
        print ''
        print "Contains full house?", hand.has_fullhouse()
        print ''
        print "Contains four of a kind?", hand.has_four()
        print ''
        print "Contains straight flush?", hand.has_straight_flush()
        print ''
        hand.classify()
        print "Highest Value in Hand:",  hand.label

class Hist(dict):
    def __init__(self, seq=[]):
        # Creates a new histogram
        for item in seq:
            self.count(item)
    def count(self, x, f=1):
        # makes counter
        self[x] = self.get(x, 0) + f
        if self[x] == 0:
            del self[x]

class PokerHand(Hand):
    labelsOfHandTypes = ['straightflush', 'fourkind', 'fullhouse', 'flush',
                  'straight', 'threekind', 'twopair', 'pair', 'highcard']
    def hist(self):
        """Builds a histogram of the suits and another for the ranks that appear in the hand.
        Stores the result in .suits and .ranks.
        """
        self.suits = Hist()
        self.ranks = Hist()
        for card in self.cards:
            self.suits.count(card.suit)
            self.ranks.count(card.rank)
        self.sets = self.ranks.values()
        self.sets.sort(reverse=True)

    # I was having troubles and a peer told me to try to make helper functions
    def has_sets(self, *c):
        """Checks for sets of cards"""
        for need, have in zip(c, self.sets):
            if need > have: # if we have less than what is asked for
                return False
        return True
    
    def has_pair(self):
        return self.has_sets(2)
    def has_twopair(self):
        return self.has_sets(2, 2)
    def has_threeOfAKind(self):
        return self.has_sets(3)
    def has_straight(self):
        ranks = self.ranks.copy() # this is a copy so I don't ruin the original
        ranks[14] = ranks.get(1, 0)
        count = 0
        for i in range(1, 15):
            if ranks.get(i, 0):
                count += 1
                if count == 5: return True
            else:
                count = 0
        return False
    def has_flush(self):
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    def has_fullhouse(self):
        return self.has_sets(3, 2)
    def has_fourOfAkind(self):
        return self.has_sets(4)
    def has_straightflush(self):
        s = set()
        for c in self.cards:
            s.add((c.rank, c.suit))
            if c.rank == 1:
                s.add((14, c.suit))
        for suit in range(4): # goes through suits
            count = 0
            for rank in range(1, 15): # goes through the ranks
                if (rank, suit) in s:
                    count += 1
                    if count == 5: return True # looks for 5 in a row
                else:
                    count = 0
        return False
    def has_pair(self):
        self.rank_hist()
        for val in self.rank.values():
            if val >= 2: # if hand has a pair
                return True 
        return False
    

    def has_highcard(self):
        return len(self.cards) # True if a high card
    def classify(self):
        self.make_histograms()
        self.labels = []
        for label in PokerHand.labelsOfHandTypes:
            f = getattr(self, 'has_' + label)
            if f():
                self.labels.append(label)
class PokerDeck(Deck):
    # Tried to make a deal_hands that doesn't give me errors
    def deal_hands(deck, num_cards, num_hands):
        hands = []
        for i in range(num_hands):        
            hand = PokerHand()
            deck.move_cards(hand, num_cards)
            hand.classify()
            hands.append(hand)
        return hands

    
if __name__ == '__main__':
    lhist = Hist()
    # Create a new Deck
    deck = Deck()
    deck.shuffle()
    hands = deck.deal_hands(7, 7)
    for hand in hands:
        for label in hand.labels:
            lhist.count(label)
            print "Contains Pair", hand.has_pair()
            print "Contains Two Pairs", hand.has_twopair()
            print "Contains Three of a Kind", hand.has_threeOfAKind()
            print "Contains Straight", hand.has_straight()
            print "Contains Flush", hand.has_flush()
            print "Contains Full House", hand.has_fullhouse()
            print "Contains Four of a Kind", hand.has_fourOfAkind()
            print "Contains Straight Flush", hand.has_straightflush()

class PokerHand(Hand):

    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1
            
    def rank_hist(self):
        """builds a histogram of the ranks that appear in the hand - stores result in rank attribute"""
        self.ranks={}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1
        

    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    def has_four_of_kind(self):
        "Returns True if the hand has a piar, False otherwise - works for any number of cards"
        self.rank_hist()
        for val in self.ranks.values():
            if val == 4:
                return True
        return False

    def has_three_of_kind(self):
        "Returns True if the hand has a piar, False otherwise - works for any number of cards"
        self.rank_hist()
        for val in self.ranks.values():
            if val == 3:
                return True
        return False
    
    def classify(self):
        if self.has_four_of_kind()==True:
            return "Four of a Kind"
        elif self.has_flush()==True:
            return "Flush"
        elif self.has_three_of_kind()==True:
            return "Three of a Kind"
    
if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(7):
        print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 7)  
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        hand.sort()
        print hand
        print "Contains flush?", hand.has_flush()
        print "Contains three of a kind?", hand.has_three_of_kind()
        print "Contains four of a kind?", hand.has_four_of_kind()

class PokerHand(Hand):
    
    hands = ['High Card', 'Pair', 'Two Pair', 'Three of a Kind', 
             'Straight', 'Flush', 'Full House', 'Four of a Kind', 'Straight Flush']
    
    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1
            
    def rank_hist(self):
        self.ranks = {}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1
            
    def get_multiples(self, number):
        self.rank_hist()
        return [self.ranks[key] for key in self.ranks if self.ranks[key] >= number]
    
    def has_highcard(self):
        return bool(len(self.cards))
            
    def has_pair(self):
        self.rank_hist()
        multiples = self.get_multiples(2)
        if multiples:
            return True
        else:
            return False
    
    def has_twopair(self):
        self.rank_hist()
        multiples = self.get_multiples(2)
        if len(multiples) >= 2:
            return True
        else:
            return False
    
    def has_threeofakind(self):
        self.rank_hist()
        multiples = self.get_multiples(3)
        if multiples:
            return True
        else:
            return False
    
    def has_straight(self):
        self.rank_hist()
        for rank in self.ranks:
            straight = [self.ranks.get(rank + i, 0) for i in range(5)]
            if not (0 in straight):
                return True
        return False

    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False

    def has_fullhouse(self):
        self.rank_hist()
        multiples = self.get_multiples(2)
        if len(multiples) >= 2 and [x for x in multiples if x >= 3]:
            return True
        else:
            return False
    
    def has_fourofakind(self):
        self.rank_hist()
        multiples = self.get_multiples(4)
        if multiples:
            return True
        else:
            return False
    
    def has_straightflush(self):
        if self.has_straight() and self.has_flush():
            return True
        else:
            return False
    
    def classify(self):
        funcs = [getattr(self, 'has_' + hand.replace(' ', '').lower()) for hand in self.hands]
        for i, func in enumerate(funcs):
            if func():
                self.label = self.hands[i]

def label_hist(hands):
    labels = {}
    for hand in hands:
        labels[hand.label] = labels.get(hand.label, 0) + 1
                
if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()
    hands = deck.deal_hands(7, 7)

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(len(hands)):
        print "Trial {}:".format(i)
        hand = hands[i]
        hand.sort()
        hand.classify()
        print hand
        print 'Best Hand: ' + hand.label
#         print "Contains flush?", hand.has_flush()
#         print "Contains pair?", hand.has_pair()
#         print "Contains two pair?", hand.has_twopair()
#         print "Contains three of a kind?", hand.has_threeofakind()
#         print "Contains straight?", hand.has_straight()
#         print "Contains full house?", hand.has_fullhouse()
#         print hand.get_multiples(1)
        print ''
"""This module contains code from
Think Python by <NAME>
http://thinkpython.com

Copyright 2012 <NAME>
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html

"""

import random

class Card(object):
    """Represents a standard playing card.
    
    Attributes:
      suit: integer 0-3
      rank: integer 1-13
    """

    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    rank_names = [None, "Ace", "2", "3", "4", "5", "6", "7", 
              "8", "9", "10", "Jack", "Queen", "King"]

    def __init__(self, suit=0, rank=2):
        self.suit = suit
        self.rank = rank

    def __str__(self):
        """Returns a human-readable string representation."""
        return "{0} of {1}".format(Card.rank_names[self.rank],
                                   Card.suit_names[self.suit])

    def __cmp__(self, other):
        """Compares this card to other, first by suit, then rank.

        Returns a positive number if this > other; negative if other > this;
        and 0 if they are equivalent.
        """
        t1 = self.suit, self.rank
        t2 = other.suit, other.rank
        return cmp(t1, t2)


class Deck(object):
    """Represents a deck of cards.

    Attributes:
      cards: list of Card objects.
    """
    
    def __init__(self):
        self.cards = []
        for suit in range(4):
            for rank in range(1, 14):
                card = Card(suit, rank)
                self.cards.append(card)

    def __str__(self):
        res = []
        for card in self.cards:
            res.append(str(card))
        return '\n'.join(res)

    def add_card(self, card):
        """Adds a card to the deck."""
        self.cards.append(card)

    def remove_card(self, card):
        """Removes a card from the deck."""
        self.cards.remove(card)

    def pop_card(self, i=-1):
        """Removes and returns a card from the deck.

        i: index of the card to pop; by default, pops the last card.
        """
        return self.cards.pop(i)

    def shuffle(self):
        """Shuffles the cards in this deck."""
        random.shuffle(self.cards)

    def sort(self):
        """Sorts the cards in ascending order."""
        self.cards.sort()

    def move_cards(self, hand, num):
        """Moves the given number of cards from the deck into the Hand.

        hand: destination Hand object
        num: integer number of cards to move
        """
        for i in range(num):
            hand.add_card(self.pop_card())
    
    def deal_hands(self, num_hands, num_cards):
        """Return a list of new Hand objects dealt from the Deck.
        
        num_hands: number of Hands to return
        num_cards: number of Cards per Hand
        """
        hand_list = []
        for item in range(num_hands):
            hand = Hand()
            deck.move_cards(hand, num_cards)
            hand_list.append(hand)
            # print hand, "\n"
        return hand_list


class Hand(Deck):
    """Represents a hand of playing cards."""
    
    def __init__(self, label=''):
        self.cards = []
        self.label = label


def find_defining_class(obj, method_name):
    """Finds and returns the class object that will provide 
    the definition of method_name (as a string) if it is
    invoked on obj.

    obj: any python object
    method_name: string method name
    """
    for ty in type(obj).mro():
        if method_name in ty.__dict__:
            return ty
    return None



"""!!!!!!!!!!Start of Exercise 2!!!!!!!!!!"""

import sys

class Histogram(dict):

    def __init__(self, listed=[]):
        for item in listed:
            self.count(item)

    def count(self, x, f=1):
        self[x] = self.get(x, 0) + f
        if self[x] == 0:
            del self[x]


class PokerHand(Hand):
    """Represents a poker hand."""

    all_labels = ['straightflush', 'fourkind', 'fullhouse', 'flush',
                  'straight', 'threekind', 'twopair', 'pair', 'highcard']

    def make_histograms(self):
        self.suits = Histogram()
        self.ranks = Histogram()
        
        for c in self.cards:
            self.suits.count(c.suit)
            self.ranks.count(c.rank)

        self.sets = self.ranks.values()
        self.sets.sort(reverse=True)
 
    def has_highcard(self):
        return len(self.cards)
        
    def check_sets(self, *t):
        for need, have in zip(t, self.sets):
            if need > have: return False
        return True

    def has_pair(self):
        return self.check_sets(2)
        
    def has_twopair(self):
        return self.check_sets(2, 2)
        
    def has_threekind(self):
        return self.check_sets(3)
        
    def has_fourkind(self):
        return self.check_sets(4)

    def has_fullhouse(self):
        return self.check_sets(3, 2)

    def has_flush(self):
        for val in self.suits.values():
            if val >= 5:
                return True
        return False

    def has_straight(self):
        ranks = self.ranks.copy()
        ranks[14] = ranks.get(1, 0)
        return self.in_a_row(ranks, 5)

    def in_a_row(self, ranks, n):
        count = 0
        for i in range(1, 15):
            if ranks.get(i, 0):
                count += 1
                if count == 5: return True
            else:
                count = 0
        return False
    
    def has_straightflush(self):
        s = set()
        for c in self.cards:
            s.add((c.rank, c.suit))
            if c.rank == 1:
                s.add((14, c.suit))
        for suit in range(4):
            count = 0
            for rank in range(1, 15):
                if (rank, suit) in s:
                    count += 1
                    if count == 5: return True
                else:
                    count = 0
        return False
                
    def has_straightflush(self):
        d = {}
        for c in self.cards:
            d.setdefault(c.suit, PokerHand()).add_card(c)
        for hand in d.values():
            if len(hand.cards) < 5:
                continue            
            hand.make_histograms()
            if hand.has_straight():
                return True
        return False


    def classify(self):
        self.make_histograms()

        self.labels = []
        for label in PokerHand.all_labels:
            f = getattr(self, 'has_' + label)
            if f():
                self.labels.append(label)


class PokerDeck(Deck):
    """Represents a deck of cards that can deal poker hands."""
    def deal_hands(deck, num_cards=5, num_hands=10):
        hands = []
        for i in range(num_hands):        
            hand = PokerHand()
            deck.move_cards(hand, num_cards)
            hand.classify()
            hands.append(hand)
        return hands


def main(*args):
    lhist = Histogram()

    n = 10000
    for i in range(n):
        if i%1000 == 0:
            print i
            
        deck = PokerDeck()
        deck.shuffle()

        hands = deck.deal_hands(7, 7)
        for hand in hands:
            for label in hand.labels:
                lhist.count(label)
            
    total = 7.0 * n
    print total, 'hands dealt:'

    for label in PokerHand.all_labels:
        freq = lhist.get(label, 0)
        if freq == 0: 
            continue
        p = total / freq
        print '%s happens one time in %.2f' % (label, p)

if __name__ == '__main__':
    main(*sys.argv)


class PokerHand(Hand):

    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1

            
    def rank_hist(self):
        self.ranks = {}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1
            
            
    def has_pair(self):
        """Returns True if the hand has a pair, False otherwise"""
        
        self.rank_hist()
        for val in self.ranks.values():
            if val == 2 or val == 4:
                return True
        return False
            
    def has_twopair(self):
        """Returns True if the hand has two pairs, False otherwise"""
        count = 0
        self.rank_hist()
        for val in self.ranks.values():
            if val == 2:
                count += 1
                if count == 2:
                    return True
        return False
         
    def has_threeofakind(self):
        """Returns True if the hand has a three of a kind, False otherwise"""
        self.rank_hist()
        for val in self.ranks.values():
            if val == 3:
                return True
        return False
    
    def has_straight(self):
        """Returns True if the hand has a straight, False otherwise"""
        self.rank_hist()
        sort_vals = sorted(self.ranks.values())
        for i in sort_vals:
            if 'Ace' in sort_vals[1:-2]:
                return False
            if sort_vals[i] != 1 + sort_vals[i - 1]:
                return False
        return True
        
        
    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    
    def has_fullhouse(self):
        """Returns True if the hand has a full house, False otherwise"""
        if self.has_pair() == True and self.has_threeofakind() == True:
            return True
        return False
        
    def has_fourofakind(self):
        """Returns True if the hand has a four of a kind, False otherwise"""
        self.rank_hist()
        for val in self.ranks.values():
            if val == 4:
                return True
        return False
            
            
    def has_straightflush(self):
        """Returns True if the hand has a straight flush, False otherwise"""
        if self.has_straight == True and self.has_flush == True:
            return True
        return False
    
    def classify(self):
        classification = 'Single'
        if self.has_pair() == True:
            classification = 'Pair'
        if self.has_twopair() == True:
            classification = 'Two Pairs'
        if self.has_threeofakind() == True:
            classification = 'Three of a Kind'
        if self.has_straight() == True:
            classification = 'Straight'
        if self.has_flush() == True:
            classification = 'Flush'
        if self.has_fullhouse() == True:
            classification = 'Full House'
        if self.has_fourofakind() == True:
            classification = 'Four of a Kind'
        if self.has_straightflush() == True:
            classification = 'Straight Flush'
        return classification
            
            
if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(7):
        print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 7)  
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        hand.sort()
        print hand
        print "Classification?", hand.classify()
        print ''

class PokerHand(Hand):

    def suit_and_rank_hist(self):
        """Builds histograms of the suits and ranks that appear in the hand.
        """
        self.suits = {}
        self.rank = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1
            self.rank[card.rank] = self.rank.get(card.rank, 0) + 1

    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_and_rank_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    
    def has_pair(self):
        self.suit_and_rank_hist()
        for val in self.rank.values():
            if val == 2:
                return True
        return False
    
    def has_three_kind(self):
        self.suit_and_rank_hist()
        for val in self.rank.values():
            if val == 3:
                return True
        return False
    
    def has_four_kind(self):
        self.suit_and_rank_hist()
        for val in self.rank.values():
            if val == 4:
                return True
        return False
    
    def has_twopair(self):
        self.suit_and_rank_hist()
        num_of_pairs = 0
        for val in self.rank.values():
            if val == 2:
                num_of_pairs += 1
        
        if num_of_pairs >= 2:
            return True
        return False
    
    def has_fullhouse(self):
        if self.has_three_kind() == True and self.has_pair() == True:
            return True
        return False
    
    def has_straight(self):
        self_count = sum(self.rank.values())
        if max(self.rank.keys()) - min(self.rank.keys())==self_count -1:
            if len(self.rank.keys())==self_count:
                return True
        elif 1 in self.rank.keys():
            if max(self.rank.keys()) - min(self.rank.keys())==self_count -2:
                if len(self.rank.keys())==self_count:
                    return True
        return False
    
    def has_straightflush(self):
        if self.has_straight() == True and self.has_flush() == True:
            return True
        return False
        
    def classify(self):
        classification = 'single'
        
        if self.has_straightflush() == True:
            classification = 'straight flush'  
        if self.has_four_kind() == True:
            classification = 'four of a kind'
        if self.has_fullhouse() == True:
            classification = 'full house'
        if self.has_straight() == True:
            classification = 'straight'
        if self.has_three_kind() == True:
            classification = 'three of a kind'
        if self.has_twopair() == True:
            classification = 'two pairs'
        if self.has_pair() == True:
            classification = 'pair'
            
        hand.label = classification
        return hand.label
        
if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(7):
        print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 7)  
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        hand.sort()
        
        print hand
        print "Contains flush?", hand.has_flush()
        print "Contains a pair?", hand.has_pair()
        print "Contains three of a kind?", hand.has_three_kind()
        print "Contains four of a kind?", hand.has_four_kind()
        print "Contains two pair?", hand.has_twopair()
        print "Contains full house?", hand.has_fullhouse()
        print "Contains straight?", hand.has_straight()
        print "Contains straightflush?", hand.has_straightflush()
        print ''

class PokerHand(Hand):

    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1
            
    def rank_hist(self):
        self.ranks = {}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1

    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    
    def has_pair(self):
        """Returns True if the hand has a pair, False otherwise."""
        self.rank_hist()
        for val in self.ranks.values():
            if val >= 2:
                return True
        return False
    
    def has_twopair(self):
        """Returns True if the hand has twopair, False otherwise."""
        self.rank_hist()
        pairs=0
        for val in self.ranks.values():
            if val >= 2:
                pairs+=1 
        if pairs>=2:
            return True
        return False
    
    def has_three_of_kind(self):
        """Returns True if the hand has twopair, False otherwise."""
        self.rank_hist()
        pairs=0
        for val in self.ranks.values():
            if val >= 3:
                return True
        return False
    
    def has_straight(self):
        self.rank_hist()
        for i in range(1,9):
            straight=[i, i+1, i+2, i+3, i+4]
            test_fail=True
            for rank in straight:
                if rank not in self.ranks.values():
                    test_fail=False
                    break
            if test_fail:
                return True
        special_straight=[10, 11, 12, 13, 14, 1]
        for rank in special_straight:
            if rank not in self.ranks.values():
                    test_fail=False
                    break
        if test_fail:
                return True
        return False
    
    def has_full_house(self):
        self.rank_hist()
        if self.has_pair:
            for rank in self.ranks.values():
                if rank>=2:
                    for other_rank in self.ranks.values():
                        if other_rank>=3 and rank != other_rank:
                            return True
        return False
    
    def has_four_of_kind(self):
        """Returns True if the hand has 4 of kind, False otherwise."""
        self.rank_hist()
        pairs=0
        for val in self.ranks.values():
            if val >= 4:
                return True
        return False
            
    def has_straight_flush(self):
        """This only works for hands of 5"""
        if self.has_straight() and self.has_flush():
            return True
        return False
    
    def classify(self):
        if self.has_straight_flush():
            self.label='Straight Flush'
        elif self.has_four_of_kind():
            self.label='Four of a Kind'
        elif self.has_full_house():
            self.label='Full House'
        elif self.has_flush():
            self.label='Flush'
        elif self.has_straight():
            self.label='Straight'
        elif self.has_three_of_kind():
            self.label='Three of a Kind'
        elif self.has_twopair():
            self.label='Two Pair'
        elif self.has_pair():
            self.label='Pair'


if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(7):
        print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 7)  
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        hand.sort()
        print hand
        print "Contains flush?", hand.has_flush()
        print ''
        print "Contains pair?", hand.has_pair()
        print ''
        print "Contains two pair?", hand.has_twopair()
        print ''
        print "Contains three of a kind?", hand.has_three_of_kind()
        print ''
        print "Contains straight?", hand.has_straight()
        print ''
        print "Contains full house?", hand.has_full_house()
        print ''
        print "Contains four of a kind?", hand.has_four_of_kind()
        print ''
        print "Contains straight flush?", hand.has_straight_flush()
        print ''
        hand.classify()
        print hand.label
        

class PokerHand(Hand):

    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1

        
    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
        
    def rank_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute ranks.
        """
        self.ranks = {}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1
            
    def has_pair(self):
        """Returns True if the hand has a pair, False otherwise.
      
        Note that this works correctly for hands with more than 2 cards.
        """
        self.rank_hist()
        for val in self.ranks.values():
            if val >= 2:
                return True
        return False
    
    def has_twopair(self):
        """Returns True if the hand has a twopair, False otherwise.
      
        Note that this works correctly for hands with more than 4 cards.
        """
        self.rank_hist()
        num_pairs = 0
        for val in self.ranks.values():
            if val >= 2:
                num_pairs += 1
        
        if num_pairs >= 2:
            return True             
        return False

if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(7):
        print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 7)  
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        hand.sort()
        print hand
        print "Contains flush?", hand.has_flush()
        print "Contains pair?", hand.has_pair()
        print "Contains twopair?", hand.has_twopair()
        print " "

class PokerHand(Hand):

    def ranks_hist(self):
        """Builds a histogram of the ranks that appear in the hand.
        
        Stores the results in attribute ranks."""
        self.ranks={}
        for card in self.cards:
            self.ranks[card.rank]= self.ranks.get(card.rank,0) +1
    
    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1
            
    def has_pair(self):
        """Returns True if the hand has a pair, False if otherwise."""
        self.ranks_hist()
        for val in self.ranks.values():
            if val>=2:
                return True
        return False 
    
    def has_twopair(self):
        """Returns Trus if the hand has a pair, False if otherwise."""
        self.ranks_hist()
        counter= 0
        for val in self.ranks.values():
            if val ==2:
                counter+=1
        if counter>=2:
            return True
        return False 
    
    def has_three_of_a_kind(self):
        """Returns True if the hand a three of a kind, False if otherwise."""
        self.ranks_hist()
        for val in self.ranks.values():
            if val== 3:
                return True
        return False    
        
    def has_straight(self):   
        """Returns True if the hand has a straight, False if otherwise."""
        self.ranks_hist()
        card_ranks=[]
        for key in self.ranks.keys():
            card_ranks.append(key)
        card_ranks.sort()
        
        if card_ranks[0]==1:
            card_ranks.append(14)
        i=0
        count=0
        while i<len(card_ranks)-1:
            if card_ranks[i]== card_ranks[i+1]-1:
                i+=1
                count+=1
                if count==5:
                    return True
            else:
                count=0
                i+=1
        return False                  
            
    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    
    def has_full_house(self):
        """Returns True if the hand has a full house, False if otherwise."""
        if self.has_pair() and self.has_three_of_a_kind():
            return True
        return False            
        
    def has_four_of_a_kind(self):
        """Returns True if the hand has four of a kind, False if otherwise."""
        self.ranks_hist()
        for val in self.ranks.values():
            if val==4:
                return True  
        return False    
            
    def has_straight_flush(self): 
        """Returns True if the hand has a straight flush, False if otherwise."""
        #I know this is wrong.  The other idea I had was really complicated and required creating 4 new dictionaries.
        if self.has_flush and self.has_straight:
            return True
        else:
            return False

    def classify(self):
        """figures out the highest-value classification for a hand and sets the label attribute accordingly"""
        if self.has_straight_flush():
            self.label= 'straight_flush'
            return 'straight_flush'
        if self.has_four_of_a_kind():
            self.label= 'four of a kind'
            return 'four of a kind'
        if self.has_full_house():
            self.label= 'full house'
            return 'full house'
        if self.has_straight():
            self.lable= 'straight'
            return 'straight'
        if self.has_three_of_a_kind():
            self.label= 'three of a kind'
            return 'three of a kind'
        if self.has_twopair():
            self.label='two pairs'
            return 'two pairs'
        if self.has_pair():
            self.label= 'pair'
            return 'pair'   

if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(7):
        print "Trial {}, Class {}:".format(i, hand.classify())
#         print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 7)  
#         # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        hand.sort()
        print hand
        print "Contains pair?", hand.has_pair()
        print "Contains two pairs?", hand.has_twopair()
        print "Contains three of a kind?", hand.has_three_of_a_kind()
        print "Contains flush?", hand.has_flush()
        print "Contains straight?", hand.has_straight()
        print "Contains full house?", hand.has_full_house()
        print "Contains four of a kind?", hand.has_four_of_a_kind()        
        print ''

class PokerHand(Hand):
    
    score_types = ['pair', 'twopair', 'threekind', 'straight', 'flush', 
                  'fullhouse', 'fourkind', 'straight_flush']
    
    def make_hists(self):
        self.suit_hist()
        self.rank_hist()
        self.sets = self.ranks.values()
        self.sets.sort(reverse = True)
           
    def sort_by_type(self):
        """ Make a list of lists of the cards in each suit
        """
        # Create a list of lists, where [1] = clubs, [2] = diamonds,
        #  [3] = hearts, and [4] = spades
        cards_by_suit_list = [[],[],[],[]]
        
        for card in self.cards:
            cards_by_suit_list[card.suit].append(card.rank)
            if card.rank == 1:
                cards_by_suit_list[card.suit].append(14)
            #print cards_by_suit_list
        
        for suit_list in cards_by_suit_list:
            suit_list.sort()
            
        return cards_by_suit_list
            

    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1
            
    def rank_hist(self):
        """Builds a histogram of the ranks that appear in the hand.
        
        Stores the result in attribute ranks.
        """
        self.ranks = {}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1

    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    
    def has_pair(self):
        """ Returns True if the hand has a pair of cards with the same rank.
        """
        self.make_hists()
        for val in self.ranks.values():
            if val >=2:
                return True
        return False
    
    def has_twopair(self):
        """ Returns True if the hand has two pairs.
        """
        self.make_hists()
        pairs = 0
        for val in self.ranks.values():
            if val >=2:
                pairs += 1
        if pairs >= 2:
            return True
        return False
    
    def has_threekind(self):
        """ Returns True if the hand has three cards with the same rank.
        """
        self.rank_hist()
        for val in self.ranks.values():
            if val >=3:
                return True
        return False
    
    def has_fourkind(self):
        """ Returns True if the hand has four cards with the same rank.
        """
        self.rank_hist()
        for val in self.ranks.values():
            if val >=4:
                return True
        return False
    
    def has_fullhouse(self):
        if self.has_twopair() and self.has_threekind():
            return True
        return False
    
    def has_straight(self):
        self.make_hists()
        self.sort_by_type()
        ranks = self.ranks.copy()
        ranks[14] = ranks.get(1,0)
        
        return self.in_row(ranks, 5)
    
    def has_straight_flush(self):
        count = 0
        cards_by_suit = self.sort_by_type()
        for i in range(len(cards_by_suit)):
            for rank in range(1, 15):
                if rank in cards_by_suit[i]:
                    count += 1
                    if count == 5:
                        return True
                else:
                    count = 0
        return False
                    
        
    def in_row(self, ranks, num_needed):
        count = 0
        for i in range(1, 15):
            if ranks.get(i, 0):
                count += 1
                if count == num_needed:
                    return True
            else:
                count = 0
        return False

    def classify(self):
        self.labels = ['empty hand']
        for label in self.score_types:
            current_check = getattr(self, "has_" + label)
            if current_check():
                self.labels.append(label)

if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(7):
        print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 7)  
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        hand.sort()
        hand.classify()
        print hand
        print "Contains pair?", hand.has_pair()
        print "Contains two pair?", hand.has_twopair()
        print "Contains three of a kind?", hand.has_threekind()
        print "Contains straight?", hand.has_straight()
        print "Contains flush?", hand.has_flush()
        print "Contains full house?", hand.has_fullhouse()
        print "Contains four of a kind?", hand.has_fourkind()
        print "Contains straight flush?", hand.has_straight_flush()
        print hand.labels[-1]
        print ''

class PokerHand(Hand):

    def suit_hist(self):
        """Builds a histogram of the suits rank that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}  ##dictionary of suits keys, with number of suits as value
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1
    def rank_hist(self):
        """Builds a histogram of the card rank that appear in the hand.

        Stores the result in attribute suits.
        """    
        self.rank = {}
        for card in self.cards:
            self.rank[card.rank] = self.rank.get(card.rank, 0) + 1

    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():   ##for each value, if its greater than 5, return true
            if val >= 5:
                return True
        return False
    def has_pair(self):
        """returns true if the hand has a pair"""
        self.rank_hist()
        for val in self.rank.values():
            if val == 2:
                return True
        return False
    def has_two_pair(self):
        self.rank_hist()
        count = 0
        for val in self.rank.values():
            if val ==2:
                count += 1
        if count >=2:
            return True
        return False
    def has_three_of_a_kind(self):
        self.rank_hist()
        for val in self.rank.values():
            if val >=3:
                return True
        return False
    def has_straight(self):
        self.sort()
        self.group = {}
        j = 0
        for i, card in enumerate(self.cards):
            if i > 0:
                if card.rank == self.cards[i-1].rank + 1:
                    self.group[j] = self.group.get(j, 1) + 1
                else:
                    j +=1
        for val in self.group.values():
            if val >= 5:
                return True
        return False
    
    def has_full_house(self):
        return self.has_three_of_a_kind() and self.has_pair()
    
    def classify(self):
        if self.has_pair():
            self.label = 'pair'
        if self.has_two_pair():
            self.label = 'two pair'
        if self.has_three_of_a_kind():
            self.label = 'three of a kind'
        if self.has_straight():
            self.label = 'straight'
        if self.has_flush():
            self.label = 'flush'
        if self.has_full_house():
            self.label = 'full house'


if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    pair = 0.0
    flush = 0.0
    two_pair = 0.0
    three_of_a_kind = 0.0
    straight = 0.0
    full_house = 0.0
    number_of_decks = 5000
    number_of_cards = 5
    number_of_hands = 7
    for i in range(number_of_decks):
        deck = Deck()
        deck.shuffle()
        for i in range(number_of_hands):
            #print "Trial {}:".format(i)
            hand = PokerHand()
            deck.move_cards(hand, number_of_cards)  
            # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
            hand.sort()
            #print hand
            hand.classify()
            if hand.label == 'pair':
                pair +=1
            if hand.label == 'two pair':
                two_pair +=1
            if hand.label == 'three of a kind':
                three_of_a_kind += 1
            if hand.label == "straight":
                straight +=1 
            if hand.label == "flush":
                flush +=1 
            if hand.label == 'full house':
                full_house +=1
    
    print "pair: ", pair/(number_of_hands*number_of_decks)
    print "two pair: ", two_pair/(number_of_hands*number_of_decks)
    print "three of a kind: ", three_of_a_kind/(number_of_hands*number_of_decks)
    print "straight: ", straight/(number_of_hands*number_of_decks)
    print "flush: ", flush/(number_of_hands*number_of_decks)
    print "full house: ", full_house/(number_of_hands*number_of_decks)
    

class PokerHand(Hand):
    
    def classify(self):
        if self.has_fourofkind():
            return "Four of a Kind"
        elif self.has_fullhouse():
            return "Full House"
        elif self.has_flush():
            return "Flush"
        elif self.has_threeofkind():
            return "Three of a Kind"
        elif self.has_twopair():
            return "Two pair"
        elif self.has_pair():
            return "Pair"
        else:
            return "None"
            

    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1
            
    def rank_hist(self):
        self.ranks = {}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1
    
    def has_pair(self):
        self.rank_hist()
        for rank in self.ranks.values():
            if rank >= 2:
                return True
        return False
        
        
    def has_twopair(self):
        self.rank_hist()
        counter = 0
        for rank in self.ranks.values():
            if rank >= 2:
                counter += 1
        if counter >= 2:
            return True
        return False
                
        
        
    def has_threeofkind(self):
        self.rank_hist()
        for rank in self.ranks.values():
            if rank >= 3:
                return True
        return False
                
        
    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
        
        
    def has_fullhouse(self):
        self.rank_hist()
        condition1 = False
        condition2 = False
        for rank in self.ranks.values():
            if rank >= 3:
                condition1 = True
        for rank in self.ranks.values():
            if rank >= 2:
                condition2 = True
        if condition1 and condition2:
            return True
        return False
        
        
    def has_fourofkind(self):
        self.rank_hist()
        for rank in self.ranks.values():
            if rank >= 4:
                return True
        return False


if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(7):
        print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 7)  
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        hand.sort()
        print hand
        print "Contains flush?", hand.has_flush()
        print "Contains pair?", hand.has_pair()
        print "Contains two pair?", hand.has_twopair()
        print "Contains three of a kind?", hand.has_threeofkind()
        print "Contains full house?", hand.has_fullhouse()
        print "Contains four of a kind?", hand.has_fourofkind()
        print hand.classify()
        print ''
        

class PokerHand(Hand):

    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1
            
    def rank_hist(self):
        """ Builds a histogram of the ranks that appear in the hand
        
        Stores the result in a dictionary"""
        self.ranks = {}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1

    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    
    def has_pair(self):
        """Returns True if the hand has a pair, otherwise false
        Note: now works for any valid hand size"""
        self.rank_hist()
        if len(self.ranks.values()) < len(self.cards):
            return True
        return False
    
    def has_twopair(self):
        """Returns True if the hand has a pair, otherwise false
        Note: now works for any valid hand size"""
        self.rank_hist()
        if len(self.ranks.values()) < len(self.cards) -1:
            return True
        return False
    
    def has_threekind(self):
        """Returns True if the hand has a tripple, otherwise false"""
        self.rank_hist()
        res = False
        for val in self.ranks.values():
            if val >= 3:
                res = True
        return res
    
    def has_straight(self):
        """REturn Treu is the hand has sa strianght."""
        ranks = []
        for card in self.cards:
            ranks.append(card.rank)
        ranks.sort()
        ranksshift= ranks[1:]
        ranks = ranks[:-1]
        def diff(a,b):
            return b-a
        r = map(diff,ranks, ranksshift)
        return  r == [1,1,1,1]
    
    def has_fullhouse(self):
        """Returns true if has fullhouse"""
        return self.has_threekind() and self.has_twopair()
    
    def has_fourkind(self):
        """Returns True if the hand has a 4, otherwise false"""
        self.rank_hist()
        res = False
        for val in self.ranks.values():
            if val >= 4:
                res = True
        return res
    
    def has_straightflush(self):
        """Returns true is has straightflush"""
        return self.has_straight() and self.has_flush()
    
import random
    
if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
#     for i in range(7):
#         print "Trial {}:".format(i)
#         hand = PokerHand()
#         deck.move_cards(hand, 5)  
#         # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
#         # Because my function deals a normal hand not a poker hand
#         hand.sort()
#         print hand
#         print "Contains flush?", hand.has_flush()
#         print "Contains pair?", hand.has_pair()
#         print ''
    pair = PokerHand()
    pair.add_card(Card(0,1))
    pair.add_card(Card(1,1)) #these two are a pair
    notpair = PokerHand()
    notpair.add_card(Card(0,1))
    notpair.add_card(Card(0,2))
    print "Pair!", pair.has_pair()
    print "No Pair!", notpair.has_pair()
    
    twopair = PokerHand()
    pair.move_cards(twopair, 2)
    twopair.add_card(Card(0,4))
    twopair.add_card(Card(1,4))
    print "Two!", twopair.has_twopair()
    print "No!", notpair.has_pair()
    
    three = PokerHand()
    twopair.move_cards(three, 4)
    three.add_card(Card(2,1))
    print "Three!", three.has_threekind()
    print "No!", notpair.has_threekind()
    
    straight = PokerHand()
    for i in range(5):
        straight.add_card(Card(random.randint(0,3),i+1))
    print "Straight", straight.has_straight()
    
    three.add_card(Card(3,1))
    print "Four!", three.has_fourkind()
    
    three.add_card(Card(0,2))
    three.add_card(Card(1,2))
    print "Full", three.has_fullhouse()
    
    straightflush = PokerHand()
    for i in range(5):
        straightflush.add_card(Card(0,i+1))
    print "Straightflush", straightflush.has_straightflush()
    

class PokerHand(Hand):

    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1
            
    def rank_hist(self):
        """
        Builds a histogram of the ranks that appear in the hand
        stores the result in attribute ranks
        """
        self.ranks = {}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.suit, 0) + 1
            
    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    
    def has_pair(self):
        """Returns True if the hand has a pair, else false"""
        self.rank_hist()
        for val in self.ranks.values():
            if val >= 2:
                return True
        return False
    
    def has_twopair(self):
        """Returns True if the hand has two pair, else false"""
        self.rank_hist()
        multiples = [self.ranks[key] for key in self.ranks if self.ranks[key] > 1]
        return len(multiples) > 1
    
    def has_threeofakind(self):
        """Returns True if the hand has three of a kind, else false"""
        self.rank_hist()
        for val in self.ranks.values():
            if val >= 3:
                return True
        return False
    
    def has_straight(self):
        """Returns true if the hand has five cards in ascending rank, else false"""
        self.rank_hist()
        count = 0
        for i in range(1, 14):
            if self.ranks.get(i, 0):
                count += 1
                if count == 5: return True
            else:
                count = 0
        return False
    
    def has_flush(self):
        """Returns true if the hand has five cards with the same suit"""
        self.suit_hist()
        for val in self.suits.values():
            if val > 4: return True
        return False
            
    
    def has_fullhouse(self):
        """Returns true if the hand has three of a kind and a separate pair, else false"""
        self.suit_hist()
        multiples = [self.ranks[key] for key in self.ranks if self.ranks[key] > 1]
        has_three = False
        for i in self.ranks.values():
            if i >= 3:
                has_three = True
        return len(multiples) > 1 and has_three
                
    def has_fourofakind(self):
        """Returns true if the hand has four of the same rank card, else false"""
        self.rank_hist()
        for val in self.ranks.values():
            if val > 3: return True
        return False
    
    def has_straightflush(self):
        """Returns true if the hand has five cards in ascending rank that are in the same suit"""
        return self.has_straight() and self.has_flush()
    
    def classify(self):
        labels = ['pair', 'twopair', 'threeofakind', 'straight', 'flush', 'fullhouse', 'fourofakind', 'straightflush']
        functions = [getattr(self, 'has_' + hand) for hand in labels]
        for i, func in enumerate(functions):
            if func():
                self.label = labels[i]
        
if __name__ == '__main__':
    # Create a new Decksuits
    deck = Deck()
    deck.shuffle()

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(7):
        print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 7)  
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        hand.sort()
        print hand
        print "Contains pair?", hand.has_pair()
        print ''
        print "Contains two pair?", hand.has_twopair()
        print ''
        print "Contains three of a kind?", hand.has_threeofakind()
        print ''
        print "Contains straight?", hand.has_straight()
        print ''
        print "Contains flush?", hand.has_flush()
        print ''
        print "Contains full house?", hand.has_fullhouse()
        print ''
        print "Contains four of a kind?", hand.has_fourofakind()
        print ''
        print "Contains straight flush?", hand.has_straightflush()
        print ''
        hand.classify()
        print hand.label


class PokerHand(Hand):

    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1

    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    
    def rank_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.ranks = {}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1
    
    def has_pair(self):
        
        self.rank_hist()
        for val in self.ranks.values():
            if val >= 2:
                return True
        return False

    def has_twopair(self):
        
        self.rank_hist()
        count = 0
        for val in self.ranks.values():
            if val >= 2:
                count += 1
                if count >= 2:
                    return True
        return False

    def classify(self):
        if self.has_flush() == True:
            return 3#"Woah, you have a flush"
        elif self.has_twopair() == True:
            return 2#"You have two pairs, not too shabby"
        elif self.has_pair() == True:
            return 1#"One pair? You have your work cut out for you"
        else:
            return 0#"I hope you're good at bluffing"

def probability(num):
    deck = Deck()
    deck.shuffle()
    pair = 0
    two_pair = 0
    flush = 0
    for i in range(num):
        hand = PokerHand()
        deck.move_cards(hand,7)
        hand.sort()
        b = hand.classify()
        if b == 3:
            flush += 1
        elif b == 2:
            two_pair += 1
        elif b == 1:
            pair += 1
        else:
            pass
    return 'Flush:', flush/10, 'Two pair:', two_pair/10, 'Pair:', pair/10

#I don't understand why it's not working -- it doesn't work as a func, but
#it works when it's not in a function

#if __name__ == '__main__':
     # Create a new Deck
    #deck = Deck()
    #deck.shuffle()
    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    #for i in range(3):
        #print "Trial {}:".format(i)
        #hand = PokerHand()
        #deck.move_cards(hand, 7)  
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        #hand.sort()
        #print hand
        #print "Contains flush?", hand.has_flush()
        #print "Contains pair?", hand.has_pair()
        #print "Contains two pairs?", hand.has_twopair()
        #print hand.classify()
        #print ''
        
print probability(10)

class PokerHand(Hand):
    
    def classify(self):
        """
        Sets label for highest-value classification of the hand.
        """
        if self.has_straight_flush():
            return 'Highest-value: Straight flush'
        elif self.has_four_kind():
            return 'Highest-value: Four of a kind'
        elif self.has_full():
            return 'Highest-value: Full house'
        elif self.has_flush():
            return 'Highest-value: Flush'
        elif self.has_straight():
            return 'Highest-value: Straight'
        elif self.has_three_kind():
            return 'Highest-value: Three of a kind'
        elif self.has_twopair():
            return 'Highest-value: Two pair'
        elif self.has_pair():
            return 'Highest-value: Pair'

    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1
            
    def rank_hist(self):
        """Builds a histogram of the ranks that appear in the hand.
        """
        self.ranks = {}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1        
            
    def has_pair(self):
        """
        Returns True if the hand has a pair, False otherwise.
        """
        self.rank_hist()
        for x in self.ranks.values():
            if x == 2:
                return True
        return False    
    
    def has_twopair(self):
        """
        Returns True if the hand has a two pair, False otherwise.
        """
        self.rank_hist()
        count = 0
        for x in self.ranks.values():
            if x == 2:
                count += 1
        if count == 2:
            return True
        return False
    
    def has_three_kind(self):
        """ 
        Returns True if the hand has a three of a king, False otherwise.
        """
        self.rank_hist()
        for x in self.ranks.values():
            if x == 3:
                return True
        return False
    
    def has_straight(self):
        """ 
        Returns True if the hand has a straight, False otherwise.
        """
        rank_sort = self.ranks.values()
        rank_sort.sort()
        count = 0
        index = 0
        next_index = index + 1
        for x in rank_sort:
            if rank_sort[next_index] == x + 1:
                index += 1
                count += 1
                if count == 5:
                    return True
            else:
                count = 0
        return False
        
    def has_flush(self):
        """
        Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False 
        
    def has_full(self):
        """
        Returns True if the hand has a full house, False otherwise.
        """
        self.rank_hist()
        count = 0
        for x in self.ranks.values():
            if x == 2:
                for y in self.ranks.values():
                    if y == 3:
                        return True
        return False
        
    def has_four_kind(self):
        """
        Returns True if the hand has a four of a kind, False otherwise.
        """
        self.rank_hist()
        for x in self.ranks.values():
            if x == 4:
                return True
        return False
        
    def has_straight_flush(self):
        """ 
        Returns True if the hand has a straight flush, False otherwise.
        """
        s = set()
        for card in self.cards:
            s.add((card.rank, card.suit))
            if card.rank == 1:
                s.add((14, card.suit))
                
        for suit in range(4):
            count = 0
            for rank in range(1, 15):
                if (rank, suit) in s:
                    count += 1
                    if count == 5: return True
                else:
                    count = 0
        return False
        
if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(7):
        print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 7)  
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        hand.sort()
        print hand
        hands_poker = ['flush', 'pair', 'two pair', 'three of a kind', 'four of a kind', 'straight', 'full house', 'straight_flush']
        methods_poker_hand = [hand.has_flush(), hand.has_pair(), hand.has_twopair(), hand.has_three_kind(), hand.has_four_kind(), hand.has_straight(), hand.has_full(), hand.has_straight_flush()]
        for (card_combo, method) in zip(hands_poker,methods_poker_hand):
            print "Contains %s?" % (card_combo), method
            print ''
        print hand.classify()

class PokerHand(Hand):

    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1
            
    def rank_hist(self):
        """Builds a histogram of the ranks that appear in the hand.

        Stores the result in attribute ranks.
        """
        self.ranks = {}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1
            
    def suit_tup(self):
        """Returns tuples with the frequency of suit occurrance"""
        suit_freq = self.suit_hist().sets()
        freq_suit = []
        for (s, f) in suit_freq:
            return freq_suit.append((f, s))
      
    def rank_tup(self):
        """Returns tuples with the frequency of rank occurrance"""
        rank_freq = self.rank_hist().sets()
        freq_rank = []
        for (r, f) in rank_freq:
            return freq_rank.append((f, r))
            
    def has_pair(self):
        """Returns True if the hand has a pair"""
        self.rank_hist()
        for val in self.ranks.values():
            if val >= 2:
                return True
        return False
            
    def has_4_kind(self):
        """Returns True if the hand has two pairs"""
        self.rank_hist()
        for val in self.ranks.values():
            if val >= 4:
                return True
        return False
            
    def has_3_kind(self):
        """Returns True if there are three of a kind"""
        self.rank_hist()
        for val in self.ranks.values():
            if val >= 3:
                return True
        return False
    
    def has_2_pair(self):
        """Returns True if there are two pairs of cards with the same rank"""
        self.rank_hist()
        for val in range(len(self.ranks.values())):
            if self.ranks.values()[val] >= 2:
                for val2 in self.ranks.values()[val:]:
                    if val2 >= 2:
                        return True
        return False
            
    def has_full_house(self):
        """Returns True if there are three of one rank and two of another"""
        self.rank_hist()
        for val in self.ranks.values():
            if val == 3:
                for val2 in self.ranks.values():
                    if val2 == 2:
                        return True
        return False
    
    def has_straight(straight):
        """I'm not really sure what this is??  WAT?"""
    
    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    
    def has_straight_flush(self):
        """Would return True if there was a flush that was also straight."""
        
        """if self.has_flush():
            if self.has_straight():
                return True
        return False"""
    
    def classify(self):
        """Returns the highest value card grouping for a given trial. Note, this is only valid for the methods I was able to get to work"""
        hand_options = ['full house', 'flush', 'four of a kind','has two pair', 'three of a kind', 'pair']
        hand_results = [self.has_full_house(), self.has_flush(), self.has_4_kind(),self.has_2_pair(), self.has_3_kind(), self.has_pair()]
        for i in range(len(hand_results)):        
            if hand_results[i] == True:
                return hand_options[i]
        
if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(7):
        print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 7)  
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        hand.sort()
        print hand
        #print hand.cards[0].rank
        print "Contains flush?", hand.has_flush()
        print "Contains pair?", hand.has_pair()
        print "Contains four of a kind?", hand.has_4_kind()
        print "Contains three of a kind?", hand.has_3_kind()
        print "Contains full house?", hand.has_full_house()
        print hand.classify()
        print ''
class Hist(dict):
    """A map from each item (x) to its frequency"""
    def __init__(self,seq=[]):
        "Creates a new histogram starting with the items in seq"
        for x in seq:
            self.count(x)
    def count(self, x, f=1):
        "Increments the counter associated with item x"
        self[x] = self.get(x, 0) + f
        if self[x] == 0:
            del self[x]

class PokerHand(Hand):

    def suit_hist(self, suits):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = self.suit_hist()
        
    labels = ['straightflush','fourkind', 'fullhouse', 'flush', 'straight','threekind', 'twopair', 'pair', 'highcard' ]
    def make_histograms(self):
        self.suits = Hist()
        self.ranks = Hist()
        for c in self.cards:
            self.suits.count(c.suit)
            self.sets.count(c.rank)
        self.sets = self.ranks.values()
        self.sets.sort(reverse=True)
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1
    
    def has_highcard(self):
        return len(self.cards)
    
    def check_sets(self,*t):
        for need,have in zip(t, self.sets):
            if need > have: return False
        return True

    def in_a_row(self, ranks, n):
        count = 0
        for i in range(1, 15):
            if ranks.get(i,0):
                count += 1
                if count == 5: return True
            else:
                count = 0
        return False

    def has_pair(self):
        return self.check_sets(2)
    
    def has_twopair(self):
        return self.check_sets(2,2)
    
    def has_threekind(self):
        return self.check_sets(3)
    
    def has_fourkind(self):
        return self.check_sets(4)
    
    def has_fullhouse(self):
        return self.check_sets(3,2)

    def has_flush(self):
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    
    def has_straight(self):
        ranks = self.ranks.copy()
        ranks[14] = ranks.get(1,0)
        return self.in_a_row(ranks,5)
    def has_straightflush(self):
        d = {}
        for c in self.cards:
            d.setdefault(c.suit, PokerHand()).add_card(c)
        for hand in d.values():
            if len(hand.cards) < 5:
                continue
            hand.make_histograms()
            if hand.has_straight():
                return True
        return False
    
    def classify(self):
        self.make_histograms()
        self.labels = []
        for label in PokerHand.labels:
            f = getattr(self, 'has_' + label)
            if f():
                self.labels.append(label)

class PokerDeck(Deck):
    def deal_hands(deck, num_cards=5, num_hands=10):
        hands = []
        for i in range(num_hands):
            hand = PokerHand()
            deck.move_cards(hand, num_cards)
            hand.classify()
            hands.append(hand)
        return hands


if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(7):
        print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 7)  
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        hand.sort()
        print hand
        print "Contains flush?", hand.has_flush()
        print ''

class PokerHand(Hand):

    def suit_hist(self): # Shouldn't this be an attribute?
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1
            
    def classify(self):
        if has_straightflush:
            self.label = 'Straight Flush'
        elif has_quad:
            self.label = 'Four of a Kind'
        elif has_fullhouse:
            self.label = 'Full House'
        elif has_flush:
            self.label = 'Flush'
        elif has_straight:
            self.label = 'Straight'
        elif has_threeofakind:
            self.label = 'Three of a Kind'
        elif has_twopair:
            self.label = 'Two Pairs'
        elif has_pair:
            self.label = 'Pair'

    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    
    def has_pair(self):
        self.suit_hist()
        for val in self.suits.values:
            if val >= 2:
                return True
        return False
    
    def has_twopair(self):
        self.suit_hist()
        pairCount = 0
        for val in self.suits.values:
            if val >= 2:
                pairCount += 1
        if pairCount >= 2:
            return True
        return False
    
    def has_threeofakind(self):
        self.suit_hist()
        for val in self.suits.values:
            if val >= 3:
                return True
        return False
    
    def has_straight(self):
        sorted_cards = self.cards
        sorted_cards.sort()           # Does this change self.cards? I feel like it does
        streak = 0
        for i in range(sorted_cards[:-1]):
            if sorted_cards[i].rank == sorted_cards[i+1].rank + 1 # the next card == this card +1
                streak += 1
            elif sorted_cards[i].rank != sorted_cards[i+1].rank:  # If this card and the next card aren't equal, reset streak
                streak = 0
        return streak >= 5
    
    def has_fullhouse(self):
        self.suit_hist()
        house_part = 0
        #shit code o'clock
        pair_found = False
        triple_found = False
        for val in self.suit.values:
            if val >= 2 and not pair_found:
                house_part += 1
            if val >= 3 and not triple_found:
                house_part += 1
        return house_part >= 2
    
    def has_quad(self): #Here lies sexy(-ish) code
        self.suit_hist()
        for val in self.suits.values:
            if val >= 4:
                return True
        return False
    
    def has_straightflush(self):
        sorted_cards = self.cards
        sorted_cards.sort()           # Does this change self.cards? I feel like it does
        superSorted = []
        for i in range(4):
            suit_list = []
            for card in sorted_cards:
                if card.suit == i:
                    suit_list.append(card)
            super_sorted.append(suit_list)
                
        for suit in super_sorted:
            if len(suit) >= 5:
                for i in range(sorted_cards[:-1]):
                    if suit[i].rank == suit[i+1].rank + 1 # the next card == this card +1
                        streak += 1
                    elif sorted_cards[i].rank != sorted_cards[i+1].rank:  # If this card and the next card aren't equal, reset streak
                        streak = 0
            if streak >= 5:
                return True
        return False
                


if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(7):
        print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 7)  
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        hand.sort()
        print hand
        print "Contains flush?", hand.has_flush()
        print ''

class PokerHand(Hand):   
    def classify(self):
        if self.has_straightflush():
            print "Straight Flush"
        elif self.has_fourofakind():
            print "Four of a Kind"
        elif self.has_fullhouse():
            print "Full House"
        elif self.has_flush():
            print "Flush"
        elif self.has_straight():
            print "Straight"
        elif self.has_threeofakind():
            print "Three of a kind"
        elif self.has_twopair():
            print "Two Pair"
        elif self.has_pair():
            print "Pair"
    
    def create_cards(self):
        self.list0 = {}
        self.list1 = {}
        self.list2 = {}
        self.list3 = {}

        for card in self.cards:
            if card.suit == 0:
                self.list0[card.rank] = self.list0.get(card.rank, 0) + 1
            if card.suit == 1:
                self.list1[card.rank] = self.list1.get(card.rank, 0) + 1
            if card.suit == 2:
                self.list2[card.rank] = self.list2.get(card.rank, 0) + 1
            if card.suit == 3:
                self.list3[card.rank] = self.list3.get(card.rank, 0) + 1
                
#         print self.list0, self.list1, self.list2, self.list3
    
    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1
            
    def rank_hist(self):
        """Builds a histogram of the numbers that appear in the hand.

        Stores the result in attribute suits.
        """
        self.ranks = {}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1

    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    
    def has_pair(self):
        self.rank_hist()
        for val in self.ranks.values():
            if val >= 2:
                return True
        return False
        
    def has_twopair(self):
        self.rank_hist()
        counter = 0
        for val in self.ranks.values():
            if val >= 2:
                counter += 1
        if counter >= 2:
            return True
        return False
        
    def has_threeofakind(self):
        self.rank_hist()
        for val in self.ranks.values():
            if val >= 3:
                return True
        return False
        
    def has_straight(self):
        self.rank_hist()
        for i in range (9):
            if(i in self.ranks and i+1 in self.ranks and i+2 in self.ranks and i+3 in self.ranks and i+4 in self.ranks):
                return True
        return False
            
        
    def has_fullhouse(self):
        self.suit_hist()
        counter = 0
        
        for val in self.suits.values():
            if val >= 3:
                counter += 1
        for val in self.suits.values():
            if val == 2 and counter >= 1:
                return True
        if counter >= 2:
            return True
        return False
        
    def has_fourofakind(self):
        self.rank_hist()
        for val in self.ranks.values():
            if val >= 4:
                return True
        return False
        
    def has_straightflush(self):
        self.create_cards()
        for i in range (9):
            if(i in self.list0 and i+1 in self.list0 and i+2 in self.list0 and i+3 in self.list0 and i+4 in self.list0):
                return True
        for i in range (9):
            if(i in self.list1 and i+1 in self.list1 and i+2 in self.list1 and i+3 in self.list1 and i+4 in self.list1):
                return True
        for i in range (9):
            if(i in self.list2 and i+1 in self.list2 and i+2 in self.list2 and i+3 in self.list2 and i+4 in self.list2):
                return True
        for i in range (9):
            if(i in self.list3 and i+1 in self.list3 and i+2 in self.list3 and i+3 in self.list3 and i+4 in self.list3):
                return True
        return False
        
if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(7):
        print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 5)
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        hand.sort()
        print hand
        hand.classify()
#         print "Contains flush?", hand.has_straightflush()
#         print ''

class PokerHand(Hand):

    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1
            
    def rank_hist(self):
        """Builds a histogram of the ranks that appear in the hand. 
            Stores the result in attritube ranks
        """
        self.ranks = {}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1

    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    
    def has_pair(self):
        """returns True if there is a pair in the deck, False otherwise"""
        self.rank_hist()

        for card in self.suits.values():
            if card == 2:
                return True
        return False
            
    def has_twopair(self):
        """returns True if their are at least two pairs, False otherwise"""
    
        self.rank_hist()

        pairs = 0
        
        for card in self.suits.values():
            if card == 2:
                pairs += 1
                return True
            if pairs >= 2:
                return True
            
        return False
    
    def has_threekind(self):
        """returns True if there is a three of a kind, False otherwise"""
        self.rank_hist()

        for card in self.suits.values():
            if card == 3:
                return True
        return False

    def has_straight(self):
        """returns True if there is a straight, False otherwise"""
        self.rank_hist()
        
        for i in range(1,10):
            for j in range(5):
                if i+j in self.ranks.keys():
                    if j < 4:
                        continue
                    else:
                        return True
                else:
                    break
        return False

    
    def has_fullhouse(self):
        """returns True if there is a full house, False otherwise """
        #self.rank_hist()
        return self.has_threekind and self.has_pair

    def has_fourkind(self):
        """returns True if there is a four of a kind, False otherwise """
        
        self.rank_hist()

        for card in self.suits.values():
            if card == 4:
                return True
        return False
    
    def has_straightflush(self):
        """returns True if there is a straight flush, False otherwise """
        self.sort()
        if len(self.cards) == 5:
            for i in range(0,5):
                if self.cards[-i].suit == self.cards[-(i+1)].suit and self.cards[-i].rank == self.cards[-(i+1)] + 1:
                    if i == 4:
                        return True
                    else: 
                        continue
                else:
                    break
            return False
        
    def classify(self):
        """returns the highest value for a given hand"""
        if self.has_straightflush():
            return "Straight Flush"
        elif self.has_fourkind():
            return "Four of a Kind"
        elif self.has_fullhouse():
            return "Full House"
        elif self.has_straight():
            return "Straight"
        elif self.has_threekind():
            return "Three of a Kind"
        elif self.has_twopair():
            return "Two pair"
        elif self.has_pair():
            return "Pair"
        else:
            return "None"
        
    
        
if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(7):
        print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 7)  
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        hand.sort()
        print hand
        print "Contains flush?", hand.has_flush()
        print "classification ", hand.classify()
        print ''
       

class PokerHand(Hand):

    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1
            
    def rank_hist(self):
        self.ranks = {}
        for card in self.cards:
            self.ranks[card.rank] = self.ranks.get(card.rank, 0) + 1
            
    
    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    
    def has_pair(self):
        """Returns True if there is a pair in the hand, False otherwise.
        """
        self.rank_hist()
        for val in self.ranks.values():
            if val >=2:
                return True
        return False
    
    def has_twopair(self):
        self.rank_hist()
        count = 0
        for val in self.ranks.values():
            if val % 2 == 0:
                count += val/2
        if count > 1:
            return True
        return False
    
    def classify(self):
        if self.has_flush()==True:
            self.label = 'flush'
            return self.label
        elif self.has_twopair() == True:
            self.label = 'two pair'
            return self.label
        elif self.has_pair() == True:
            self.label = 'pair'
            return self.label
        else:
            self.label = 'nothing good'
            return self.label
        
if __name__ == '__main__':
    # Create a new Deck
    deck = Deck()
    deck.shuffle()

    # Deal the cards and classify the hands. You'll need to add more tests as you create methods
    for i in range(1):
        print "Trial {}:".format(i)
        hand = PokerHand()
        deck.move_cards(hand, 7)  
        # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
        hand.sort()
        print hand
        print "Contains flush?", hand.has_flush()
        print "contains pair?", hand.has_pair()
        print 'Contains two pair?', hand.has_twopair()
        print 'Classify?', hand.classify()

class PokerHand(Hand):

    def suit_hist(self):
        """Builds a histogram of the suits that appear in the hand.

        Stores the result in attribute suits.
        """
        self.suits = {}
        for card in self.cards:
            self.suits[card.suit] = self.suits.get(card.suit, 0) + 1
            
    def num_hist(self):
        self.nums = {}
        for card in self.cards:
            self.nums[card.rank] = self.nums.get(card.rank, 0) + 1

    def has_flush(self):
        """Returns True if the hand has a flush, False otherwise.
      
        Note that this works correctly for hands with more than 5 cards.
        """
        self.suit_hist()
        for val in self.suits.values():
            if val >= 5:
                return True
        return False
    
    def has_pair(self):
        self.num_hist()
        for x in self.nums.values():
            if x > 1:
                return True
        return False
    def has_twoPair(self):
        self.num_hist()
        x = 0
        for val in self.nums.values():
            if val > 1:
                x += 1
        if(x > 1):
            return True
        return False
    def has_three(self):
        self.num_hist()
        for x in self.nums.values():
            if x > 2:
                return True
        return False
    def has_straight(self):
        temp = sorted(self.cards, key = lambda card: card.rank)
        cur = temp[0].rank
        streak = 1
        for i in range(1, len(temp)):
            if temp[i].rank == cur+1:
                streak += 1
                if streak == 5:
                    return True
            elif streak == 4 and temp[i].rank == 1 and cur == 13:
                return True
            elif temp[i].rank != cur:
                streak = 1
            cur = temp[i].rank
        return False
    def has_full(self):
        if not self.has_three():
            return False
        self.num_hist()
        stuff = []
        count = 0
        for x in self.nums.keys():
            if self.nums[x] < 3:
                stuff.append(x)
            elif count == 1:
                break
            else:
                count += 1
        for y in stuff:
            if self.nums[y] == 2:
                return True
        return False
    def has_four(self):
        self.num_hist()
        if 4 in self.nums.values():
            return True
        return False
    def has_straightFlush(self):
        temp = sorted(self.cards, key = lambda card: card.rank)
        cur = temp[0].rank
        curSuit = temp[0].suit
        streak = 1
        suitStreak = 1
        for i in range(1, len(temp)):
            if temp[i].suit == curSuit:
                suitStreak += 1
            else:
                upcomings = []
                num = 1
                while(num < 4 and i+num < len(temp)):
                    upcomings.append(temp[i+num])
                    num += 1
                boolTemp = False
                for x in upcomings:
                    if x.suit == curSuit:
                        boolTemp = True
                if not boolTemp:
                    suitStreak = 1
                    curSuit = temp[i].suit
            if temp[i].rank == cur+1:
                streak += 1
            elif streak == 4 and temp[i].rank == 1 and cur == 13:
                if(suitStreak > 4):
                    return True
            elif temp[i].rank != cur:
                streak = 1
            if streak > 4 and suitStreak > 4:
                return True
            cur = temp[i].rank
        return False
    def classify(self):
        return 'straight flush' if self.has_straightFlush() else 'four of a kind' if self.has_four() else 'full house' if self.has_full() else 'flush' if self.has_flush() else 'straight' if self.has_straight() else 'trips' if self.has_three() else 'two pair' if self.has_twoPair() else 'one pair' if self.has_pair() else 'junk'
def probs(repetitions):
    occurences = {}
    for i in range(repetitions):
        deck = Deck()
        deck.shuffle()
        hand = PokerHand()
        deck.move_cards(hand, 7)
        hand.sort()
        s = hand.classify()
        occurences[s] = occurences.get(s, 0) + 1
    return occurences
def table(repititions):
    print 'For {} repititions:'.format(repititions)
    occur = probs(repititions)
    hands = ['junk', 'one pair', 'two pair', 'trips', 'straight', 'flush', 'full house', 'four of a kind', 'straight flush']
    x = 0
    for i in occur.values():
        x += i
    for thing in hands:
        try:
            p = float(occur[thing]) / x
        except KeyError:
            p = 0
        print '{} has probability {}'.format(thing, p)
    
if __name__ == '__main__':
#     # Create a new Deck
#     deck = Deck()
#     deck.shuffle()

#     # Deal the cards and classify the hands. You'll need to add more tests as you create methods
#     for i in range(7):
#         print "Trial {}:".format(i)
#         hand = PokerHand()
#         deck.move_cards(hand, 7)  
# #         hand.cards = [Card(0, i) for i in range(1, 8)] 
#         # Note: Why aren't we using the deal_hands Deck method you wrote? How could we modify it so that we _could_ use it?
#         hand.sort()
#         print hand
#         print "Contains pair?", hand.has_pair()
#         print "Contains two pair?", hand.has_twoPair()
#         print "Contains trips?", hand.has_three()
#         print "Contains straight?", hand.has_straight()
#         print "Contains flush?", hand.has_flush()
#         print "Contains full house?", hand.has_full()
#         print "Contains quad?", hand.has_four()
#         print "Contains straight flush?", hand.has_straightFlush()
#         print hand.classify()
#         print ''
#     print probs(100)
    table(10000)<jupyter_output>For 10000 repititions:
junk has probability 0.174
one pair has probability 0.4382
two pair has probability 0.2365
trips has probability 0.052
straight has probability 0.0401
flush has probability 0.0301
full house has probability 0.0268
four of a kind has probability 0.0017
straight flush has probability 0.0006
{'fraction_non_alphanumeric': 0.10295957830706023, 'fraction_numerical': 0.009482419417159103, 'mean_word_length': 1.7715206413455682, 'pattern_counts': {'":': 2, '<': 101, '<?xml version=': 0, '>': 326, 'https://': 0, 'lorem ipsum': 0, 'www.': 28, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 29, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '2', 'dir': 'jupyter-structured-clean-dedup', 'id': '23797', 'license': 'MIT', 'n_tokens_mistral': 85267, 'n_tokens_neox': 80975, 'n_words': 27728, 'path': 'processed_notebooks/day12_reading_journal_responses.ipynb', 'repo_name': 'osteele/assignment-tools', 'size': '282605'}