Stop learning alone!

Learn faster and stay on-track by joining this free class with other self-learners.

Register for MIT OpenCourseWare 6.00 Introduction to Computer Science and Programming now.

MIT OpenCourseWare 6.00 Introduction to Computer Science and Programming

Class length: 24 weeks. Start anytime.

Creator: duallain

Status: Established

Join this class!

Lesson 11: Assignment 1

Assignment 6: Word games 2

Homework Submissions

15 total

ChapLeo (Self-grade: Outstanding)
Submitted 3 months ago | Permalink | Time spent: 1 day

Ok, this problem-set was a little tough, but I got through it (with a little help from http://curiousreef.com)

# Problem Set 6: 6.00 Word Game II
# Name: Chapman
# Collaborators: sebrenner(http://curiousreef.com)
# Time: 30:30
#

import random
import string
import time

VOWELS = 'aeiou'
CONSONANTS = 'bcdfghjklmnpqrstvwxyz'
HAND_SIZE = 7
time_limit = 0.20

SCRABBLE_LETTER_VALUES = {
    'a': 1, 'b': 3, 'c': 3, 'd': 2, 'e': 1, 'f': 4, 'g': 2, 'h': 4, 'i': 1, 'j': 8, 'k': 5, 'l': 1, 'm': 3, 'n': 1, 'o': 1, 'p': 3, 'q': 10, 'r': 1, 's': 1, 't': 1, 'u': 1, 'v': 4, 'w': 4, 'x': 8, 'y': 4, 'z': 10
}


# -----------------------------------
# Helper code
# (you don't need to understand this helper code)

WORDLIST_FILENAME = "words.txt"



def load_words():
    """
    Returns a list of valid words. Words are strings of lowercase letters.
    
    Depending on the size of the word list, this function may
    take a while to finish.
    """
    print "Loading word list from file..."
    # inFile: file
    inFile = open(WORDLIST_FILENAME, 'r', 0)
    # wordlist: list of strings
    wordlist = []
    for line in inFile:
        wordlist.append(line.strip().lower())
    print "  ", len(wordlist), "words loaded."
    return wordlist

def get_frequency_dict(sequence):
    """
    Returns a dictionary where the keys are elements of the sequence
    and the values are integer counts, for the number of times that
    an element is repeated in the sequence.

    sequence: string or list
    return: dictionary
    """
    # freqs: dictionary (element_type -> int)
    freq = {}
    for x in sequence:
        freq[x] = freq.get(x,0) + 1
    return freq


# (end of helper code)
# -----------------------------------



def get_words_to_points(word_list):
    """Return a dict that maps every word in word_list to its point value"""
    dict_points = {}
    for word in word_list:    #for each word in 'word_list' list, 
        score = 0
        for letter in word:    
            score += SCRABBLE_LETTER_VALUES[letter] #the scores of individual letters are added up,
        dict_points[word] = score                 # and stored  in 'dict_points' dictionary as a key-value pair.
    return dict_points

#
# Problem #1: Scoring a word
#
def get_word_score(word, n):
    """
    Returns the score for a word. Assumes the word is a
    valid word.

    The score for a word is the sum of the points for letters
    in the word, plus 50 points if all n letters are used on
    the first go.

    Letters are scored as in Scrabble; A is worth 1, B is
    worth 3, C is worth 3, D is worth 2, E is worth 1, and so on.

    word: string (lowercase letters)
    returns: int >= 0
    """
    score = 0
    for element in word:                          #iterates for each letter in 'word'
        score += SCRABBLE_LETTER_VALUES[element]  #adds the respective value from dictionary 'SCRABBLE_LETTER_VALUES'
    if len(word) == n:      #if all letters are used, adds 50 point bonus
        score += 50
    return score    #returns total score for word



#
# Make sure you understand how this function works and what it does!
#
def display_hand(hand):
    """
    Displays the letters currently in the hand.

    For example:
       display_hand({'a':1, 'x':2, 'l':3, 'e':1})
    Should print out something like:
       a x x l l l e
    The order of the letters is unimportant.

    hand: dictionary (string -> int)
    """
    for letter in hand.keys():
        for j in range(hand[letter]):
            print letter,              # print all on the same line
    print                              # print an empty line

#
# Make sure you understand how this function works and what it does!
#
def deal_hand(n):
    """
    Returns a random hand containing n lowercase letters.
    At least n/3 the letters in the hand should be VOWELS.

    Hands are represented as dictionaries. The keys are
    letters and the values are the number of times the
    particular letter is repeated in that hand.

    n: int >= 0
    returns: dictionary (string -> int)
    """
    hand={}
    num_vowels = n / 3
    
    for i in range(num_vowels):
        x = VOWELS[random.randrange(0,len(VOWELS))]
        hand[x] = hand.get(x, 0) + 1
        
    for i in range(num_vowels, n):    
        x = CONSONANTS[random.randrange(0,len(CONSONANTS))]
        hand[x] = hand.get(x, 0) + 1
        
    return hand

#
# Problem #2: Update a hand by removing letters
#
def update_hand(hand, word):
    """
    Assumes that 'hand' has all the letters in word.
    In other words, this assumes that however many times
    a letter appears in 'word', 'hand' has at least as
    many of that letter in it. 

    Updates the hand: uses up the letters in the given word
    and returns the new hand, without those letters in it.

    Has no side effects: does not mutate hand.

    word: string
    hand: dictionary (string -> int)    
    returns: dictionary (string -> int)
    """
    returnHand= hand
    for letter in word:    #iterates for each letter in 'word'
        returnHand[letter] = returnHand.get(letter,0) - 1   #deacreases by 1, the repective value of each letter(key) 
    return returnHand   #returns the updated hand



#
# Problem #3: Test word validity
#
def is_valid_word(word, hand, points_dict):
    """
    Returns True if word is in the word_list and is entirely
    composed of letters in the hand. Otherwise, returns False.
    Does not mutate hand or word_list.
    
    word: string
    hand: dictionary (string -> int)
    word_list: list of lowercase strings
    """
    tempHand = hand.copy() #makes a copy of dictionary 'hand' to prevent mutation
    for letter in word:   #iterates for each letter in 'word'
        if tempHand.get(letter,0) == 0: #if the value of letter (key) is 0 or cannot be found;
            return False                # ends function  by returning 'False'
        else:                        #if value of key greater than 0;
            tempHand[letter] -= 1    #subtracts 1 from the value  
    if not word in points_dict:      # checks whether 'word' is in 'points_dict' dictionary 
        return False               #if not, ends funtion by returning 'False'
    return True                #if the 'word' passes both tests, ends function by returning 'True'





#
# Problem #4: Playing a hand
#
def play_hand(hand, word_list):
    """
    Allows the user to play the given hand, as follows:

    * The hand is displayed.
    
    * The user may input a word.

    * An invalid word is rejected, and a message is displayed asking
      the user to choose another word.

    * When a valid word is entered, it uses up letters from the hand.

    * After every valid word: the score for that word and the total
      score so far are displayed, the remaining letters in the hand 
      are displayed, and the user is asked to input another word.

    * The sum of the word scores is displayed when the hand finishes.

    * The hand finishes when there are no more unused letters.
      The user can also finish playing the hand by inputing a single
      period (the string '.') instead of a word.

    * The final score is displayed.

      hand: dictionary (string -> int)
      word_list: list of lowercase strings
    """
    totalScore = 0
    timer = 0
    TimeAllowed= time_limit    #time allowed is set to 0.30 sceonds.
  
    currentHand = hand.copy()   #makes copy of dictionary 'hand' to prevent mutation
    noLetters= False
    TimeOver = False
    userWord = ''   #initial 'userWord' is set to null to prevent crashing 
    print 'Starting hand:'      #the initial hand is displayed
    display_hand(currentHand)
    
    while userWord != '.' and  noLetters == False and TimeOver == False:   #iterates while user input is NOT . and letters in hand is is greater than 0 
        start_time = time.time()               #time at start of user input is recorded
        userWord = player_choice(choice,currentHand)              #calls either 'pick_best_word' or 'pick_best_word_faster' according to user's choice
        end_time = time.time()                  #time at end of user input is recorded
        total_time = end_time - start_time                  #calculates time taken to input word
    
        timer += total_time                      #the time taken by user to provide (current) input is added to the total time 
        
        if userWord =='.':   #if user input is . prints blank line and exits iteration
            print
        elif  timer >= TimeAllowed:                  #if total time taken by user is greater than time allowed,                                 
            print 'Sorry, you have exceeded your time limit!'  #prints message and exits iteration.
            TimeOver = True
    
        elif not is_valid_word(userWord, currentHand, word_list):   #else if user input is an invalid word;
            print 'Invalid word. Please try again.'                 #displays hand and prompts user to try again
            display_hand(currentHand)
            print 'You have %0.2f seconds left' %(TimeAllowed - timer)    #displays the amount of time left
        else:                                                   #if user inputs a valid word, within time limit;
            currentHand = update_hand(currentHand, userWord)    #the hand is updated by deducting the letters used by user
            WordPoints = get_word_score(userWord, HAND_SIZE)  #the score for word is calculated and assgned to 'WordPoints' variable
            if total_time < 1:                             #if time taken is  less than 1 second, only points earned by word is counted; 
                currentScore = WordPoints                   #this helps prevents zero-division error.
            else:
                currentScore = WordPoints/total_time         #else the points earned by word is divided by the time taken
            totalScore += currentScore                        #the current score is added to the total score
            
            print 'It took %0.2f seconds to provide an answer.' %total_time    #displays time taken to provide answer(to 2 decimal places)
            print 'You have %0.2f seconds left' %(TimeAllowed - timer)          #displays the amount of time left
            print userWord, 'earned %0.2f points!' %currentScore   #points earned by the user and the total score (so far) are displayed (to 2 decimal places)
            print 'Your current Total score is: %0.2f' %totalScore
            print 'Hand:'
            display_hand(currentHand)      #the updated hand is displayed
            temp = 0
            for letter in currentHand:     #calculates the number of letters left in hand
                temp += currentHand[letter]
            if temp < 1 :                #if the number of letters left is 0
                noLetters = True         # exits iteration
            
    print 'Your total score is: %0.2f' %totalScore  #prints total score (to 2 decimal places)
    
    
#
# Problem #5: Playing a game
# 
# 
def play_game(word_list):
    """
    Allow the user to play an arbitrary number of hands.

    * Asks the user to input 'n' or 'r' or 'e'.

    * If the user inputs 'n', let the user play a new (random) hand.
      When done playing the hand, ask the 'n' or 'e' question again.

    * If the user inputs 'r', let the user play the last hand again.

    * If the user inputs 'e', exit the game.

    * If the user inputs anything else, ask them again.
    """
    
        ## uncomment the following block of code once you've completed Problem #4
    hand = deal_hand(HAND_SIZE) # random init
    while True:
        cmd = raw_input('Enter n to deal a new hand, r to replay the last hand, or e to end game: ')
        if cmd == 'n':
            hand = deal_hand(HAND_SIZE)
            play_hand(hand.copy(), word_list)
            print
        elif cmd == 'r':
            play_hand(hand.copy(), word_list)
            print
        elif cmd == 'e':
            break
        else:
            print "Invalid command."

##def get_time_limit(points_dict, k):
##    """
##    Return the time limit for the computer player as a function of the
##    multiplier k.
##    points_dict should be the same dictionary that is created by
##    get_words_to_points.
##    """
##    start_time = time.time()
##    # Do some computation. The only purpose of the computation is so we can
##    # figure out how long your computer takes to perform a known task.
##    for word in points_dict:
##        get_frequency_dict(word)
##        get_word_score(word, HAND_SIZE)
##    end_time = time.time()
##    return (end_time - start_time) * k


def pick_best_word(hand, points_dict):
    """
    Return the highest scoring word from points_dict that can be made
    with the given hand. Return '.' if no words can be made with the
    given hand.
    """ 
    largest_word =''          
    highest_points = 0
    for word in points_dict:    #iterates for each word in 'points_dict' dictionary,
        temp_hand = hand.copy() # makes a temporary copy of the 'hand' dictionary to prevent mutation,
        Possible = True
        counter = 0
        while Possible==True and counter in range(len(word)):   #checks whether the (current) word can be made from letters in 'hand' dictionary,
                letter= word[counter]
                if temp_hand.get(letter,0)== 0:
                    Possible = False
                    
                else:
                    temp_hand[letter] -= 1
                    
                counter += 1

        if Possible == True:                       #if the (current) word can be made from the 'hand' dictionary letters,
            if points_dict[word] > highest_points:    #checks whether the current word is the highest scoring word so far,
                largest_word = word                   #if it is, 'largest_word' varible is pointed to the current word.
                highest_points = points_dict[word]
    if highest_points == 0:           #if no words can be made form 'hand' dictionary letters, "." is returned to stop execution.
        return '.'
    else:
        return largest_word           #else, the highest scoring word is returned.
    


def pick_best_word_faster(hand,rearrange_dict):
    list1=[]
    string1=''
    temp_hand = hand.copy()       #makes a copy of 'hand' dictionary to prevent mutation
    for letter in temp_hand:      #converts the dictionary into a list 
        for element in range(hand[letter]):  
            list1 += letter
    list1.sort()        #sorts the list so the letters are in alphabetical order
    for element in list1:  #converts the list into a (sorted) string; 'string1' 
        string1 += element   
    
    possible_combinations = division(string1)  #gets a list of all possible combinations of 'string1'
    highest_score=0
    largest_word =''
    for element in possible_combinations:   #iterates for each possible combination of 'string1',
        if rearrange_dict.has_key(element):   #if the combination is present as a key in the 'rearrange_dict' dictionary,
            word = rearrange_dict[element]    #the corresponding value(word) is assigned to the 'word' varibale,
            word_score = get_word_score(word, HAND_SIZE)    #points the 'word_score' variable to the score of the word
            if word_score > highest_score:  #if the word is the highest scoring word so far, 
                highest_score = points_dict[word]   #it's value(score) is assigned to the 'highest_score' variable,
                largest_word = word    # and the word is assigned to the 'largest_word' vairiable.

    if largest_word =='':      #at end of above iteration, if no valid word is found,
        return '.'         # "." is returned
    else:
        return largest_word  #else the largest word is returned


def get_word_rearrangements(word_list):
    rearrange_dict= {}
    print 'Loading word rearrangement dictionary.....'   #informs user the 'rearrange_dict' dictionary is been compiled
    for word in word_list:         #repeats for every word in the 'word_list',
        x = 0
        temp_list = []
        word_string = ''
        for letter in word:              #each letter in the word is added to the temporary list, 'temp_list,' 
            temp_list.append(letter)     #this is because lists are mutable while strings are not,
            
        while x < len(temp_list):   #then a selection sort is preformed on the list to get a sorted list (letters in alphabetical order),
            y = x+1
            while y < len(temp_list):
                temp = 0
                if temp_list[x]> temp_list[y]:
                    temp = temp_list[y]
                    temp_list[y] = temp_list[x]
                    temp_list[x] = temp
                y += 1
            x += 1
        for element in temp_list:    #the list is then converted into a string,
            word_string += element
        rearrange_dict[word_string] =word    #this sorted string is used as the key, while the (current) word used as the value, in the 'rearrange_dict' dictionary. 
    print 'Word rearrangement dictionary loaded.' #the user is informed the dictionary has been successfully compiled.
    return rearrange_dict        #the 'rearrange_dict' dictionary is returned
            
        
        
def division(string1):
    """Returns all possible combinations of given string. This function assumes the string is already
    sorted when entered into the function"""
    
    if len(string1)==1:     #if length of the string is 1, 
       
        return [string1]    #the string is convertd into a list and returned.
    
    else:                #else the string is broken into two parts.
        L = string1[0]              #the 'L' variable is pointed to the first letter in the string
        R = division(string1[1:])   #the 'R' variable is pointed to the rest of the string and recursed
        listx = []
        for element in R:            #each element in the variable 'R'(result of recursion) is combined,
            listx += [L + element]   #with the 'L' variable and added to the temporary list; 'listx'

        return listx + [L] + R       #the list containing listx, the 'L' vairiable and the 'R' variable is returned.
                                        

def play_choice():
    user_word =''
    while True:
        user_choice = raw_input("Enter '1' to use 'pick_best_word' or '2' to use pick_best_word_faster:") #prompts user to enter choice
        if user_choice== '1' or user_choice=='2':
            return int(user_choice)   #if user entered 1 or 2, returns user's choice
            

def player_choice(user_choice,currentHand):
    if user_choice==2:
        return pick_best_word_faster(currentHand,rearrange_dict) #calls the 'pick_best_word_faster' function to get the highest scoring word.
    else:
        return pick_best_word(currentHand, points_dict) #calls the 'pick_best_word' function to get the highest scoring word.
       
#
# Build data structures used for entire session and play game
#

if __name__ == '__main__':
    word_list = load_words()
    points_dict = get_words_to_points(word_list)
    rearrange_dict = get_word_rearrangements(word_list)
    choice= play_choice()
    play_game(word_list)


#
## Problem 5 ##
#
#
#pick_best_word# 
#
#With respect to 'word_list': Each word in the 'word_list' needs to be compared against the 'hand', to check whether it can be formed.
#When the size of the 'word_list' increases, the number of steps involved also increases propotionatly.
#Hence the order of complexity with respect to the 'word_list' is O(length of word_list)= O(n). This means
#it's linear.
#
#With respect to the 'hand': The only process performed  with regard to the 'hand' dictionary is to
#check whether each letter in a given word (from the word_list) exists in the dictionary. The time taken to access any key of a
#dictionary is constant(irrespective of the size of the dictionary). This means, as the dictionary grows,
#the number of steps does not grow. Hence the complexity is constant: O(1).
#
#
#
#pick_best_word_faster#
#
#With respect to 'word_list': the word_list is not a local variable in pick_best_word_faster. Instead, the pick_best_word_faster checks each
#(sorted) sub-multiset of the letters in 'hand' against the 'rearrange_dict' dictionary. If a sub-multiset exists as a key in the dictionary,
#the corresponding word(value) is extracted. Since, the number of steps needed to access a 'key' in a dictionary does not depend
#on the size of dictionary, the complexity is constant: O(1).
#
#With respect to 'hand': The 'division' function is used to identify all possible sub-multisets of the 'hand'.
#Then each possible sub-multiset is checked against the 'rearrange_dict' dictionary. This procedure seems to have an exponential
#order of growth: O(2^n).
#
#
#Even though 'pick_best_word_faster' has an exponential order of growth (while 'pick_best_word' has a linear order of growth),
#since the number of words in the 'word_list' is much larger than the number of letters in the 'hand',
#'pick_word_faster' is a much faster function than 'pick_best_word'.
nickrt (Self-grade: Outstanding)
Submitted 12 months ago | Permalink | Time spent: 5 hours

This was a good problem set, I didn't have many problems with it, except while implementing the pick_best_word_faster function because of an error in a previous problem I got an error no matter what I did so I wasted a lot of time. I also used a python library to get the combinations function and had to modify it a little bit to make it fit my code. The first 3 and 5th problem took me 1:30 tops, and problem 4 took me at least 3:30.

#Problem set 6
#Name: Nick
#Collaborators: None
#Time: 5:00
#

import random
import string
import time

VOWELS = 'aeiou'
CONSONANTS = 'bcdfghjklmnpqrstvwxyz'
HAND_SIZE = 7
POINTS_DICT = None#Problem 3
REARRANGE_DICT = None#Problem 4

SCRABBLE_LETTER_VALUES = {
    'a': 1, 'b': 3, 'c': 3, 'd': 2, 'e': 1, 'f': 4, 'g': 2, 'h': 4, 'i': 1, 'j': 8, 'k': 5, 'l': 1, 'm': 3, 'n': 1, 'o': 1, 'p': 3, 'q': 10, 'r': 1, 's': 1, 't': 1, 'u': 1, 'v': 4, 'w': 4, 'x': 8, 'y': 4, 'z': 10
}

# -----------------------------------
# Helper code
# (you don't need to understand this helper code)

WORDLIST_FILENAME = "/Users/Nick/Desktop/Introduction to CS and Programming/Assignments/6/Supporting Files/words.txt"

def load_words():
    """
    Returns a list of valid words. Words are strings of lowercase letters.
    
    Depending on the size of the word list, this function may
    take a while to finish.
    """
    print "Loading word list from file..."
    # inFile: file
    inFile = open(WORDLIST_FILENAME, 'r', 0)
    # wordlist: list of strings
    wordlist = []
    for line in inFile:
        wordlist.append(line.strip().lower())
    print "  ", len(wordlist), "words loaded."
    return wordlist

def get_frequency_dict(sequence):
    """
    Returns a dictionary where the keys are elements of the sequence
    and the values are integer counts, for the number of times that
    an element is repeated in the sequence.

    sequence: string or list
    return: dictionary
    """
    # freqs: dictionary (element_type -> int)
    freq = {}
    for x in sequence:
        freq[x] = freq.get(x,0) + 1
    return freq


# (end of helper code)
# -----------------------------------


def get_word_score(word, n):
    """
    Returns the score for a word. Assumes the word is a
    valid word.

    The score for a word is the sum of the points for letters
    in the word, plus 50 points if all n letters are used on
    the first go.

    Letters are scored as in Scrabble; A is worth 1, B is
    worth 3, C is worth 3, D is worth 2, E is worth 1, and so on.

    word: string (lowercase letters)
    returns: int >= 0
    """
    score = 0
    for letter in word:
        score += SCRABBLE_LETTER_VALUES[letter.lower()]
    if len(word) == n:
        score += 50
    return score

#
# Make sure you understand how this function works and what it does!
#
def display_hand(hand):
    """
    Displays the letters currently in the hand.

    For example:
       display_hand({'a':1, 'x':2, 'l':3, 'e':1})
    Should print out something like:
       a x x l l l e
    The order of the letters is unimportant.

    hand: dictionary (string -> int)
    """
    for letter in hand.keys():
        for j in range(hand[letter]):
             print letter,              # print all on the same line
    print                              # print an empty line

#
# Make sure you understand how this function works and what it does!
#
def deal_hand(n):
    """
    Returns a random hand containing n lowercase letters.
    At least n/3 the letters in the hand should be VOWELS.

    Hands are represented as dictionaries. The keys are
    letters and the values are the number of times the
    particular letter is repeated in that hand.

    n: int >= 0
    returns: dictionary (string -> int)
    """
    hand={}
    num_vowels = n / 3
    
    for i in range(num_vowels):
        x = VOWELS[random.randrange(0,len(VOWELS))]
        hand[x] = hand.get(x, 0) + 1
        
    for i in range(num_vowels, n):    
        x = CONSONANTS[random.randrange(0,len(CONSONANTS))]
        hand[x] = hand.get(x, 0) + 1
        
    return hand


def update_hand(hand, word):
    """
    Assumes that 'hand' has all the letters in word.
    In other words, this assumes that however many times
    a letter appears in 'word', 'hand' has at least as
    many of that letter in it. 

    Updates the hand: uses up the letters in the given word
    and returns the new hand, without those letters in it.

    word: string
    hand: dictionary (string -> int)    
    returns: dictionary (string -> int)
    """
    freq = get_frequency_dict(word)
    newhand = {}
    for char in hand:
        newhand[char] = hand[char]-freq.get(char,0)
    return newhand
    #return dict( ( c, hand[c] - freq.get(c,0) ) for c in hand )
        


def is_valid_word(word, hand, word_list):
    """
    Returns True if word is in the word_list and is entirely
    composed of letters in the hand. Otherwise, returns False.
    Does not mutate hand or word_list.
    
    word: string
    hand: dictionary (string -> int)
    word_list: list of lowercase strings
    """
    freq = get_frequency_dict(word)
    for letter in word:
        if freq[letter] > hand.get(letter, 0):
            return False
    return word in POINTS_DICT.keys()

#
#Problem 3##############################################################
#

def get_words_to_points(word_list):
    points_dict = {}
    for word in range(len(word_list)):
        points_dict[word_list[word]] = get_word_score(word_list[word],HAND_SIZE)
    return points_dict

def pick_best_word(hand):
    """
    Return the highest scoring word from points_dict that can be made with the
    given hand.

    Return '.' if no words can be made with the given hand.
    """
    highest = 0
    best = None
    for word in POINTS_DICT.keys():
        wordDict = get_frequency_dict(word)
        works = True
        for letter in wordDict.keys():
            if hand.get(letter,0) < wordDict[letter]: works = False
        if works == True and POINTS_DICT[word] > highest:
            highest = POINTS_DICT[word]
            best = word
    if highest == 0: return '.'
    else:
        return best

#######################################################################

#
#Problem 4#############################################################
#

def get_word_rearrangements(word_list):
    rearrange_dict = {}
    for word in word_list:
        sorted_string = ''
        for letter in sorted(word):
            sorted_string += letter
        rearrange_dict[sorted_string] = word
    return rearrange_dict

def pick_best_word_faster(handDict):
    word = ''
    highest = 0
    hand = ''
    for i in handDict.keys():
        hand += i*handDict[i]
    subSets = []
    for r in range(len(hand)):
        subSets += combinations(hand, r)
    for combList in range(len(subSets)):
        comb = ''
        subSets[combList] = sorted(subSets[combList])
        for letter in subSets[combList]:
            comb += letter
        if comb in REARRANGE_DICT.keys():
            points = POINTS_DICT[REARRANGE_DICT[comb]]
            if points > highest:
                word = REARRANGE_DICT[comb]
    if word != '':
        return word
    else: return '.'
def combinations(iterable, r):
    # combinations('ABCD', 2) --> AB AC AD BC BD CD
    # combinations(range(4), 3) --> 012 013 023 123
    pool = list(iterable)
    n = len(pool)
    if r > n:
        return
    indices = range(r)
    yield list(pool[i] for i in indices)
    while True:
        for i in reversed(range(r)):
            if indices[i] != i + n - r:
                break
        else:
            return
        indices[i] += 1
        for j in range(i+1, r):
            indices[j] = indices[j-1] + 1
        yield list(pool[i] for i in indices)

    
    
########################################################################


#
#Problem 2##############################################################
#
def get_time_limit(points_dict, k):
    """
Returns the time limit for the computer as a function of the multiplier k.

points_dict should be the same dictionary that is created by get_words_to_points.
"""
    start_time = time.time()
    for word in points_dict:
        get_frequency_dict(word)
        get_word_score(word, HAND_SIZE)
    end_time = time.time()
    return (end_time - start_time) * k

########################################################################

def play_hand(hand, word_list):
    """
    Allows the user to play the given hand, as follows:

    * The hand is displayed.
    
    * The user may input a word.

    * An invalid word is rejected, and a message is displayed asking
      the user to choose another word.

    * When a valid word is entered, it uses up letters from the hand.

    * After every valid word: the score for that word is displayed,
      the remaining letters in the hand are displayed, and the user
      is asked to input another word.

    * The sum of the word scores is displayed when the hand finishes.

    * The hand finishes when there are no more unused letters.
      The user can also finish playing the hand by inputing a single
      period (the string '.') instead of a word.

      hand: dictionary (string -> int)
      word_list: list of lowercase strings
    """
    total = 0.0
    initial_handlen = sum(hand.values())
    while True:#Problem 2
        time_limit = raw_input('Enter time limit, in seconds, for players: ')#Problem 2
        try:#Problem 2
            time_limit = float(time_limit)#Problem 2
            break
        except:#Problem 2
            print('Not a number')#Problem 2
    total_time = 0#Problem 2

    time_limit = get_time_limit(POINTS_DICT, time_limit)#Problem 3
    
    while sum(hand.values()) > 0:
        print 'Current Hand:',
        display_hand(hand)
        start_time = time.time()#Problem 1
        ##userWord = raw_input('Enter word, or a . to indicate that you are finished: ')
        userWord = pick_best_word_faster(hand)
        end_time = time.time()#Problem 1
        word_time = end_time - start_time#Problem 1
        word_time_exact = word_time
        total_time += word_time#Problem 2
        if total_time >= time_limit: break #Problem 2
                          
        if word_time < 1: word_time = 1#Problem 1
        if userWord == '.':
             break
        else:
            isValid = is_valid_word(userWord, hand, word_list)
            if not isValid:
                print 'Invalid word, please try again.'
            else:
                print 'It took %.2f seconds to provide an answer.' % (word_time_exact) #Problem 1
                points = get_word_score(userWord, initial_handlen) / word_time#Problem 1(the divide by part)
                total += points
                print '%s earned %.2f points. Total: %.2f points' % (userWord, points, total)
                hand = update_hand(hand, userWord)
    if total_time < time_limit:#Problem 2
        print 'Total score: %.2f points.' % total
    else:#Problem 2
        print 'Total time excceds %.2f seconds.  You scored %.2f points.' % (time_limit, total)#Problem 2



def play_game(word_list):
    """
    Allow the user to play an arbitrary number of hands.

    * Asks the user to input 'n' or 'r' or 'e'.

    * If the user inputs 'n', let the user play a new (random) hand.
      When done playing the hand, ask the 'n' or 'e' question again.

    * If the user inputs 'r', let the user play the last hand again.

    * If the user inputs 'e', exit the game.

    * If the user inputs anything else, ask them again.
    """
    
    hand = deal_hand(HAND_SIZE) # random init
    while True:
        cmd = raw_input('Enter n to deal a new hand, r to replay the last hand, or e to end game: ')
        if cmd == 'n':
            hand = deal_hand(HAND_SIZE)
            play_hand(hand.copy(), word_list)
            print
        elif cmd == 'r':
            play_hand(hand.copy(), word_list)
            print
        elif cmd == 'e':
            break
        else:
            print "Invalid command."

#
# Build data structures used for entire session and play game
#
if __name__ == '__main__':
    word_list = load_words()

    POINTS_DICT = get_words_to_points(word_list) #Problem 3
    REARRANGE_DICT = get_word_rearrangements(word_list)#Problem 4
    play_game(word_list)




#
##Problem 5#######################################################
#Complexity of pick_best_word in terms of word_list and HAND_SIZE
#
#O(len(word_list))
#
#
#Complexity of pick_best_word_faster in terms of word_list and HAND_SIZE
#
#O(x ** HAND_SIZE)
#
#Pick best word is faster for short hands, but the longer the hand gets the
#longer the function takes.
###################################################################
sebrenner (Self-grade: Pretty good)
Submitted 1 year ago | Permalink | Time spent: 8 hours
# 6.00 Problem Set 6
#
# The 6.00 Word Game
#
# Scott Brenner
# Problem 1: 25 minutes
# Problem 2: 35 minutes
# Problem 3: ~8 hours  Lots of careless problems along the way.  Problems cause by short periods of time to work on problems and trouble with large sclae organization.


import random
import string
import time

VOWELS = 'aeiou'
CONSONANTS = 'bcdfghjklmnpqrstvwxyz'
HAND_SIZE = 7
COMPUTER_TIME_FACTOR = 1

SCRABBLE_LETTER_VALUES = {
'a': 1, 'b': 3, 'c': 3, 'd': 2, 'e': 1, 'f': 4, 'g': 2, 'h': 4, 'i': 1, 'j': 8, 'k': 5, 'l': 1, 'm': 3, 'n': 1, 'o': 1, 'p': 3, 'q': 10, 'r': 1, 's': 1, 't': 1, 'u': 1, 'v': 4, 'w': 4, 'x': 8, 'y': 4, 'z': 10
}

WORDLIST_FILENAME = "words.txt"

# -----------------------------------
# PREPROCESSSING FUNCTIONS- These prep the word lists/dictionaries for game play.  They should execute only once--when the app is launched.  They include function used for scoring and for the computer-player.
def load_words():
    """
    Returns a list of valid words. Words are strings of lowercase letters.
    
    Depending on the size of the word list, this function may
    take a while to finish.
    """
    print "Loading word list from file..."
    # inFile: file
    inFile = open(WORDLIST_FILENAME, 'r', 0)
    # wordlist: list of strings
    wordlist = []
    for line in inFile:
        wordlist.append(line.strip().lower())
    print "  ", len(wordlist), "words loaded."
    return wordlist

def sort_word(word_string):
    """
    Takes a string, alphabetizes it and returns it as a string.
    """
    char_list =[]
    sorted_string = ''
    for char in word_string:
        char_list.append(char)
    char_list.sort()
    for char in char_list:
        sorted_string += char
    return sorted_string

def get_word_rearrangements(a_list_of_words):
    """
    This function takes a list of words and returns a dictionary of strings mapped to actual words.
    
    This function is used by the computer-player to find valid words.
    
    Create a dict where, for any set of letters, you can determine if there is some acceptable word that is a rearrangement of those letters.
    Let d = {}
    For every word w in the word list:
        Let d[(string containing the letters of w in sorted order)] = w
    """
    rearrange_dict = {}
    for word in a_list_of_words:
        #   build a list from the char in word: 1) convert word string to list, 2) sort list, 3) convert list back to string.
        char_list =[]
        my_string = ''
        for char in word:
            char_list.append(char)
        char_list.sort()
        for each in range(len(char_list)):
            my_string +=char_list[each]
        rearrange_dict[my_string] = word
    #print "In get_word_rearrangements. Rrearrange_dict:", rearrange_dict
    return rearrange_dict

def get_frequency_dict(sequence):
    """
    Returns a dictionary where the keys are elements of the sequence
    and the values are integer counts, for the number of times that
    an element is repeated in the sequence.
    
    sequence: string or list
    return: dictionary
    """
    # freqs: dictionary (element_type -> int)
    freq = {}
    for x in sequence:
        freq[x] = freq.get(x,0) + 1
    return freq

def get_word_score(word, n):
    """
    Returns the score for a word. Assumes the word is a
    valid word.
    
    The score for a word is the sum of the points for letters
    in the word, plus 50 points if all n letters are used on
    the first go.
    
    Letters are scored as in Scrabble; A is worth 1, B is
    worth 3, C is worth 3, D is worth 2, E is worth 1, and so on.
    
    word: string (lowercase letters)
    returns: int >= 0
    """
    score = 0
    for letter in word:
        score += SCRABBLE_LETTER_VALUES[letter.lower()]
    if len(word) == n:
        score += 50
    return score


# -----------------------------------
# GAME PLAY FUNCTIONS- These functions start and play the game play.  They will execute mulitple times.
def display_hand(hand):
    """
    Displays the letters currently in the hand.
    
    For example:
       display_hand({'a':1, 'x':2, 'l':3, 'e':1})
    Should print out something like:
       a x x l l l e
    The order of the letters is unimportant.
    
    hand: dictionary (string -> int)
    """
    for letter in hand.keys():
        for j in range(hand[letter]):
             print letter,              # print all on the same line
    print                              # print an empty line

def deal_hand(n):
    """
    Returns a random hand containing n lowercase letters.
    At least n/3 the letters in the hand should be VOWELS.
    
    Hands are represented as dictionaries. The keys are
    letters and the values are the number of times the
    particular letter is repeated in that hand.
    
    n: int >= 0
    returns: dictionary (string -> int)
    """
    hand={}
    num_vowels = n / 3
    
    for i in range(num_vowels):
        x = VOWELS[random.randrange(0,len(VOWELS))]
        hand[x] = hand.get(x, 0) + 1
        
    for i in range(num_vowels, n):    
        x = CONSONANTS[random.randrange(0,len(CONSONANTS))]
        hand[x] = hand.get(x, 0) + 1
        
    return hand

def update_hand(hand, word):
    """
    Assumes that 'hand' has all the letters in word.
    In other words, this assumes that however many times
    a letter appears in 'word', 'hand' has at least as
    many of that letter in it. 
    
    Updates the hand: uses up the letters in the given word
    and returns the new hand, without those letters in it.
    
    word: string
    hand: dictionary (string -> int)    
    returns: dictionary (string -> int)
    """
    freq = get_frequency_dict(word)
    newhand = {}
    for char in hand:
        newhand[char] = hand[char]-freq.get(char,0)
    return newhand
    #return dict( ( c, hand[c] - freq.get(c,0) ) for c in hand )

def is_valid_word(word, hand, point_dict):
    """
    Returns True if word is in the word_list and is entirely
    composed of letters in the hand. Otherwise, returns False.
    Does not mutate hand or word_list.
    
    word: string
    hand: dictionary (string -> int)
    word_list: list of lowercase strings
    """
    freq = get_frequency_dict(word)                 # Create dictionary frequency dictionary for word.  E.g., if work is 'hello', freq = {'h': 1, 'e': 1, 'l': 2, 'o': 1}.
    for letter in word:
        if freq[letter] > hand.get(letter, 0):      # Confirm that each letter needed to spell word is in the hand in sufficient quantity.
            return False
    return word in point_dict

def play_hand(hand, word_list):
    """
    Allows the user to play the given hand, as follows:
    * The hand is displayed.
    
    * The user may input a word.
    
    * An invalid word is rejected, and a message is displayed asking the user to choose another word.
    
    * When a valid word is entered, it uses up letters from the hand.
    
    * After every valid word: the score for that word is displayed,
      the remaining letters in the hand are displayed, and the user
      is asked to input another word.
    
    * The sum of the word scores is displayed when the hand finishes.
    
    * The hand finishes when there are no more unused letters.
      The user can also finish playing the hand by inputing a single
      period (the string '.') instead of a word.
      
      hand: dictionary (string -> int)
      word_list: list of lowercase strings
    """    
    
    total_points = 0.0
    initial_handlen = sum(hand.values())
    foo = True
    elapsed_time = 0.0
    chessTime = get_time_limit(point_dict, COMPUTER_TIME_FACTOR)
    print "\nComputer will have %0.2f seconds to play the hand.\n" % chessTime
    # this is commented out because it was replaced by the line above.  The line above sets chessTime based on computer speed.  The commented code aske the user for chessTime.
    # while foo:
    #     chessTime = raw_input('Enter time limit, in seconds, for players:')
    #     if chessTime.isdigit():
    #         chessTime = float(chessTime)
    #         foo = False
    while sum(hand.values()) > 0:
        print
        print 'Current Hand:',
        display_hand(hand)
        startTime = time.time()
        # userWord = raw_input('Enter word, or a . to indicate that you are finished: ')
        userWord = pick_best_word_faster(hand, arranged_words) # 'faster' function
        # userWord = pick_best_word(hand, point_dict) # orignial fuction
        endTime = time.time()
        playTime = endTime - startTime
        print "It took %0.5f seconds to play %s." % (playTime, userWord)
        elapsed_time += playTime
        if userWord == '.':
            break
        elif elapsed_time > chessTime:
            print "It took %0.5f seconds to play %s." % (playTime, userWord)
            # print 'It took %0.2f seconds to enter your word.' % elapsed_time
            print 'Your total time to play the hand exceeded %0.5f seconds. Your final score is %0.2f points.' % (chessTime, total_points)
            break
        else:
            isValid = is_valid_word(userWord, hand, word_list)
            if not isValid:
                print 'Invalid word, please try again.'
            else:
                if playTime < 1: playTime = 1
                points = get_word_score(userWord, initial_handlen) /  playTime
                total_points += points
                print '%s earned %0.5f points. Total: %0.5f points' % (userWord, points, total_points)
                hand = update_hand(hand, userWord)
                #print 'Updated hand:%s' % hand
    print 'Total score: %0.5f points.' % total_points
    return total_points

def play_game(word_list):
    """
    Allow the user to play an arbitrary number of hands.
    * Asks the user to input 'n' or 'r' or 'e'.
    
    * If the user inputs 'n', let the user play a new (random) hand. When done playing the hand, ask the 'n' or 'e' question again.
    
    * If the user inputs 'r', let the user play the last hand again.
    
    * If the user inputs 'e', exit the game.
    
    * If the user inputs anything else, ask them again.
    """
    
    # hand_score = 0.0
    # counter = 0
    # while hand_score < 40 or counter < 5:
    #         hand = deal_hand(HAND_SIZE)
    #         hand_score = play_hand(hand.copy(), word_list)
    #         print "Counter %i." %counter 
    #         counter += 1
    # print "Counter %i." %counter 
    
    hand = deal_hand(HAND_SIZE) # random init
    while True:
        cmd = raw_input('Enter n to deal a new hand, r to replay the last hand, or e to end game: ')
        if cmd == 'n':
            hand = deal_hand(HAND_SIZE)
            play_hand(hand.copy(), word_list)
            print
        elif cmd == 'r':
            play_hand(hand.copy(), word_list)
            print
        elif cmd == 'e':
            break
        else:
            print "Invalid command."

# -----------------------------------
# COMPUTER PLAYER FUNCTIONS- These functions play the computer's hand.  They find and play the best word.  They will execute mulitple times.
def pick_best_word(hand, points_dict):
    """
    Return the highest scoring word from points_dict that can be made with the given hand.
    
    Return '.' if no words can be made with the given hand.
    """
    freq = get_frequency_dict(hand)     # Create dictionary frequency dictionary for the hand
    best_word = ""
    best_word_value = 0
    for word in points_dict:
        if is_valid_word(word, hand, points_dict):
            word_value = get_word_score(word, HAND_SIZE)
            #print "The word is %s, with value %i.   The best word is %s, with value %i." % (word, word_value, best_word, best_word_value)
            if word_value > best_word_value:
                best_word = word
                best_word_value = word_value
    if best_word_value > 0:
        return best_word
    return "."

def get_words_to_point(word_list):
    """
    Return a dict that maps every word in word_list to its point value.
    """
    word_value_dictionary = {}
    for word in word_list:
        word_value_dictionary[word] = get_word_score(word, 7)
    return word_value_dictionary
    #return len(word_value_dictionary)

def pick_best_word_faster(hand, rearrange_dict):
    """
    Takes a hand {dictionary} and a dictionary of letter combinations that map to a valid word.
    
    Returns the highest value word or '.'-if there is no valid word possible.
    
    Pseudo-code:
    To find some word that can be made out of the letters in HAND:
        For each subset S of the letters of HAND:
            Let w = (string containing the letters of S in sorted order)
            If w in d:
                return d[w]
                
    This function must convert the hand{dictionary} to a string.  In doing so it must check to make sure that the value of each key in the had is > 0
    """
    #print "In pick best. Hand:", hand
    hand_string = ''
    
    for each in hand:
        if hand[each] > 0:
            hand_string += each * hand[each]
        
    #print "Hand sorted: %s" %hand_string
    
    best_word =''
    best_word_score = 0
    subsets = build_substrings(hand_string)
    subset_value = 0
    
    for subset in subsets:
        sorted_subset = sort_word(subset)
        if sorted_subset in rearrange_dict:
            subset_value = get_word_score(sorted_subset, HAND_SIZE)
            if subset_value > best_word_score:
                best_word = rearrange_dict[sorted_subset]                
                best_word_score = subset_value
    
    if best_word_score > 0:
        return best_word
    else:
        return '.'

def get_time_limit(points_dict, k): 
    """
    Return the time limit for the computer player as a function of the multiplier k.
    
    points_dict should be the same dictionary that is created by get_words_to_points.
    """
    start_time = time.time()
    # Do some computation. The only purpose of the computation is so we can
    # figure out how long your computer takes to perform a known task.
    for word in points_dict:
        get_frequency_dict(word)
        get_word_score(word, HAND_SIZE)
    end_time = time.time()
    return (end_time - start_time) * k

def build_substrings(string):
    """
    Works on the premiss that given a set of the substrings of a string the
    the subsets of a string with one more char is the formed by taking all the
    substrings in the known subset and also adding to them the set formed by
    adding the character to every element in the old set and then adding the 
    new char.
    
    """
    result = []
    if len(string) == 1:
        result.append(string)
    else:
        for substring in build_substrings(string[:-1]):
            result.append(substring)
            substring = substring + string[-1]
            result.append(substring)
        result.append(string[-1])
        result = list(set(result))  # Convert result into a set.  Sets have no duplicates. Then convert back to list.
        result.sort()
    # now iterate through substrings and sort the characters of each substring    
    #for each in 
    return result


# -----------------------------------
# PLAY GAME: Build data structures used for entire session and play game.
#
if __name__ == '__main__':
    word_list = load_words()
    point_dict = get_words_to_point(word_list)
    arranged_words = get_word_rearrangements(word_list)
    #print len(arranged_words)
    play_game(word_list)




## Problem 5 ##
# your response here.
# as many lines as you want.
# 
# pick_best_word()
#     This method creates a dictionary of every valid word mapped to the point value.  Then it iterates through the dictionary comparing the hand to the word.  If they word can be made from the hand then the word's score is compared to the word score of the earlier possible word.  The higher score word is retained.
#     Under this method the point value dictionary must be built and then the function iterates through comparing the hand to eveyr possible word.
#     Amortizing the time-cost of building the dictionary over each pick, the time complexity of this method grows with the length of the word list and independently from the size of the hand.  Adding letters to the hand will increase the time to execute but only negligibly.  I think the computational complexity of the function is linear.
# 
#   With a hand size of 7 letters the time to pick best word was less than .6 seconds.
#   With a hand size of 17 letters the time to pick best word was ~.6 seconds.
#   With a hand size of 25 letters the time to pick best word was ~.65 seconds.



#     
# pick_best_word_faster()
#   This method also begins by creating a dictionary.  Each value in the dictioanry is a valid word.  Each key is a alphabetized string of the letters in the word. E.g., {'acot':'taco'}.  Note that each key is unique but the value isn't necessarily unique.  The dictionary value for 'acot' could be 'coat'.  This is because the dictionary only needs to list every valid alpahbetized string of letters.  Not ever valid word.  This makes the dictionary the same size or shorter than the dictionary created in pick_best_word().  For the word list used in this problem the savings is ~14,000 entries (83667 words, 69091 dict keys)
#     Armed with this dictionary the function can take advantage of the speed of the "in dictionary" function, which I thinks is logarithmic.  The next part of this function is build a set of substrings of hand.  The function then iterates through this set of substrings checking if they are in the dictionary, and comparing their point value to the prior highest value string in the dictionary.  The function returns the highest point value value from the dictionary.
#     This method is much faster than pick_best_word() because is takes adavantice of the bysect search functionality built into search dicstionaries.  This bysect search algorithm grow logarithmically based on the length of dictionary.
#   Adding letters to the hand will increase the time to execute the function that creates the subsets, but they dicitonary search is the more significant funciton.  I think the computational complexity of the function is logarithmic.

#   With a hand size of 7 letters the time to pick best word was less than .001 seconds.
#   With a hand size of 17 letters the time to pick best word was between .3 and .5 seconds.
#   With a hand size of 25 letters the time to pick best word was between 15 and 45 seconds.

#   Comparing the two functions, it appears the pick_best_word_faster() is much faster for relatively small hands and any size dictionary.  But pick_best_word() is faster if the hands will be large.  Not surprisingly the 'better' function depends on the specifics of the problem.

Comments:

nickrt
12 months ago

For problem number 5 the pick_best_word_faster it is not logarithmic. The dictionary access speed is constant due to hashing, and based on your test results it isn't log because it increase exponentially when hand sizes increase.

Sign up or log in to comment

ochikobore (Self-grade: Could be better)
Submitted 1 year ago | Permalink | Time spent: 10 hours
# 6.00 Problem Set 6
#
# The 6.00 Word Game
#

import time
import random
import string

VOWELS = 'aeiou'
CONSONANTS = 'bcdfghjklmnpqrstvwxyz'
HAND_SIZE = 7

SCRABBLE_LETTER_VALUES = {
    'a': 1, 'b': 3, 'c': 3, 'd': 2, 'e': 1, 'f': 4, 'g': 2, 'h': 4, 'i': 1, 'j': 8, 'k': 5, 'l': 1, 'm': 3, 'n': 1, 'o': 1, 'p': 3, 'q': 10, 'r': 1, 's': 1, 't': 1, 'u': 1, 'v': 4, 'w': 4, 'x': 8, 'y': 4, 'z': 10
}

WORDLIST_FILENAME = "words.txt"

def load_words():
    """
    Returns a list of valid words. Words are strings of lowercase letters.
    
    Depending on the size of the word list, this function may
    take a while to finish.
    """
    print "Loading word list from file..."
    # inFile: file
    inFile = open(WORDLIST_FILENAME, 'r', 0)
    # wordlist: list of strings
    wordlist = []
    for line in inFile:
        wordlist.append(line.strip().lower())
    print "  ", len(wordlist), "words loaded."
    return wordlist

def get_frequency_dict(sequence):
    """
    Returns a dictionary where the keys are elements of the sequence
    and the values are integer counts, for the number of times that
    an element is repeated in the sequence.

    sequence: string or list
    return: dictionary
    """
    # freqs: dictionary (element_type -> int)
    freq = {}
    for x in sequence:
        freq[x] = freq.get(x,0) + 1
    return freq


def get_word_score(word, n):
    """
    Returns the score for a word. Assumes the word is a
    valid word.

    The score for a word is the sum of the points for letters
    in the word, plus 50 points if all n letters are used on
    the first go.

    Letters are scored as in Scrabble; A is worth 1, B is
    worth 3, C is worth 3, D is worth 2, E is worth 1, and so on.

    word: string (lowercase letters)
    returns: int >= 0
    """
    score = 0
    for letter in word:
        score += SCRABBLE_LETTER_VALUES[letter.lower()]
    if len(word) == n:
        score += 50
    return score

def display_hand(hand):
    """
    Displays the letters currently in the hand.

    For example:
       display_hand({'a':1, 'x':2, 'l':3, 'e':1})
    Should print out something like:
       a x x l l l e
    The order of the letters is unimportant.

    hand: dictionary (string -> int)
    """
    for letter in hand.keys():
        for j in range(hand[letter]):
             print letter,              # print all on the same line
    print                              # print an empty line


def deal_hand(n):
    """
    Returns a random hand containing n lowercase letters.
    At least n/3 the letters in the hand should be VOWELS.

    Hands are represented as dictionaries. The keys are
    letters and the values are the number of times the
    particular letter is repeated in that hand.

    n: int >= 0
    returns: dictionary (string -> int)
    """
    hand={}
    num_vowels = n / 3
    
    for i in range(num_vowels):
        x = VOWELS[random.randrange(0,len(VOWELS))]
        hand[x] = hand.get(x, 0) + 1
        
    for i in range(num_vowels, n):    
        x = CONSONANTS[random.randrange(0,len(CONSONANTS))]
        hand[x] = hand.get(x, 0) + 1
        
    return hand


def update_hand(hand, word):
    """
    Assumes that 'hand' has all the letters in word.
    In other words, this assumes that however many times
    a letter appears in 'word', 'hand' has at least as
    many of that letter in it. 

    Updates the hand: uses up the letters in the given word
    and returns the new hand, without those letters in it.

    word: string
    hand: dictionary (string -> int)    
    returns: dictionary (string -> int)
    """
    freq = get_frequency_dict(word)
    newhand = {}
    for char in hand:
        newhand[char] = hand[char]-freq.get(char,0)
    return newhand
    #return dict( ( c, hand[c] - freq.get(c,0) ) for c in hand )
        

def is_valid_word(word, hand, word_list):
    """
    Returns True if word is in the word_list and is entirely
    composed of letters in the hand. Otherwise, returns False.
    Does not mutate hand or word_list.
    
    word: string
    hand: dictionary (string -> int)
    word_list: list of lower case strings
    """
    freq = get_frequency_dict(word)
    for letter in word:
        if freq[letter] > hand.get(letter, 0):
            return False
    return word in word_list


def play_hand(hand, word_list):
    """
    Allows the user to play the given hand, as follows:

    * The hand is displayed.
    
    * The user may input a word.

    * An invalid word is rejected, and a message is displayed asking
      the user to choose another word.

    * When a valid word is entered, it uses up letters from the hand.

    * After every valid word: the score for that word is displayed,
      the remaining letters in the hand are displayed, and the user
      is asked to input another word.

    * The sum of the word scores is displayed when the hand finishes.

    * The hand finishes when there are no more unused letters.
      The user can also finish playing the hand by inputing a single
      period (the string '.') instead of a word.

      hand: dictionary (string -> int)
      word_list: list of lowercase strings
    """    
    total = 0
    initial_handlen = sum(hand.values())
    time_limit = input("Enter time limit, in seconds, for players: ")*1.0
    time_remaining = time_limit
    
    while sum(hand.values()) > 0:
        
        print 'Current Hand:',
        display_hand(hand)
        
        start_time = time.time()
        
        userWord = raw_input('Enter word, or a . to indicate that you are finished: ')
        if userWord == '.':
             break
        else:
            isValid = is_valid_word(userWord, hand, word_list)

            if not isValid:
                
                print 'Invalid word, please try again.'
                
            else:
                end_time = time.time()
                total_time = end_time - start_time
            
                time_remaining = time_remaining - total_time
                
                if time_remaining <= 0.0:

                    print 'It took %0.2f seconds to provide an answer.' % total_time
                    print 'Total time exceeds %d seconds. You scored %d points' % (time_limit, total)
                    break
            
                print 'It took %0.2f seconds to provide an answer.' % total_time
                print 'You have %0.2f seconds remaining.' % time_remaining
                
                points = get_word_score(userWord, initial_handlen)
                total += points    
                print '%s earned %d points. Total: %d points' % (userWord, points, total)
                hand = update_hand(hand, userWord)
                
    print 'Total score: %d points.' % total



def play_game(word_list):
    """
    Allow the user to play an arbitrary number of hands.

    * Asks the user to input 'n' or 'r' or 'e'.

    * If the user inputs 'n', let the user play a new (random) hand.
      When done playing the hand, ask the 'n' or 'e' question again.

    * If the user inputs 'r', let the user play the last hand again.

    * If the user inputs 'e', exit the game.

    * If the user inputs anything else, ask them again.
    """

    hand = deal_hand(HAND_SIZE) # random init
    while True:
        cmd = raw_input('Enter n to deal a new hand, r to replay the last hand, or e to end game: ')
        if cmd == 'n':
            hand = deal_hand(HAND_SIZE)
            play_hand(hand.copy(), word_list)
            print
        elif cmd == 'r':
            play_hand(hand.copy(), word_list)
            print
        elif cmd == 'e':
            break
        else:
            print "Invalid command."

def get_words_to_points(word_list):
    """
    Basically takes word_list and turns it into a dictionary list where the elements are the words
    and the values are the points of each word.

    Returns the dictionary of words and points.
    """
    
    points_dict = {}

    for i in xrange(len(word_list)):
        
        points_dict[word_list[i]] = get_word_score(word_list[i], HAND_SIZE)

    return points_dict

def pick_best_word(hand, points_dict):
    """
    Selects the best word based on points within a set of letters.
    hand = list of your letters
    """
    
    hand_freq = get_frequency_dict(hand)

    bestWord = ""
    
    for word in points_dict:
        
        hasAllLetters = True
        
        word_freq = get_frequency_dict(word)

        #check if 
        for letter in word_freq:

            if word_freq[letter] > hand.count(letter):
                hasAllLetters = False
                break

        if hasAllLetters:
            if points_dict[word] > get_word_score(bestWord,HAND_SIZE):
                bestWord = word
                
    return bestWord

def get_time_limit(points_dict, k):
    """
    Return the time limit for the computer player as a funciton of the multiplier k.
    """

    start_time = time.time()
    for word in points_dict:
        get_frequency_dict(word)
        get_word_score(word, HAND_SIZE)
    end_time = time.time()

    return (end_time - start_time) * k
    
    

##if __name__ == '__main__':
##    word_list = load_words()
##    play_game(word_list)
DvorakAJS (Self-grade: Could be better)
Submitted 1 year ago | Permalink | Time spent: 8 hours

This one took ages, I probably finished it in 10 times or something. Didn't do last and stole a little part of someone else's code because I was lazy. I truly am a grade A student.

# Problem Set 5: 6.00 Word Game
# Name: 
# Collaborators: 
# Time: 
#

import random
import string
import time

VOWELS = 'aeiou'
CONSONANTS = 'bcdfghjklmnpqrstvwxyz'
HAND_SIZE = 7

SCRABBLE_LETTER_VALUES = {
    'a': 1, 'b': 3, 'c': 3, 'd': 2, 'e': 1, 'f': 4, 'g': 2, 'h': 4, 'i': 1, 'j': 8, 'k': 5, 'l': 1, 'm': 3, 'n': 1, 'o': 1, 'p': 3, 'q': 10, 'r': 1, 's': 1, 't': 1, 'u': 1, 'v': 4, 'w': 4, 'x': 8, 'y': 4, 'z': 10
}

# -----------------------------------
# Helper code
# (you don't need to understand this helper code)

WORDLIST_FILENAME = "words.txt"

def load_words():
    """
    Returns a list of valid words. Words are strings of lowercase letters.
    
    Depending on the size of the word list, this function may
    take a while to finish.
    """
    print "Loading word list from file..."
    # inFile: file
    inFile = open(WORDLIST_FILENAME, 'r', 0)
    # wordlist: list of strings
    wordlist = []
    for line in inFile:
        wordlist.append(line.strip().lower())
    print "  ", len(wordlist), "words loaded."
    return wordlist

def get_frequency_dict(sequence):
    """
    Returns a dictionary where the keys are elements of the sequence
    and the values are integer counts, for the number of times that
    an element is repeated in the sequence.

    sequence: string or list
    return: dictionary
    """
    # freqs: dictionary (element_type -> int)
    freq = {}
    for x in sequence:
        freq[x] = freq.get(x,0) + 1
    return freq


# (end of helper code)
# -----------------------------------

#
# Problem #1: Scoring a word
#
def get_word_score(word, n):
    """
    Returns the score for a word. Assumes the word is a
    valid word.

    The score for a word is the sum of the points for letters
    in the word, plus 50 points if all n letters are used on
    the first go.

    Letters are scored as in Scrabble; A is worth 1, B is
    worth 3, C is worth 3, D is worth 2, E is worth 1, and so on.

    word: string (lowercase letters)
    returns: int >= 0
    """
    # TO DO ...
    score=0
    for i in word:
        score+= SCRABBLE_LETTER_VALUES[i]
    if len(word)==n: score+=50
    return score

 

#
# Make sure you understand how this function works and what it does!
#
def display_hand(hand):
    """
    Displays the letters currently in the hand.

    For example:
       display_hand({'a':1, 'x':2, 'l':3, 'e':1})
    Should print out something like:
       a x x l l l e
    The order of the letters is unimportant.

    hand: dictionary (string -> int)
    """
    for letter in hand.keys():
        for j in range(hand[letter]):
            print letter,              # print all on the same line
    print                              # print an empty line

#
# Make sure you understand how this function works and what it does!
#
def deal_hand(n):
    """
    Returns a random hand containing n lowercase letters.
    At least n/3 the letters in the hand should be VOWELS.

    Hands are represented as dictionaries. The keys are
    letters and the values are the number of times the
    particular letter is repeated in that hand.

    n: int >= 0
    returns: dictionary (string -> int)
    """
    hand={}
    num_vowels = n / 3
    
    for i in range(num_vowels):
        x = VOWELS[random.randrange(0,len(VOWELS))]
        hand[x] = hand.get(x, 0) + 1
        
    for i in range(num_vowels, n):    
        x = CONSONANTS[random.randrange(0,len(CONSONANTS))]
        hand[x] = hand.get(x, 0) + 1
        
    return hand

#
# Problem #2: Update a hand by removing letters
#
def update_hand(hand, word):
    """
    Assumes that 'hand' has all the letters in word.
    In other words, this assumes that however many times
    a letter appears in 'word', 'hand' has at least as
    many of that letter in it. 

    Updates the hand: uses up the letters in the given word
    and returns the new hand, without those letters in it.

    Has no side effects: does not mutate hand.

    word: string
    hand: dictionary (string -> int)    
    returns: dictionary (string -> int)
    """
    # TO DO ...
    hand_copy=hand
    for letter in word:
        hand_copy[letter] -= 1
        if hand_copy[letter]==0: del hand_copy[letter]
    
    return hand_copy
        

#
# Problem #3: Test word validity
#
def is_valid_word(word, hand, points_dict):
    """
    Returns True if word is in the word_list and is entirely
    composed of letters in the hand. Otherwise, returns False.
    Does not mutate hand or word_list.
    
    word: string
    hand: dictionary (string -> int)
    word_list: list of lowercase strings
    """
    # TO DO ...
    hand_copy=hand.copy()
    truth = 0
    if word in points_dict:
        for letter in word:
            if hand_copy.get(letter,0) >0:
                truth+=1

            if truth==len(word): return word in points_dict
    return False


def get_words_to_points(word_list):
    """
    Return a dict that maps every word in word_list to its point value.
    """
    global points_dict
    points_dict={}
    for word in word_list:
        points_dict[word]=get_word_score(word,HAND_SIZE)

def get_time_limit(points_dict, k):
    """
    Return the time limit for the computer player as a function of the
    multiplier k.
    points_dict should be the same dictionary that is created by
    get_words_to_points.
    """
    start_time = time.time()
    # Do some computation. The only purpose of the computation is so we can
    # figure out how long your computer takes to perform a known task.
    for word in points_dict:
        get_frequency_dict(word)
        get_word_score(word, HAND_SIZE)
    end_time = time.time()
    return (end_time - start_time) * k


def pick_best_word(hand, points_dict):     
    """
    Return the highest scoring word from points_dict that can be made with the
    given hand.
    Return '.' if no words can be made with the given hand.
    """
    high_score=0
    best_word = '.'
    for word in points_dict: #neem een woord uit alle woorden
        letters=get_frequency_dict(word)    #zet om zoals hand
        all_letters_check=0             #nodig om te zien of alle letters kloppen, niet zomaar één
        number_of_letters=0
        for letter in word:
            number_of_letters+=letters[letter]
            #print word,letter,number_of_letters
        for letter in word:         #neem één letter
            if hand.get(letter,0)>=letters[letter]:         #als die letter vaker/gelijk in je hand voorkomt dan in het woord
                #print "here", word, number_of_letters, all_letters_check
                all_letters_check+=letters[letter]            #doe je all_letters_check + het aantal letters in het woord uit dict
                if all_letters_check==number_of_letters and get_word_score(word,HAND_SIZE) > high_score:
                    #print "one lower- -->", "letter:",letter, word, number_of_letters, all_letters_check, best_word
                    best_word=word
                    high_score= get_word_score(word,HAND_SIZE)
           
    print best_word
    return best_word

def wordsort(word):
    sorted_word=''
    word=list(word)
    word.sort()
    for letter in word:
        sorted_word=sorted_word[:]+letter
    return sorted_word
     


def get_word_rearrangements(word_list):
    rearrange_dict={}
    sortword=''
    for word in word_list:
        sortword=wordsort(word)
        rearrange_dict[sortword]=word
    return rearrange_dict
        
        
def createCombinations(string): #taken from someone else on opencourseware
    if len(string) == 1:
        return [string]

    solutions = []
            
    # Let T = the current string of letters
    # Let S = the current starting letter in our string
    # Let R = the rest of the string starting with the letter
    # immediately following S
    # Let K = the solutions for R
    # 
    # The solution for T is is the set of 
    # [S] + [S + K[0], S + K[1], S + K[2], ..., S + K[n]] + K

    solutions.append(string[0])
    subsolutions = createCombinations(string[1:])

    for subso in subsolutions:
        solutions.append(string[0] + subso)
        solutions.append(subso)

    return solutions

def turn_back(hand):
    """
takes your hand as a dictionary and returns it as a string.
not a pretty solution to the problem I had but meh
    """
    hand_string=''
    for letter in hand.keys():
        for j in range(hand[letter]):
              hand_string=hand_string[:]+letter
    return hand_string



def pick_best_word_faster(hand, rearrange_dict):
    string_hand=turn_back(hand)
    hands = createCombinations(string_hand)
    high_score=0
    best_word='.'
    word_in_dict=''
    for hand in hands:
        hand=wordsort(hand)
        if hand in rearrange_dict:
            word_in_dict=rearrange_dict[hand]
            if get_word_score(word_in_dict,HAND_SIZE) > high_score:
                best_word=word_in_dict
                high_score=get_word_score(word_in_dict,HAND_SIZE) 
            
    return best_word
    
        




#
# Problem #4: Playing a hand
#
def play_hand(hand, word_list):
    """
    Allows the user to play the given hand, as follows:

    * The hand is displayed.
    
    * The user may input a word.

    * An invalid word is rejected, and a message is displayed asking
      the user to choose another word.

    * When a valid word is entered, it uses up letters from the hand.

    * After every valid word: the score for that word and the total
      score so far are displayed, the remaining letters in the hand 
      are displayed, and the user is asked to input another word.

    * The sum of the word scores is displayed when the hand finishes.

    * The hand finishes when there are no more unused letters.
      The user can also finish playing the hand by inputing a single
      period (the string '.') instead of a word.

    * The final score is displayed.

      hand: dictionary (string -> int)
      word_list: list of lowercase strings
    """
    # TO DO ...


    time_limit=int(raw_input("enter the time limit for the game in seconds: "))
    
    computer_word=0
    score=0
    starting_length=len(hand)
    timestart=time.time()
    while computer_word != "." and len(hand)>0:
        print
        display_hand(hand)
        #word_input=raw_input("please insert a word you'd like to play or \".\" to stop your turn > ")
        computer_word=pick_best_word_faster(hand, rearrange_dict)
        print computer_word
        time2=time.time()
        time_taken=round(float(time2-timestart),2)
        time_limit -= time_taken
        if is_valid_word(computer_word,hand,word_list):
            update_hand(hand,computer_word)
            
            
            if time_taken <1: time_taken=1
            if time_limit>0:
                score+=(get_word_score(computer_word,starting_length))/time_taken
                print 'it took you',time_taken,'seconds to answer'
                print "you scored",round((get_word_score(computer_word,starting_length))/time_taken,2),"with this word"
                print "your total score is: ",round(score,2)
                print "you have %s time remaining" % (time_limit)
                timestart=time.time()
            else:
                print "I'm sorry, you ran out of time..."
                break
        else:
            if time_limit<=0:
                print "I'm sorry, you ran out of time..."
                break
            if computer_word != ".": print "this is not a valid word"

    print "total end score is: ",round(score,2)
            
        


    

#
# Problem #5: Playing a game
# Make sure you understand how this code works!
# 
def play_game(word_list):
    """
    Allow the user to play an arbitrary number of hands.

    * Asks the user to input 'n' or 'r' or 'e'.

    * If the user inputs 'n', let the user play a new (random) hand.
      When done playing the hand, ask the 'n' or 'e' question again.

    * If the user inputs 'r', let the user play the last hand again.

    * If the user inputs 'e', exit the game.

    * If the user inputs anything else, ask them again.
    """
    # TO DO ...
##    print "play_game not implemented."         # delete this once you've completed Problem #4
##    play_hand(deal_hand(HAND_SIZE), word_list) # delete this once you've completed Problem #4
    
    ## uncomment the following block of code once you've completed Problem #4
    hand = deal_hand(HAND_SIZE) # random init
    while True:
        cmd = raw_input('Enter n to deal a new hand, r to replay the last hand, or e to end game: ')
        if cmd == 'n':
            #time_limit=int(raw_input("enter the time limit for the game in seconds: "))
            hand = deal_hand(HAND_SIZE)
            
            play_hand(hand.copy(), word_list)
            print
        elif cmd == 'r':
            play_hand(hand.copy(), word_list)
            print
        elif cmd == 'e':
            break
        else:
            print "Invalid command."

#
# Build data structures used for entire session and play game
#
if __name__ == '__main__':
    word_list = load_words()
    get_words_to_points(word_list)
    time_limit=get_time_limit(points_dict, 1)
    rearrange_dict= get_word_rearrangements(word_list)
    play_game(word_list)
    
tuckertuck (Self-grade: Pretty good)
Submitted 1 year ago | Permalink | Time spent: 9 hours

Problem 5: The complexity of pick_best_word is O(n^2) or Quadratic. You have to iterate the length of the word_list and then the length of each word. Since there are 83667 words in the list, then you need to iterate 83667 * len(word) which is anywhere from 2 to 7 letters long. It took my computer typically 0.34 seconds to find a word.

The complexity of pick_best_word_faster is O(2^n) or Exponential. While the algorithm is more complex, it is only limited by the maximum times you have to iterate through the Hand. Since there are 7 letters maximum in the hand then it only needs to make 2^7 or 128 steps. It took my computer typically 0.001 seconds to find a word

# 6.00 Problem Set 6
#
# The 6.00 Word Game
# Name: tuckertuck
# Time: 9:00

import random
import string
import time

VOWELS = 'aeiou'
CONSONANTS = 'bcdfghjklmnpqrstvwxyz'
HAND_SIZE = 7

SCRABBLE_LETTER_VALUES = {
    'a': 1, 'b': 3, 'c': 3, 'd': 2, 'e': 1, 'f': 4, 'g': 2, 'h': 4, 'i': 1, 'j': 8, 'k': 5, 'l': 1, 'm': 3, 'n': 1, 'o': 1, 'p': 3, 'q': 10, 'r': 1, 's': 1, 't': 1, 'u': 1, 'v': 4, 'w': 4, 'x': 8, 'y': 4, 'z': 10
}

# -----------------------------------
# Helper code
# (you don't need to understand this helper code)

WORDLIST_FILENAME = "words.txt"

def load_words():
    """
    Returns a list of valid words. Words are strings of lowercase letters.
    
    Depending on the size of the word list, this function may
    take a while to finish.
    """
    print "Loading word list from file..."
    # inFile: file
    inFile = open(WORDLIST_FILENAME, 'r', 0)
    # wordlist: list of strings
    wordlist = []
    for line in inFile:
        wordlist.append(line.strip().lower())
    print "  ", len(wordlist), "words loaded."
    return wordlist

def get_frequency_dict(sequence):
    """
    Returns a dictionary where the keys are elements of the sequence
    and the values are integer counts, for the number of times that
    an element is repeated in the sequence.

    sequence: string or list
    return: dictionary
    """
    # freqs: dictionary (element_type -> int)
    freq = {}
    for x in sequence:
        freq[x] = freq.get(x,0) + 1
    return freq


# (end of helper code)
# -----------------------------------

#
# Scoring a word
#
def get_word_score(word, n):
    """
    Returns the score for a word. Assumes the word is a
    valid word.

    The score for a word is the sum of the points for letters
    in the word, plus 50 points if all n letters are used on
    the first go.

    Letters are scored as in Scrabble; A is worth 1, B is
    worth 3, C is worth 3, D is worth 2, E is worth 1, and so on.

    word: string (lowercase letters)
    returns: int >= 0
    """
    score = 0
    for letter in word:
        score += SCRABBLE_LETTER_VALUES[letter.lower()]
    if len(word) == n:
        score += 50
    return score

#
# Make sure you understand how this function works and what it does!
#
def display_hand(hand):
    """
    Displays the letters currently in the hand.

    For example:
       display_hand({'a':1, 'x':2, 'l':3, 'e':1})
    Should print out something like:
       a x x l l l e
    The order of the letters is unimportant.

    hand: dictionary (string -> int)
    """
    for letter in hand.keys():
        for j in range(hand[letter]):
             print letter,              # print all on the same line
    print                              # print an empty line

#
# Make sure you understand how this function works and what it does!
#
def deal_hand(n):
    """
    Returns a random hand containing n lowercase letters.
    At least n/3 the letters in the hand should be VOWELS.

    Hands are represented as dictionaries. The keys are
    letters and the values are the number of times the
    particular letter is repeated in that hand.

    n: int >= 0
    returns: dictionary (string -> int)
    """
    hand={}
    num_vowels = n / 3
    
    for i in range(num_vowels):
        x = VOWELS[random.randrange(0,len(VOWELS))]
        hand[x] = hand.get(x, 0) + 1
        
    for i in range(num_vowels, n):    
        x = CONSONANTS[random.randrange(0,len(CONSONANTS))]
        hand[x] = hand.get(x, 0) + 1    

    #hand = {'g':1,'o':1,'l':1,'d':1,'e':1,'s':1,'t':1}
    return hand

#
# Update a hand by removing letters
#
def update_hand(hand, word):
    """
    Assumes that 'hand' has all the letters in word.
    In other words, this assumes that however many times
    a letter appears in 'word', 'hand' has at least as
    many of that letter in it. 

    Updates the hand: uses up the letters in the given word
    and returns the new hand, without those letters in it.

    word: string
    hand: dictionary (string -> int)    
    returns: dictionary (string -> int)
    """
    freq = get_frequency_dict(word)
    newhand = {}
    for char in hand:
        newhand[char] = hand[char]-freq.get(char,0)
    return newhand
    #return dict( ( c, hand[c] - freq.get(c,0) ) for c in hand )
        

#
# Test word validity
#
def is_valid_word(word, hand, points_dict):
    """
    Returns True if word is in the word_list and is entirely
    composed of letters in the hand. Otherwise, returns False.
    Does not mutate hand or word_list.
    
    word: string
    hand: dictionary (string -> int)
    word_list: list of lowercase strings
    """
    freq = get_frequency_dict(word)
    for letter in word:
        if freq[letter] > hand.get(letter, 0):
            return False
    return word in points_dict

# Problem 1 & 2: Computing Time
#
# Playing a hand
#
def play_hand(hand, word_list, clock):
    """
    Allows the user to play the given hand, as follows:

    * The hand is displayed.
    
    * The user may input a word.

    * An invalid word is rejected, and a message is displayed asking
      the user to choose another word.

    * When a valid word is entered, it uses up letters from the hand.

    * After every valid word: the score for that word is displayed,
      the remaining letters in the hand are displayed, and the user
      is asked to input another word.

    * The sum of the word scores is displayed when the hand finishes.

    * The hand finishes when there are no more unused letters.
      The user can also finish playing the hand by inputing a single
      period (the string '.') instead of a word.

      hand: dictionary (string -> int)
      word_list: list of lowercase strings
    """    
    total = 0
    update_clock = clock
    initial_handlen = sum(hand.values())
    while sum(hand.values()) > 0:
        print
        print 'Current Hand:',
        display_hand(hand)
        start_time = time.time()
##      userWord = raw_input('Enter word, or a . to indicate that you are finished: ') # Human Player
##        userWord = pick_best_word(hand, points_dict) # Computer Player
        userWord = pick_best_word_faster(hand, rearrange_dict) # Even Faster Computer Player
        if userWord == '.':
             break
        else:
            isValid = is_valid_word(userWord, hand, points_dict)
            if not isValid:
                print 'Invalid word, please try again.'
            else:
                end_time = time.time()
                total_time = (end_time - start_time)#*(1+time_limit)
                update_clock -= total_time
                print 'It took %0.3f seconds to provide an answer.' % total_time
                if update_clock >= 0:
                    if total_time == 0: points = get_word_score(userWord, initial_handlen)
                    else: points = get_word_score(userWord, initial_handlen)/(1+total_time)
                    #print total_time
                    total += points
                    print 'You have %0.2f seconds remaining.' % update_clock
                    print '%s earned %0.2f points. Total: %0.2f points.' % (userWord, points, total)
                    hand = update_hand(hand, userWord)
                else:
                    print 'Total time exceeds %s seconds. You scored %0.2f points.' % (clock, total)
                    break
    print 'Total score: %0.2f points.' % total



#
# Playing a game
# Make sure you understand how this code works!
# 
def play_game(word_list):
    """
    Allow the user to play an arbitrary number of hands.

    * Asks the user to input 'n' or 'r' or 'e'.

    * If the user inputs 'n', let the user play a new (random) hand.
      When done playing the hand, ask the 'n' or 'e' question again.

    * If the user inputs 'r', let the user play the last hand again.

    * If the user inputs 'e', exit the game.

    * If the user inputs anything else, ask them again.
    """

    hand = deal_hand(HAND_SIZE) # random init
    
    while True:
        cmd = raw_input('Enter n to deal a new hand, r to replay the last hand, or e to end game: ')
        
        if cmd == 'n':
            #chess_clock = int(raw_input('Enter time limit, in seconds, for players: '))
            chess_clock = time_limit
            hand = deal_hand(HAND_SIZE)
            play_hand(hand.copy(), word_list, chess_clock)
            print
        elif cmd == 'r':
            play_hand(hand.copy(), word_list, chess_clock)
            print
        elif cmd == 'e':
            break
        else:
            print "Invalid command."


#
# Problem #3: Computer Player
#
def pick_best_word(hand, points_dict):
    """
    Return the highest scoring word from points_dict that can be made with the given hand.
    Return '.' if no words can be made with the given hand.
    """
    #print hand
    ans = False
    best = False
    score = 0
    for word in points_dict:
        word_freq = get_frequency_dict(word)
        #print word
        for letter in word_freq:
            #print letter
            if letter in hand and word_freq[letter] <= hand[letter]:
                ans = True
                continue
            else:
                ans = False
                break
        if ans == True:
            #print word
            new_score = points_dict[word]
            if new_score > score:
                score = new_score
                best_word = word
                best = True
    
    if best == True:
        #print best_word
        return best_word
    else: return '.'

    
def get_words_to_points(word_list):
    """
    Return a dict that maps every word in word_list to its point value.
    """  
    points_dict = {}
    for word in word_list:
        points_dict[word] = get_word_score(word, HAND_SIZE)
    return points_dict

def get_time_limit(points_dict, k):
        """
        Return the time limit for the computer player as a function of the
        multiplier k.
        points_dict should be the same dictionary that is created by
        get_words_to_points.
        """
        start_time = time.time()
        # Do some computation. The only purpose of the computation is so we can
        # figure out how long your computer takes to perform a known task.
        for word in points_dict:
             get_frequency_dict(word)
             get_word_score(word, HAND_SIZE)
        end_time = time.time()
        return (end_time - start_time) * k

#
# Problem #4: Even Faster Computer Player
#
def pick_best_word_faster(hand, rearrange_dict):
    """
    Return the highest scoring word from points_dict that can be made with the given hand.
    Return '.' if no words can be made with the given hand.
    Uses a binary counter to map whether a letter is part of the subset or not
    """
    ans = False
    best = False
    score = 0
    sorted_hand = sort_dic(hand)

    #print sorted_hand
    if sorted_hand in rearrange_dict:
        #print points_dict[rearrange_dict[sorted_hand]]
        return rearrange_dict[sorted_hand]
    elif len(sorted_hand) > 1:
        ## Binary represents whether letter gets used in the hand.
        for a in range(2):
            for b in range(2):
                for c in range(2):
                    for d in range(2):
                        for e in range(2):
                            for f in range(2):
                                for g in range(2):
                                    binary = (g,f,e,d,c,b,a)
                                    dic = dict(zip(sorted_hand,binary))
                                    #print dic
                                    sorted_dic = sort_dic(dic)
                                    #print binary, sorted_dic
                                    #print sorted_hand
                                    if sorted_dic in rearrange_dict:
                                        word = rearrange_dict[sorted_dic]

                                        #print word
                                        new_score = points_dict[word]
                                        if new_score > score:
                                            score = new_score
                                            best_word = word
                                            best = True

    if best == True:
        #print best_word
        return best_word
    else: return '.'

def sort_dic(hand):
    """
    Returns Tuple of alphabetized letters in a given Dictionary of value >= 1
    """
    l = []
    for letter in hand.keys():
        for j in range(hand[letter]):
            l.append(letter)    
    sorted_tuple = tuple(sorted(l))
    return sorted_tuple

def get_word_rearrangements(word_list):
    rearrange_dict = {}
    for word in word_list:
        dorw = tuple(sorted(word))
        #print dorw, word
        rearrange_dict[dorw] = word
    return rearrange_dict

#
# Build data structures used for entire session and play game
#
if __name__ == '__main__':
    word_list = load_words()
    rearrange_dict = get_word_rearrangements(word_list)
    points_dict = get_words_to_points(word_list)
    time_limit = get_time_limit(points_dict, 2)
    play_game(word_list)
mercutio22 (Self-grade: Pretty good)
Submitted 2 years ago | Permalink | Time spent: 1 minute
# Problem Set 5: 6.00 Word Game
# Name: 
# Collaborators: 
# Time: 
#

import random
import string
import time

VOWELS = 'aeiou'
CONSONANTS = 'bcdfghjklmnpqrstvwxyz'
HAND_SIZE = 7

SCRABBLE_LETTER_VALUES = {
    'a': 1, 'b': 3, 'c': 3, 'd': 2, 'e': 1, 'f': 4, 'g': 2, 'h': 4, 'i': 1, 'j': 8, 'k': 5, 'l': 1, 'm': 3, 'n': 1, 'o': 1, 'p': 3, 'q': 10, 'r': 1, 's': 1, 't': 1, 'u': 1, 'v': 4, 'w': 4, 'x': 8, 'y': 4, 'z': 10
}

# -----------------------------------
# Helper code
# (you don't need to understand this helper code)

WORDLIST_FILENAME = "words.txt"

def load_words():
    """
    Returns a list of valid words. Words are strings of lowercase letters.
    
    Depending on the size of the word list, this function may
    take a while to finish.
    """
    print "Loading word list from file..."
    # inFile: file
    inFile = open(WORDLIST_FILENAME, 'r', 0)
    # wordlist: list of strings
    wordlist = []
    for line in inFile:
        wordlist.append(line.strip().lower())
    print "  ", len(wordlist), "words loaded."
    print
    
    return wordlist

def get_frequency_dict(word):
    """
    Returns a dictionary where the keys are letters of the word
    and the values are integer counts, for the number of times that
    a letter is repeated in the word.

    sequence: string or list
    return: dictionary
    """
    # freqs: dictionary (element_type -> int)
    freq = {}
    for x in word:
        freq[x] = freq.get(x,0) + 1#adds one to the value of the key:value pair each time the letter 'key' is found in 'sequence'
    return freq


# (end of helper code)
# -----------------------------------

#
# Problem #1: Scoring a word
#
def get_word_score(word, n):
    """
    Returns the score for a word. Assumes the word is a
    valid word.

    The score for a word is the sum of the points for letters
    in the word, plus 50 points if all n letters are used on
    the first go.

    Letters are scored as in Scrabble; A is worth 1, B is
    worth 3, C is worth 3, D is worth 2, E is worth 1, and so on.

    word: string (lowercase letters)
    returns: int >= 0
    """
    # TO DO ...
    score = 0
    for letter in word:
        #print letter, 'adds to score:', SCRABBLE_LETTER_VALUES[letter]
        score += SCRABBLE_LETTER_VALUES[letter]
    if n == len(word):
        return score + 50
    else:        
        return score
    #######################       

#
# Make sure you understand how this function works and what it does!
#
def display_hand(hand):
    """
    Displays the letters currently in the prin.

    For example:
       display_hand({'a':1, 'x':2, 'l':3, 'e':1})
    Should print out something like:
       a x x l l l e
    The order of the letters is unimportant.

    hand: dictionary (string -> int)
    """

    
    for letter in hand.keys():
        for j in range(hand[letter]):
            print letter,              # print all on the same line
    print                              # print an empty line

#
# Make sure you understand how this function works and what it does!
#
def deal_hand(n):
    """
    Returns a random hand containing n lowercase letters.
    At least n/3 the letters in the hand should be VOWELS.

    Hands are represented as dictionaries. The keys are
    letters and the values are the number of times the
    particular letter is repeated in that hand.

    n: int >= 0
    returns: dictionary (string -> int)
    """

    
    hand={}
    num_vowels = n / 3
    
    for i in range(num_vowels):
        x = VOWELS[random.randrange(0,len(VOWELS))]#chooses a random vowel from the 'VOWELS' list
        hand[x] = hand.get(x, 0) + 1#adds key-value pair of 'ramdonly chosen vowel':number of previously present said vowel plus one.
        
    for i in range(num_vowels, n):    
        x = CONSONANTS[random.randrange(0,len(CONSONANTS))]
        hand[x] = hand.get(x, 0) + 1
        
    return hand

#
# Problem #2: Update a hand by removing letters
#
def update_hand(hand, word):
    """
    Assumes that 'hand' has all the letters in word.
    In other words, this assumes that however many times
    a letter appears in 'word', 'hand' has at least as
    many of that letter in it. 

    Updates the hand: uses up the letters in the given word
    and returns the new hand, without those letters in it.

    Has no side effects: does not mutate hand.

    word: string
    hand: dictionary (string -> int)    
    returns: dictionary (string -> int)
    """
    # TO DO ...
    remaining_hand=hand.copy()
    for letter in word:
        remaining_hand[letter]=remaining_hand.get(letter,0) - 1
    for letter in remaining_hand.keys():
        if remaining_hand[letter] == 0:
            del remaining_hand[letter]
    return remaining_hand
            

#
# Problem #3: Test word validity
#
def is_valid_word(word, hand, points_dict):
    """
    Returns True if word is in the points_dict and is entirely
    composed of letters in the hand. Otherwise, returns False.
    Does not mutate hand or points_dict.
    
    word: string
    hand: dictionary (string -> int)
    points_dict: list of lowercase strings
    """
    # TO DO ...
    frequency = get_frequency_dict(word)#gets the frequency of letters ocurring in 'word'
    for letter in word:
        if letter not in hand or hand[letter]<frequency[letter]:
            return False
    if word in points_dict:
        return True
    else: return False
    
#
# Problem #4: Playing a hand
#
def get_words_to_points(word_list,n):
    """
    Return a dict that maps every word in word_list to its point value.
    word_list = list of words
    n = maximum size of hand
    """
    points_dict={}
    print 'Computing word scores...'
    for word in word_list:
        points_dict[word] =  get_word_score (word,n)
    return points_dict
    

##def get_sorted_words_to_points(rearrange_dict,n):
    ##"""
    ##Return a dict that maps every word in rearrange_dict to its point value.
    ##rearrange_dict = list of words in sorted characters
    ##n = maximum size of hand
    ##"""
    ##
    ##points_dict={}
    ##print 'Computing word scores...'
    ##for word in rearrange_dict.values():
        ##points_dict[word] =  get_word_score (word,n)
    ##return points_dict
        
##def reverse_lookup(dict,value):
    ##""" Gets the first key of value: 'value' in dict.
    ##
    ##If no such key is in it, return'.'
    ##"""
    ##for key in dict:
        ##if value == dict[key]:
            ##return key
    ##else:
        ##return '.'
        
def pick_best_word(hand, points_dict):
    """
    Return the highest scoring word from points_dict that can be made with the
    given hand.
    Return '.' if no words can be made with the given hand.
    """
    best_score = 0#will store the best possible score
    best_word = 'which_is_it' #will hold best scoring word
    for word in points_dict.keys():
        frequencies = get_frequency_dict(word)
        for letter in word:  #dismisses words which contain
            if letter not in hand: # leters not in hand
                break
            elif frequencies[letter] > hand[letter]:#dismisses word if it has more of letter then hand has.
                break
            else:
                continue
        else:
            if points_dict[word] > best_score:
                best_score = points_dict[word]
                best_word = word
    if best_score == 0: #means no word in points_dict(the word list) could be made with letters in hand
        print 'It is not possible to form a word with the letters in this hand.'
        return '.'
    else:
        print 'The best word in',
        display_hand(hand),
        print 'is:', best_word
        return best_word

#pick_best_word(deal_hand(7), get_words_to_points(load_words(), 7)) #test pick_best_word function
def get_word_arrangements(word_list):
    """Creates a dictionary word-->sorted letters"""
    rearrange_dict = {}
    for word in word_list:
        letter_list=list(word)#a list of letters in word
        letter_list.sort()
        newword=''.join(letter_list)
        rearrange_dict[newword]=word
    #print rearrange_dict
    return rearrange_dict
    
def get_substrings_of(string):
    """Retrieves a list of all possible substrings in a string.
    
    Works on the premisse that the substrings subset of a string with 
    one more character is the formed by taking all the substrings in the
    current subset and adding this character to every element.
    """
    
    result = []
    if len(string) == 1: #the base case
        result.append(string)
        pass
    else:
        for item in get_substrings_of(string[:-1]):
            result.append(item)
            substring = item + string[-1]
            result.append(substring)
        result.append(string[-1])
    result = list(set( result )) #remove dupes
    return result
    
    
def pick_best_word_faster(hand, rearrange_dict): #currently its not returning the best word!!
    """Well, it should pick the highest-scoring word faster!
    
    Return the highest scoring word from points_dict that can be made with the
    given hand.
    Return '.' if no words can be made with the given hand.
    """
    
    #this part gets the hand dictionary and outputs a list of 
    #its letters 
    letter_list = []
    for letter in hand.keys():
        for j in range(hand[letter]):
            letter_list.append(letter)
    letter_list.sort() 
    
    #display_hand(hand)
    #print letter_list
    
    sortedhandstring = ''
    for letter in letter_list:
        sortedhandstring += letter
    #print word 
    
    #this part takes a list of substrings an gives a list of sorted sub-
    #strings
    sorted_substring_list=[]
    for substring in get_substrings_of(sortedhandstring):
        substring_letters=[]
        for letter in substring:
            substring_letters.append(letter)
        substring_letters.sort()
        sorted_substring = ''
        for letter in substring_letters:
            sorted_substring += letter
        else:
            sorted_substring_list.append(sorted_substring)
    #print sorted_substring_list
    
    #this parts filters substrings which cannot form words:
    for substring in sorted_substring_list[:]:
        if substring not in rearrange_dict:
            sorted_substring_list.remove(substring)
    
    hand_points = get_words_to_points(sorted_substring_list,HAND_SIZE)
    
    if hand_points:
        high_scoring = max(hand_points, key=hand_points.get)
        return rearrange_dict[high_scoring]
    else:
        print 'No english words in this hand'
        return '.'
  
        
def play_hand(hand, word_list,points_dict):
    """
    Allows the user to play the given hand, as follows:

    * The hand is displayed.
    
    * The user may input a word.

    * An invalid word is rejected, and a message is displayed asking
    the user to choose another word.

    * When a valid word is entered, it uses up letters from the hand.

    * After every valid word: the score for that word and the total
    score so far are displayed, the remaining letters in the hand 
    are displayed, and the user is asked to input another word.

    * The sum of the word scores is displayed when the hand finishes.

    * The hand finishes when there are no more unused letters.
    The user can also finish playing the hand by inputing a single
    period (the string '.') instead of a word.

    * The final score is displayed.

    hand: dictionary (string -> int)
    word_list: list of lowercase strings
    """
    
    # TO DO ...
    #print "play_hand not implemented." # replace this with your code...
    totalscore = 0 #starting score
    #time_limit=input('Enter a time limit, in seconds, for players: ')
    #if type(time_limit) != type(1):
    #    time_limit=input('Enter a time limit, in seconds, for players: ')
    #else:
    choice = input("Who should play, CPU(1), faster CPU(2) or you(3)? Input number: ")
    
    if choice == 1 or choice ==2:
        time_limit= get_time_limit(points_dict, 1)
        beggining = time.time()
    elif choice == 3:
        while True:
            try:
                time_limit= int(input('What should the time limit be?: '))
                break
            except (NameError, TypeError):
                print 'Plase enter the amount of time in seconds: '
                continue
    beggining = time.time()
    while len(hand)>0:
        if choice == 1:
            print 'Your hand is: '
            display_hand(hand)
            word = pick_best_word(hand, points_dict)
        elif choice == 2:
            print 'Your hand is: '
            display_hand(hand)
            word = pick_best_word_faster(hand, rearrange_dict)
        elif choice == 3:
            print
            print 'Your hand is: '
            display_hand(hand)
            beggining = time.time()
            word = raw_input('Enter a word composed of letters from your hand or a . to indicate that you are finished: ')
        else: 
            print 'Invalid Input!'
        print
        if word == '.':
            ending = time.time()
            timetaken = ending - beggining
            print 'It took %0.2f seconds to provide an answer' % timetaken
            print 'Totalscore: %0.2f' % totalscore
            return
        elif not is_valid_word(word, hand, word_list):
            print word, "either isn't a proper english word or contains letters which are not in your hand!"
            print
            print 'Current hand:',
            display_hand(hand)  
        else:
            ending = time.time()
            timetaken = ending - beggining
            remaining_time = time_limit - timetaken
            if remaining_time >= 0: 
                print 'It took %0.2f seconds to provide a proper answer' % timetaken
                score = get_word_score(word, HAND_SIZE)
                scoreadjusted = score/timetaken
                totalscore += scoreadjusted
                print word, 'earned you %0.2f points' %scoreadjusted
                hand = update_hand(hand, word)#the word uses up letters in the hand
                print 'Current hand:', 
                display_hand(hand)
                print 'remaining time: %0.2f' % remaining_time
            else: 
                print "You've run out of time!"
                totalscoreadjusted = totalscore/timetaken
                print 'Totalscore: %0.2f' % totalscoreadjusted
                break
    else:
        ending = time.time()
        timetaken = ending - beggining
        print 'Total score: %0.2f' % totalscore
        return
      
        
        
       
#play_hand(deal_hand(HAND_SIZE), load_words())



#
# Problem #5: Playing a game
# Make sure you understand how this code works!
# 
def play_game(word_list):
    """
    Allow the user to play an arbitrary number of hands.

    * Asks the user to input 'n' or 'r' or 'e'.

    * If the user inputs 'n', let the user play a new (random) hand.
      When done playing the hand, ask the 'n' or 'e' question again.

    * If the user inputs 'r', let the user play the last hand again.

    * If the user inputs 'e', exit the game.

    * If the user inputs anything else, ask them again.
    """
    # TO DO ...
    ## uncomment the following block of code once you've completed Problem #4
    hand = deal_hand(HAND_SIZE) # random init
    while True:
        cmd = raw_input('Enter n to deal a new hand, r to replay the last hand, or e to end game: ')
        if cmd == 'n':
            hand = deal_hand(HAND_SIZE)
            play_hand(hand.copy(), word_list,points_dict)
            print
        elif cmd == 'r':
            play_hand(hand.copy(), word_list,points_dict)
            print
        elif cmd == 'e':
            break
        else:
            print "Invalid command."

def get_time_limit(points_dict, k):
    """
    Return the time limit for the computer player as a function of the
    multiplier k.
    points_dict should be the same dictionary that is created by
    get_words_to_points.
    """
    start_time = time.time()
    # Do some computation. The only purpose of the computation is so we can
    # figure out how long your computer takes to perform a known task.
    for word in points_dict:
        get_frequency_dict(word)
        get_word_score(word, HAND_SIZE)
    end_time = time.time()
    return (end_time - start_time) * k

# Build data structures used for entire session and play game
#
if __name__ == '__main__':
    word_list = load_words()
    points_dict = get_words_to_points(word_list, HAND_SIZE)
    rearrange_dict = get_word_arrangements(word_list)
    #points_dict = get_sorted_words_to_points(rearrange_dict, HAND_SIZE)
    ##pick_best_word_faster(deal_hand(7), rearrange_dict)
    play_game(word_list)
    
    
    
    
    
rfh (Self-grade: Pretty good)
Submitted 2 years ago | Permalink | Time spent: 1 minute

Took me some time and going through some ugly code to come up with an algorithm to create every possible string combination for pick_best_word_faster, but the solution ended up being really simple in the end, which felt great. Not sure if problem 5 is a trick question or if my algorithm could be done more efficiently, but complexity is actually greater for pickBestWordFaster, but it only shows for a large hand size.

import random
import string
import time

VOWELS = 'aeiou'
CONSONANTS = 'bcdfghjklmnpqrstvwxyz'
HAND_SIZE = 20
POINTS_DICT = {}

SCRABBLE_LETTER_VALUES = {
    'a': 1, 'b': 3, 'c': 3, 'd': 2, 'e': 1, 'f': 4, 'g': 2, 'h': 4, 'i': 1, 'j': 8, 'k': 5, 'l': 1, 'm': 3, 'n': 1, 'o': 1, 'p': 3, 'q': 10, 'r': 1, 's': 1, 't': 1, 'u': 1, 'v': 4, 'w': 4, 'x': 8, 'y': 4, 'z': 10
}

# -----------------------------------
# Helper code
# (you don't need to understand this helper code)

WORDLIST_FILENAME = "words.txt"

def load_words():
    """
    Returns a list of valid words. Words are strings of lowercase letters.
    
    Depending on the size of the word list, this function may
    take a while to finish.
    """
    print "Loading word list from file..."
    # inFile: file
    inFile = open(WORDLIST_FILENAME, 'r', 0)
    # wordlist: list of strings
    wordlist = []
    for line in inFile:
        wordlist.append(line.strip().lower())
    print "  ", len(wordlist), "words loaded."
    return wordlist

def get_frequency_dict(sequence):
    """
    Returns a dictionary where the keys are elements of the sequence
    and the values are integer counts, for the number of times that
    an element is repeated in the sequence.

    sequence: string or list
    return: dictionary
    """
    # freqs: dictionary (element_type -> int)
    freq = {}
    for x in sequence:
        freq[x] = freq.get(x,0) + 1
    return freq


# (end of helper code)
# -----------------------------------

def getWordScore(word, n):
    """
    Returns the score for a word. Assumes the word is a
    valid word.

    The score for a word is the sum of the points for letters
    in the word, plus 50 points if all n letters are used on
    the first go.

    Letters are scored as in Scrabble; A is worth 1, B is
    worth 3, C is worth 3, D is worth 2, E is worth 1, and so on.

    word: string (lowercase letters)
    returns: int >= 0
    """
    score = 0
    for letter in word:
        score += SCRABBLE_LETTER_VALUES[letter]

    if len(word) == n:
        score += 50

    return score

#
# Make sure you understand how this function works and what it does!
#
def displayHand(hand):
    """
    Displays the letters currently in the hand.

    For example:
       displayHand({'a':1, 'x':2, 'l':3, 'e':1})
    Should print out something like:
       a x x l l l e
    The order of the letters is unimportant.

    hand: dictionary (string -> int)
    """
    for letter in hand.keys():
        for j in range(hand[letter]):
            print letter,              # print all on the same line
    print                              # print an empty line

#
# Make sure you understand how this function works and what it does!
#
def deal_hand(n):
    """
    Returns a random hand containing n lowercase letters.
    At least n/3 the letters in the hand should be VOWELS.

    Hands are represented as dictionaries. The keys are
    letters and the values are the number of times the
    particular letter is repeated in that hand.

    n: int >= 0
    returns: dictionary (string -> int)
    """
    hand={}
    num_vowels = n / 3
    
    for i in range(num_vowels):
        x = VOWELS[random.randrange(0,len(VOWELS))]
        hand[x] = hand.get(x, 0) + 1
        
    for i in range(num_vowels, n):    
        x = CONSONANTS[random.randrange(0,len(CONSONANTS))]
        hand[x] = hand.get(x, 0) + 1
        
    return hand

def updateHand(hand, word):
    """
    Assumes that 'hand' has all the letters in word.
    In other words, this assumes that however many times
    a letter appears in 'word', 'hand' has at least as
    many of that letter in it. 

    Updates the hand: uses up the letters in the given word
    and returns the new hand, without those letters in it.

    Has no side effects: does not mutate hand.

    word: string
    hand: dictionary (string -> int)    
    returns: dictionary (string -> int)
    """
    wordFreqDict = get_frequency_dict(word)
    updatedHand = {}

    for letter in hand:
        updatedHand[letter] = hand[letter] - wordFreqDict.get(letter, 0)
        if not updatedHand[letter]:
            del updatedHand[letter]

    return updatedHand

def canMakeWord(word, hand):
    """
    Returns True if word can be made with the provided hand,
    False if it cannot
    """

    wordFreqDict = get_frequency_dict(word)
    for letter in wordFreqDict:
        if hand.get(letter, 0) < wordFreqDict[letter]:
            return False

    return True

def isValidWord(word, hand, pointsDict):
    """
    Returns True if word is in the word_list and is entirely
    composed of letters in the hand. Otherwise, returns False.
    Does not mutate hand or word_list.
    
    word: string
    hand: dictionary (string -> int)
    word_list: list of lowercase strings
    """
    return pointsDict.get(word, False) and canMakeWord(word, hand)

def displayHand(hand):
    """
    Displays the current hand
    """

    for letter in hand:
        print letter,hand[letter]

def getWordInput(hand):
    word = raw_input('Enter a word or . to indicate that you are finished: ')
    inputIsValid = isValidWord(word, hand, POINTS_DICT) or word == '.'

    while not inputIsValid:
        word = raw_input('Word not valid. Please choose another word: ')
        inputIsValid = isValidWord(word, hand, POINTS_DICT) or word == '.'

    return word

def getWordsToPoints(wordList):
    """
    Return a dict that maps every word in wordList to its point value.
    """
    pointsDict = {}
    for word in wordList:
        pointsDict[word] = getWordScore(word, HAND_SIZE)
    
    return pointsDict

def pickBestWord(hand, pointsDict):
    """
    Return the highest scoring word from pointsDict that can be made with the given hand.

    Return '.' if no words can be made with the given hand.
    """

    bestScore = 0
    bestWord = '.'

    for word in pointsDict.keys():
        if canMakeWord(word, hand) and pointsDict[word] > bestScore:
            bestScore = pointsDict[word]
            bestWord = word

    return bestWord

def createCombinations(string):
    if len(string) == 1:
        return [string]

    solutions = []

    # Let T = the current string of letters
    # Let S = the current starting letter in our string
    # Let R = the rest of the string starting with the letter
    # immediately following S
    # Let K = the solutions for R
    # 
    # The solution for T is is the set of 
    # [S] + [S + K[0], S + K[1], S + K[2], ..., S + K[n]] + K

    solutions.append(string[0])
    subsolutions = createCombinations(string[1:])

    for subso in subsolutions:
        solutions.append(string[0] + subso)
        solutions.append(subso)

    return solutions

def pickBestWordFaster(hand, pointsDict, wordRearrangements):
    bestScore = 0
    bestWord = '.'
    currentHandAsString = ''

    for letter in hand:
        for i in range(hand[letter]):
            currentHandAsString += letter

    sortedCurrentHandAsString = ''.join(sorted(currentHandAsString))

    allPossibleSolutions = createCombinations(sortedCurrentHandAsString)

    for solution in allPossibleSolutions:
        if solution in wordRearrangements and pointsDict[wordRearrangements[solution]] > bestScore:
            bestScore = pointsDict[wordRearrangements[solution]]
            bestWord = wordRearrangements[solution]

    return bestWord

def getWordRearrangements(wordList):
    """
    Builds and returns a dictionary of words where the key is composed
    of all letters in a word, sorted alphabetically and the value is
    the word itself.
    """

    rearranged = {}
    for word in wordList:
        rearranged[''.join(sorted(word))] = word
    return rearranged

def getTimeLimit(pointsDict, k):
    """
    Return the time limit for the computer player as a function of the
    multiplier k
    """
    startTime = time.time()
    for word in pointsDict:
        get_frequency_dict(word)
        getWordScore(word, HAND_SIZE)
    endTime = time.time()
    return (endTime - startTime) * k

def play_hand(hand, word_list):
    """
    Allows the user to play the given hand, as follows:

    * The hand is displayed.
    
    * The user may input a word.

    * An invalid word is rejected, and a message is displayed asking
      the user to choose another word.

    * When a valid word is entered, it uses up letters from the hand.

    * After every valid word: the score for that word and the total
      score so far are displayed, the remaining letters in the hand 
      are displayed, and the user is asked to input another word.

    * The sum of the word scores is displayed when the hand finishes.

    * The hand finishes when there are no more unused letters.
      The user can also finish playing the hand by inputing a single
      period (the string '.') instead of a word.

    * The final score is displayed.

      hand: dictionary (string -> int)
      word_list: list of lowercase strings
    """
    print 'Enter time limit, in seconds, for players: '
    timeLimit = getTimeLimit(POINTS_DICT, 20)
    print "Time Limit: %0.8f seconds " % timeLimit
    timeLeft = timeLimit

    print "Current Hand: "
    displayHand(hand)

    score = 0
    gameIsDone = False

    while len(hand) and not gameIsDone:
        startTime = time.time()
#        word = pickBestWord(hand, POINTS_DICT)
        word = pickBestWordFaster(hand, POINTS_DICT, REARRANGE_DICT)
        print '"%s"' % word
        totalTime = time.time() - startTime

        if word == '.':
            gameIsDone = True
            continue

        print 'It took %0.10f seconds to provide an answer' % totalTime
        timeLeft -= totalTime

        if (timeLeft > 0):
            # Only give a maximum of the total letter score
            if totalTime < 1:
                totalTime = 1
            wordScore = getWordScore(word, HAND_SIZE) / totalTime
            score += wordScore
            hand = updateHand(hand, word)

            print 'Score for "%s": %0.2f. Total score so far: %0.2f' % (word, wordScore, score)
            print "Remaining letters: "
            displayHand(hand)
        else:
            print "Total time exceeds %0.8f seconds" % timeLimit
            gameIsDone = True
        print

    print "Final Score: %0.2f" % score

def play_game(word_list):
    """
    Allow the user to play an arbitrary number of hands.

    * Asks the user to input 'n' or 'r' or 'e'.

    * If the user inputs 'n', let the user play a new (random) hand.
      When done playing the hand, ask the 'n' or 'e' question again.

    * If the user inputs 'r', let the user play the last hand again.

    * If the user inputs 'e', exit the game.

    * If the user inputs anything else, ask them again.
    """

    hand = deal_hand(HAND_SIZE)
    while True:
        cmd = raw_input('Enter n to deal a new hand, r to replay the last hand, or e to end game: ')
        if cmd == 'n':
            hand = deal_hand(HAND_SIZE)
            play_hand(hand.copy(), word_list)
            print
        elif cmd == 'r':
            play_hand(hand.copy(), word_list)
            print
        elif cmd == 'e':
            break
        else:
            print "Invalid command."

#
# Build data structures used for entire session and play game
#
if __name__ == '__main__':
    word_list = load_words()
    POINTS_DICT = getWordsToPoints(word_list)
    REARRANGE_DICT = getWordRearrangements(word_list)
#    for rearranged in REARRANGE_DICT:
#        print "Rearranged: %s, Word: %s" % (rearranged, REARRANGE_DICT[rearranged])
    play_game(word_list)


## Problem 5 ##
# 
# pickBestWord:
# Need to iterate all possible words (length(words))
# size of current word * size of word list
# Seems to be quadratic
# 
# pickBestWordFaster
# Need to iterate all possible subsets of sorted(hand), which is dependent 
# f(x) = 1 + 2(f(x - 1))
# Problem size is doubling each step, exponential where n is the size of hand
# This seems to be faster when the hand size is less than 20

# Testing code below
#
# x = 19
# hand = deal_hand(x)
# print "Comparing pickBestWord and PickBestWordFaster with %d" % x
# start = time.time()
# word = pickBestWord(hand, POINTS_DICT)
# end = time.time()
# 
# print "pickBestWord took %0.8f seconds" % (end - start)
# 
# start = time.time()
# word = pickBestWordFaster(hand, POINTS_DICT, REARRANGE_DICT)
# end = time.time()
# print "pickBestWordFaster took %0.8f seconds" % (end - start)

Comments:

tuckertuck
1 year ago

I think the purpose of Problem 5 is to teach us that even though an algorithm is more complex in certain scopes it is more efficient.

Sign up or log in to comment

chrcoe (Self-grade: Pretty good)
Submitted 2 years ago | Permalink
# Problem Set 6: Modifed 6.00 Word Game (from ps5.py)
# Name: chrcoe
# Collaborators: hendrix from curiousreef for substrings algorithm
# Time: 2:25 min
#

import time
import random
import string

VOWELS = 'aeiou'
CONSONANTS = 'bcdfghjklmnpqrstvwxyz'
HAND_SIZE = 7
#HAND_SIZE = 25 #test seems to be one too big for this computer
#HAND_SIZE = 24 #test seems to be the max usuable, any higher, I get a MemoryError
#HAND_SIZE = 23

SCRABBLE_LETTER_VALUES = {
    'a': 1, 'b': 3, 'c': 3, 'd': 2, 'e': 1, 'f': 4, 'g': 2, 'h': 4, 'i': 1, 'j': 8, 'k': 5, 'l': 1, 'm': 3, 'n': 1, 'o': 1, 'p': 3, 'q': 10, 'r': 1, 's': 1, 't': 1, 'u': 1, 'v': 4, 'w': 4, 'x': 8, 'y': 4, 'z': 10
}

# -----------------------------------
# Helper code
# (you don't need to understand this helper code)

WORDLIST_FILENAME = "words.txt"

def load_words():
    """
    Returns a list of valid words. Words are strings of lowercase letters.
    
    Depending on the size of the word list, this function may
    take a while to finish.
    """
    print "Loading word list from file..."
    # inFile: file
    inFile = open(WORDLIST_FILENAME, 'r', 0)
    # wordlist: list of strings
    wordlist = []
    for line in inFile:
        wordlist.append(line.strip().lower())
    print "  ", len(wordlist), "words loaded."
    return wordlist

def get_frequency_dict(sequence):
    """
    Returns a dictionary where the keys are elements of the sequence
    and the values are integer counts, for the number of times that
    an element is repeated in the sequence.

    sequence: string or list
    return: dictionary
    """
    # freqs: dictionary (element_type -> int)
    freq = {}
    for x in sequence:
        freq[x] = freq.get(x,0) + 1
    return freq


# (end of helper code)
# -----------------------------------

###
### Scoring a word
###
def get_word_score(word, n): 
    """
    Returns the score for a word. Assumes the word is a
    valid word.

    The score for a word is the sum of the points for letters
    in the word, plus 50 points if all n letters are used on
    the first go.

    Letters are scored as in Scrabble; A is worth 1, B is
    worth 3, C is worth 3, D is worth 2, E is worth 1, and so on.

    word: string (lowercase letters)
    returns: int >= 0
    """

    wordScore = 0
    for letter in word:
        wordScore += SCRABBLE_LETTER_VALUES[letter]

    #print wordScore
    if len(word) == n:
        wordScore += 50  #adds 50 to score for using all n letters on first try
    return wordScore

#
# Make sure you understand how this function works and what it does!
#
def display_hand(hand):
    """
    Displays the letters currently in the hand.

    For example:
       display_hand({'a':1, 'x':2, 'l':3, 'e':1})
    Should print out something like:
       a x x l l l e
    The order of the letters is unimportant.

    hand: dictionary (string -> int)
    """
    for letter in hand.keys():
        for j in range(hand[letter]):
            print letter,              # print all on the same line
    print                              # print an empty line

#
# Make sure you understand how this function works and what it does!
#
def deal_hand(n):
    """
    Returns a random hand containing n lowercase letters.
    At least n/3 the letters in the hand should be VOWELS.

    Hands are represented as dictionaries. The keys are
    letters and the values are the number of times the
    particular letter is repeated in that hand.

    n: int >= 0
    returns: dictionary (string -> int)
    """
    hand={}
    num_vowels = n / 3
    
    for i in range(num_vowels):
        x = VOWELS[random.randrange(0,len(VOWELS))]
        hand[x] = hand.get(x, 0) + 1
        
    for i in range(num_vowels, n):    
        x = CONSONANTS[random.randrange(0,len(CONSONANTS))]
        hand[x] = hand.get(x, 0) + 1
        
    return hand

###
### Update a hand by removing letters
###
def update_hand(hand, word): 
    """
    Assumes that 'hand' has all the letters in word.
    In other words, this assumes that however many times
    a letter appears in 'word', 'hand' has at least as
    many of that letter in it. 

    Updates the hand: uses up the letters in the given word
    and returns the new hand, without those letters in it.

    Has no side effects: does not mutate hand.

    word: string
    hand: dictionary (string -> int)    
    returns: dictionary (string -> int)
    """

    newHand = hand.copy()

    for letter in word:
        newHand[letter] += -1
    return newHand
    

###
### Test word validity
### UPDATED for problem #3 of Problem Set 6.
# now takes the argument points_dict instead of word_list
def is_valid_word(word, hand, points_dict): 
    """
    Returns True if word is in the points_dict and is entirely
    composed of letters in the hand. Otherwise, returns False.
    Does not mutate hand or points_dict.
    
    word: string
    hand: dictionary (string -> int)
    points_dict: dictionary (string -> int)
    returns: boolean value
    """
    newHand = hand.copy()

    if points_dict.has_key(word):     #test if the word is in points_dict
        for letter in word:     #tests every letter in word to see if it is in hand
            if newHand.get(letter, 0) > 0:  #if it's positive (meaning it is there)
                newHand[letter] -= 1        #remove 1 of the letters for next check
            else:return False
        return True
    else: return False

# get_time_limit function from problem set.
# called by main function calls at bottom of program
# stored in global variable time_limit
def get_time_limit(points_dict, k):
    """
    Return the time limit for the computer player as a function of the
    multiplier k.
    points_dict should be the same dictionary that is created by
    get_words_to_points.

    points_dict: dictionary (string -> int)
    k: int
    returns: float
    """
    start_time = time.time()
    # Do some computation. The only purpose of the computation is so we can
    # figure out how long your computer takes to perform a known task.
    for word in points_dict:
        get_frequency_dict(word)
        get_word_score(word, HAND_SIZE)
    end_time = time.time()
    return (end_time - start_time) * k

#
# Problem #3: Computer player -> time taken: 95 min
#
def get_words_to_points(word_list):
    """
    Return a dict that maps every word in word_list to its point value.

    word_list: list of lowercase strings
    returns: dictionary (string -> int)
    """
    #walk through word_list and return get_word_score(word_from_wordlist, HAND_SIZE)
    scores = {}
    for word in word_list:
        scores[word] = get_word_score(word, HAND_SIZE)
    return scores

def pick_best_word(hand, points_dict):
    """
    Return the highest scoring word from points_dict that can be made with the given hand.
    Return '.' if no words can be made with the given hand.
    
    hand: dictionary (string -> int)
    points_dict: dictionary (string -> int)
    returns: list index 0
    """
    #walk through points_dict to add all words that are 'makeable' with current hand
    #add these to a makeable_dict dictionary with word:score and sort this by highest score
    #return the highest scoring word possible form this sorted list.
    possibleWords = {}
    for word in points_dict:
        if is_valid_word(word, hand, points_dict):
            possibleWords[word] = points_dict[word]
    bestScore = [0,0]
    if possibleWords: 
        for word in possibleWords: #compares current word to the word in bestScore[1]
            if possibleWords[word] > bestScore[1]: #if current word is better than bestScore[0]
                bestScore[0] = word 
                bestScore[1] = possibleWords[word]
    if bestScore[0] == 0:
        return '.' #player gives up
    return bestScore[0] #else returns the word

#
# Problem #4: Even Faster Computer player -> time taken: 25 min (so far)
#
def get_word_rearrangements(word_list):
    """
    Return a dict that maps every word in word_list to that word sorted alphabetically.

    word_list: list of lowercase strings
    returns: sorted dictionary (string -> int) 
    """
    #walk through word_list and return sorted version
    rearrange_dict = {}
    for word in word_list:
        tempList = [] #convert word into list from a string
        tempList = list(word)
        tempList.sort()
        sortedList = "" #null string-we need this to put the now sorted list back into rearrange_dict
        for letter in tempList:
            sortedList += letter #adds each letter from tempList in order into sortedList
        rearrange_dict[sortedList] = word #from pseudocode given
    return rearrange_dict

def pick_best_word_faster(hand, rearrange_dict):
    """
    Return the highest scoring word from rearrange_dict that can be made with the given hand.
    Return '.' if no words can be made with the given hand.

    hand: dictionary (string -> int)
    rearrange_dict: dictionary (string -> int)
    returns: list index 0
    """

    def substrings(string):
        """
        Given a string provides all the substrings.
        needed help on this from hendrix of curiousreef
        """
        result = []
        if len(string) == 1:
            result.append(string)
        else:
            for substring in substrings(string[:-1]):
                result.append(substring)
                substring = substring + string[-1]
                result.append(substring)
            result.append(string[-1])
        return result

    stringHand = ""
    for letter in hand.keys():
        for i in range(hand[letter]):
            stringHand +=letter

    sortedHand = ""
    listHand = list(stringHand) #list version of hand
    listHand.sort()
    for letter in listHand:
        sortedHand += letter

    possibleWords = {}
    for word in substrings(sortedHand):
        if word in rearrange_dict:
            possibleWords[rearrange_dict[word]]=points_dict[rearrange_dict[word]]
    bestScore = [0,0]
    if possibleWords: 
        for word in possibleWords: #compares current word to the word in bestScore[1]
            if possibleWords[word] > bestScore[1]: #if current word is better than bestScore[0]
                bestScore[0] = word 
                bestScore[1] = possibleWords[word]
    if bestScore[0] == 0:
        return '.' #player gives up
    return bestScore[0] #else returns the word

#
# Problem #1: How long? time taken: 20 min
# Problem #2: time Limit time taken: 5 min 
#
def play_hand(hand, points_dict): 
    """
    Allows the user to play the given hand, as follows:

    * The hand is displayed.
    
    * The user may input a word.

    * An invalid word is rejected, and a message is displayed asking
      the user to choose another word.

    * When a valid word is entered, it uses up letters from the hand.

    * After every valid word: the score for that word and the total
      score so far are displayed, the remaining letters in the hand 
      are displayed, and the user is asked to input another word.

    * The sum of the word scores is displayed when the hand finishes.

    * The hand finishes when there are no more unused letters.
      The user can also finish playing the hand by inputing a single
      period (the string '.') instead of a word.

    * The final score is displayed.

      hand: dictionary (string -> int)
      word_list: list of lowercase strings
    """
    #max_time = float(raw_input('Enter time limit (in seconds): '))
    max_time = time_limit
    print '\nTime limit set as:',time_limit,'seconds.'
    time_left = max_time
    print '\nCurrent hand:',
    #display_hand(hand)
    newHand = hand.copy()
    display_hand(newHand)
    #print sum(newHand.values()),'before loop\n'
    currentScore = 0.0
    #wordScore = get_word_score(userWord,HAND_SIZE)

    while sum(newHand.values()) != 0:
        start_time = time.time() #added ability to time how long user takes
        #userWord = str(raw_input('Enter a word from the given hand (or . to quit):'))
        #userWord = pick_best_word(newHand,points_dict)
        userWord = pick_best_word_faster(newHand,rearrange_dict)
        #this will call pick_best_word which will be the computer playing.
        end_time = time.time()
        total_time = end_time - start_time
        time_left -= total_time
        print 'It took',round(total_time,4),'seconds to enter \'',userWord,'\'.','You have',round(time_left,4),'seconds remaining.'
        #print sum(newHand.values()),'beg of loop\n'
        if userWord == '.': #when you press . you quit prematurely
            break
        if time_left <= 0: #tests time left
            print 'Total time exceeds,',max_time,'seconds.'
            break
        elif not is_valid_word(userWord, newHand, points_dict): #test for word validity
            print 'Invalid word'
            print '\nCurrent hand:',
            display_hand(newHand)
        else:
            roundedTotal = 1 + total_time #fixing divide by zero errors by making points
                        #awarded as such: Normal word score/1 + time taken to answer
            wordScore=round(get_word_score(userWord,HAND_SIZE)/roundedTotal,4)
            currentScore += wordScore
            print '\''+userWord+'\'','earns you',wordScore,'points.','Total:',currentScore,'points.'
            newHand = update_hand(newHand, userWord)
            print '\nCurrent hand:',
            display_hand(newHand)
            
        #print sum(newHand.values()),'end of loop\n'
            
    print '\nFinal score:',currentScore,'Thanks for playing!'
#   
# Playing a game
# Make sure you understand how this code works!
# 
def play_game(points_dict):
    """
    Allow the user to play an arbitrary number of hands.

    * Asks the user to input 'n' or 'r' or 'e'.

    * If the user inputs 'n', let the user play a new (random) hand.
      When done playing the hand, ask the 'n' or 'e' question again.

    * If the user inputs 'r', let the user play the last hand again.

    * If the user inputs 'e', exit the game.

    * If the user inputs anything else, ask them again.
    """
    
    #print "play_game not implemented."         # delete this once you've completed Problem #4
    #play_hand(deal_hand(HAND_SIZE), word_list) # delete this once you've completed Problem #4
    #testDict = {'h':1,'e':2,'l':4,'o':2,'j':1}    #for testing only
    #testDict1 = {'h':1,'e':1,'l':2,'o':1}
    #play_hand(testDict1, word_list)          #for testing only
    
    ## uncomment the following block of code once you've completed Problem #4
    hand = deal_hand(HAND_SIZE) # random init
    while True:
        cmd = raw_input('Enter n to deal a new hand, r to replay the last hand, or q to quit: ')
        if cmd == 'n':
            hand = deal_hand(HAND_SIZE)
            play_hand(hand.copy(), points_dict)
            print
        elif cmd == 'r':
            play_hand(hand.copy(), points_dict)
            print
        elif cmd == 'q':
            break
        else:
            print "Invalid command."

#
# Build data structures used for entire session and play game
#
if __name__ == '__main__':
    word_list = load_words()
    points_dict = get_words_to_points(word_list) #builds points_dict list of values for each word
    rearrange_dict = get_word_rearrangements(word_list) #builds rearrange_dict list of values for each word
    time_limit = get_time_limit(points_dict,1) #sets global time_limit based on computer
    play_game(points_dict)
    
##=========================================================================
##
## Problem 5 ##
## Characterize the time complexity of your implementation (in terms of the
##  size of word_list and the number of letters in a hand) of both
##  pick_best_word and pick_best_word_faster.
##
##=========================================================================









mrphud (Self-grade: Pretty good)
Submitted 2 years ago | Permalink
# 6.00 Problem Set 6
#
# The 6.00 Word Game
#

import random
import string
import time

VOWELS = 'aeiou'
CONSONANTS = 'bcdfghjklmnpqrstvwxyz'
HAND_SIZE = 7

SCRABBLE_LETTER_VALUES = {'a': 1, 'b': 3, 'c': 3, 'd': 2, 'e': 1, 'f': 4,
                          'g': 2, 'h': 4, 'i': 1, 'j': 8, 'k': 5, 'l': 1,
                          'm': 3, 'n': 1, 'o': 1, 'p': 3, 'q': 10, 'r': 1,
                          's': 1, 't': 1, 'u': 1, 'v': 4, 'w': 4, 'x': 8,
                          'y': 4, 'z': 10}

# -----------------------------------
# Helper code
# (you don't need to understand this helper code)

WORDLIST_FILENAME = "words.txt"

def load_words():
    """
    Returns a list of valid words. Words are strings of lowercase letters.
    
    Depending on the size of the word list, this function may
    take a while to finish.
    """
    print "Loading word list from file..."
    # inFile: file
    inFile = open(WORDLIST_FILENAME, 'r', 0)
    # wordlist: list of strings
    wordlist = []
    for line in inFile:
        wordlist.append(line.strip().lower())
    print "  ", len(wordlist), "words loaded."
    return wordlist

def get_frequency_dict(sequence):
    """
    Returns a dictionary where the keys are elements of the sequence
    and the values are integer counts, for the number of times that
    an element is repeated in the sequence.

    sequence: string or list
    return: dictionary
    """
    # freqs: dictionary (element_type -> int)
    freq = {}
    for x in sequence:
        freq[x] = freq.get(x,0) + 1
    return freq


# (end of helper code)
# -----------------------------------

#
# Problem #1: Scoring a word
#
def get_word_score(word, n):
    """
    Returns the score for a word. Assumes the word is a
    valid word.

    The score for a word is the sum of the points for letters
    in the word, plus 50 points if all n letters are used on
    the first go.

    Letters are scored as in Scrabble; A is worth 1, B is
    worth 3, C is worth 3, D is worth 2, E is worth 1, and so on.

    word: string (lowercase letters)
    returns: int >= 0
    """
    score = 0
    for letter in word:
        score += SCRABBLE_LETTER_VALUES[letter.lower()]
    if len(word) == n:
        score += 50
    return score

#
# Make sure you understand how this function works and what it does!
#
def display_hand(hand):
    """
    Displays the letters currently in the hand.

    For example:
       display_hand({'a':1, 'x':2, 'l':3, 'e':1})
    Should print out something like:
       a x x l l l e
    The order of the letters is unimportant.

    hand: dictionary (string -> int)
    """
    for letter in hand.keys():
        for j in range(hand[letter]):
             print letter,              # print all on the same line
    print                              # print an empty line

#
# Make sure you understand how this function works and what it does!
#
def deal_hand(n):
    """
    Returns a random hand containing n lowercase letters.
    At least n/3 the letters in the hand should be VOWELS.

    Hands are represented as dictionaries. The keys are
    letters and the values are the number of times the
    particular letter is repeated in that hand.

    n: int >= 0
    returns: dictionary (string -> int)
    """
    hand={}
    num_vowels = n / 3
    
    for i in range(num_vowels):
        x = VOWELS[random.randrange(0,len(VOWELS))]
        hand[x] = hand.get(x, 0) + 1
        
    for i in range(num_vowels, n):    
        x = CONSONANTS[random.randrange(0,len(CONSONANTS))]
        hand[x] = hand.get(x, 0) + 1
        
    return hand

#
# Problem #2: Update a hand by removing letters
# My function

def update_hand(hand, word):
    """
    Assumes that 'hand' has all the letters in word.
    In other words, this assumes that however many times
    a letter appears in 'word', 'hand' has at least as
    many of that letter in it. 

    Updates the hand: uses up the letters in the given word
    and returns the new hand, without those letters in it.

    Has no side effects: does not mutate hand.

    word: string
    hand: dictionary (string -> int)    
    returns: dictionary (string -> int)
    """
    for letter in word:         # goes through the word
        if hand[letter] > 1:    # if the number of letters in hand is > 1
            hand[letter] = hand.pop(letter) - 1     # the number is reduced by 1
        else:
            hand.pop(letter)    # if less than one, the key:value pair is removed
    # pop(key, default = error) removes the key:value pair and displays the value 
    return hand

# Their function didn't work with my setup. It only reduced the value(key). It didn't remove
# the key completly. The presence of 'key':0 sent my program into an infinite loop.
##def update_hand(hand, word):
##    """
##    Assumes that 'hand' has all the letters in word.
##    In other words, this assumes that however many times
##    a letter appears in 'word', 'hand' has at least as
##    many of that letter in it. 
##
##    Updates the hand: uses up the letters in the given word
##    and returns the new hand, without those letters in it.
##
##    word: string
##    hand: dictionary (string -> int)    
##    returns: dictionary (string -> int)
##    """
##    freq = get_frequency_dict(word)
##    newhand = {}
##    for char in hand:
##        newhand[char] = hand[char]-freq.get(char,0)
##    return newhand
##    #return dict( ( c, hand[c] - freq.get(c,0) ) for c in hand )
        

#
# Problem #3: Test word validity
#

def is_valid_word(word, hand, points_dictionary):
    """
    Returns True if word is in the word_list and is entirely
    composed of letters in the hand. Otherwise, returns False.
    Does not mutate hand or word_list.
    
    word: string
    hand: dictionary (string -> int)
    word_list: list of lowercase strings
    """
    freq = get_frequency_dict(word)
    for letter in word:
        if freq[letter] > hand.get(letter, 0):
            return False
    return word in points_dictionary

#
# Problem #4: Playing a hand
#

##def play_hand(hand, points_dict):
##    """
##    Allows the user to play the given hand, as follows:
##
##    * The hand is displayed.
##    
##    * The user may input a word.
##
##    * An invalid word is rejected, and a message is displayed asking
##      the user to choose another word.
##
##    * When a valid word is entered, it uses up letters from the hand.
##
##    * After every valid word: the score for that word is displayed,
##      the remaining letters in the hand are displayed, and the user
##      is asked to input another word.
##
##    * The sum of the word scores is displayed when the hand finishes.
##
##    * The hand finishes when there are no more unused letters.
##      The user can also finish playing the hand by inputing a single
##      period (the string '.') instead of a word.
##
##      hand: dictionary (string -> int)
##      word_list: list of lowercase strings
##    """    
##    total_time = 0
##    total = 0
##    initial_handlen = sum(hand.values())
##    while sum(hand.values()) > 0:
##        print 'Current Hand:',
##        display_hand(hand)
##        start_time = time.time()    # time to start counting
##        good_words = find_all_words(hand, points_dict)
##        userWord = pick_best_word(good_words)
##        if userWord == '.':
##             break
##        else:
##            isValid = is_valid_word(userWord, hand, points_dict)
##            if not isValid:
##                print 'Invalid word, please try again.'
##            else:
##                end_time = time.time()      # time to end counting
##                time_spent = end_time - start_time
##                total_time += time_spent
##                try:
##                    points = round(get_word_score(userWord, initial_handlen)/time_spent, 2)
##                except:                    # just in case the entry time is near 0 seconds 
##                    time_spent = 0.1       # This error pathway sets a minimum time
##                    print 'It took less than %0.2f seconds to provide an answer' % (time_spent)
##                    print 'You are too fast. Let\'s say it took at least %0.2f seconds to answer' % (total_time)
##                print 'It took %0.2f seconds to provide an answer' % (time_spent)
##                # The %0.2f tells python to insert the variable total_time as a floating point
##                # that is rounded to the second decimal (hundreths place)
##                time_left = time_clock(total_time, time_limit)
##                if time_left > 0:
##                    total += points
##                    print '%s earned %0.2f points. Total: %0.2f points' % (userWord, points, total)
##                    print 'You have %0.2f seconds remaining' % (time_left)
##                    hand = update_hand(hand, userWord)
##                else:
##                    print '%s earned %0.2f points, but ...' % (userWord, points)
##                    print 'Total time exceeds %s seconds.' % (time_limit)
##                    break
##    print 'Total score: %s points.' % total

#
# Problem #5: Playing a game
# Make sure you understand how this code works!
# 

def play_game(points_dictionary):
    """
    Allow the user to play an arbitrary number of hands.

    * Asks the user to input 'n' or 'r' or 'e'.

    * If the user inputs 'n', let the user play a new (random) hand.
      When done playing the hand, ask the 'n' or 'e' question again.

    * If the user inputs 'r', let the user play the last hand again.

    * If the user inputs 'e', exit the game.

    * If the user inputs anything else, ask them again.
    """

    hand = deal_hand(HAND_SIZE) # random init
    while True:
        cmd = raw_input('Enter n to deal a new hand, r to replay the last hand, or e to end game: ')
        if cmd == 'n':
            hand = deal_hand(HAND_SIZE)
            play_hand(hand.copy(), points_dictionary)
            print
        elif cmd == 'r':
            play_hand(hand.copy(), points_dictionary)
            print
        elif cmd == 'e':
            break
        else:
            print "Invalid command."

#
## Problem # 6. Functions used for Problem set 6.
#

def hand_convert_to_list(hand):
    """ Converts the dictionary 'hand' into a list of keys*frequency"""
    keylist = hand.keys()
    number_of_letters = hand_length_count(hand)
    temp_dict = hand.copy()
    check = False
    while not check:
        for key in keylist:
            if temp_dict[key] > 1:
                keylist.append(key)
                temp_dict[key] = temp_dict.pop(key) - 1   
        if len(keylist) == number_of_letters:
            check = True
    return keylist

def hand_length_count(hand):
    """returns the total number of letters in scrabble dictionary"""
    number_of_letters = 0
    for letter in hand:
        number_of_letters += hand[letter]
    return number_of_letters

def find_all_words(hand, points_dict):
    """Return the highest scoring words from points_dict that
    can be made with the given hand"""
    ctr = sum(hand.values())
    good_words = {}
    while ctr > 1:
        for word in points_dict:
            if len(word) == ctr and word_compare(hand, word):
                good_words[word] = points_dict[word]
        ctr -= 1
    if len(good_words) != 0:
       return good_words   
    else:
        return '.'

def choose_best_words(good_words):
    """Returns a sub-dictionary of the highest scoring words from find_all_words"""
    if good_words == '.':
        return '.'
    highest = 0
    best_words = {}
    for word in good_words:
        if good_words[word] >= highest:
            highest = good_words[word]
            best_words[word] = highest
    return best_words

def pick_best_word(good_words):
    """Returns highest scoring word from find_all_words"""
    if good_words == '.':
        return '.'
    highest = 0
    for word in good_words:
        if good_words[word] >= highest:
            highest = good_words[word]
            best_word = word
    return best_word

def get_words_to_points(word_list):
    """Return a dictionary that maps every word in word_list to its point value"""
    points_dict = {}
    for word in word_list:
        points_dict[word] = get_word_score(word, 10)
    return points_dict

def word_compare(hand, key):
    """Returns True if key can be made by letters in hand. Returns False otherwise"""
    handlist = hand_convert_to_list(hand)
    keylist = list(key)
    for letter in keylist:
        if letter in handlist:
            handlist.remove(letter)
        else:
            return False
    return True

def get_time_limit(points_dict, k):
    """Return the time limit for the computer player as a function of the multiplier k.
    points_dict should be the same dictionary that is created by get_words_to_points."""
    start_time = time.time()
    # Do some computation. the only purpose of the computation is so we can figure out
    # how long your computer takes to perform a known task.
    for word in points_dict:
        get_frequency_dict(word)
        get_word_score(word, HAND_SIZE)
    end_time = time.time()
    return (end_time - start_time)*k

def time_clock(time_spent, time_limit):
    """ Sets a time limit to a round."""
    time_left = time_limit - time_spent
    if time_left < 0:
        time_left = 0
    return time_left

#
## Problem 7. Even Faster Computer Player
#

def rearrange_words(word_list):
    """ Returns a dictionary of all words in word_list linked to the sorted letters in the word"""
    rearrange_dict = {}
    for word in word_list:
        rearrangedlist = sorted(word)
        rearrangedstring = ''.join(rearrangedlist)
        rearrange_dict[rearrangedstring] = word
    return rearrange_dict

def get_rearranged_to_points(rearrange_dict):
    """Return a dictionary that maps every sorted string in rearrange_dict to its point value"""
    rearrange_points_dict = {}
    for sortedstring in rearrange_dict:
        rearrange_points_dict[sortedstring] = get_word_score(sortedstring, 10)
    return rearrange_points_dict

def play_hand(hand, rearrange_points):
    """
    Allows the user to play the given hand, as follows:

    * The hand is displayed.
    
    * The user may input a word.

    * An invalid word is rejected, and a message is displayed asking
      the user to choose another word.

    * When a valid word is entered, it uses up letters from the hand.

    * After every valid word: the score for that word is displayed,
      the remaining letters in the hand are displayed, and the user
      is asked to input another word.

    * The sum of the word scores is displayed when the hand finishes.

    * The hand finishes when there are no more unused letters.
      The user can also finish playing the hand by inputing a single
      period (the string '.') instead of a word.

      hand: dictionary (string -> int)
      word_list: list of lowercase strings
    """    
    total_time = 0
    total = 0
    initial_handlen = sum(hand.values())
    while sum(hand.values()) > 0:
        print 'Current Hand:',
        display_hand(hand)
        start_time = time.time()    # time to start counting
        userWord = pick_best_word(find_all_words_faster(hand, rearrange_points))
        if userWord == '.':
             break
        else:
            isValid = is_valid_word(userWord, hand, rearrange_points)
            if not isValid:
                print 'Invalid word, please try again.'
            else:
                end_time = time.time()      # time to end counting
                time_spent = end_time - start_time
                total_time += time_spent
                try:
                    points = round(get_word_score(userWord, initial_handlen)/time_spent, 2)
                except:                    # just in case the entry time is near 0 seconds 
                    time_spent = 0.01      # This error pathway sets a minimum time
                    print 'It took less than %0.2f seconds to provide an answer' % (time_spent)
                    print 'You are too fast. Let\'s say it took at least %0.2f seconds to answer' % (time_spent)
                    points = round(get_word_score(userWord, initial_handlen)/time_spent, 2)
                print 'It took %0.2f seconds to provide an answer' % (time_spent)
                # The %0.2f tells python to insert the variable total_time as a floating point
                # that is rounded to the second decimal (hundreths place)
                time_left = time_clock(total_time, time_limit)
                if time_left > 0:
                    total += points
                    print '%s earned %0.2f points. Total: %0.2f points' % (rearrange_dict[userWord], points, total)
                    print 'You have %0.2f seconds remaining' % (time_left)
                    hand = update_hand(hand, userWord)
                else:
                    print '%s earned %0.2f points, but ...' % (userWord, points)
                    print 'Total time exceeds %s seconds.' % (time_limit)
                    break
    print 'Total score: %s points.' % total

def find_all_words_faster(hand, rearranged_points):
    """Retruns all words that can be made with hand"""
    good_words = {}
    sortedhandlist = sorted(hand_convert_to_string(hand))
    sortedhandstring = ''.join(sortedhandlist)
    for word in rearrange_points:
        if word in sortedhandstring:
            good_words[word] = rearrange_points[word]
    if len(good_words) != 0:
       return good_words   
    else:
        return '.'
    
def hand_convert_to_string(hand):
    """Converts the dictionary 'hand' into srtring"""
    keylist = hand_convert_to_list(hand)
    keyliststring = ''.join(keylist)
    return keyliststring
    
#
# Build data structures used for entire session and play game
#

if __name__ == '__main__':
    word_list = load_words()
    points_dict = get_words_to_points(word_list)
    rearrange_dict = rearrange_words(word_list)
    rearrange_points = get_rearranged_to_points(rearrange_dict)
    time_limit = get_time_limit(points_dict, k = 1)
    play_game(rearrange_points)
    
#
## Problem #8. Algorithm Analysis
#

# The differences between find_all_words and find_all_words_faster is that find_all_words
# uses a function called word_compare and checks that the len(word) is the same for
# a particular search. word_compare compares letter for letter two words.
# find_all_words_faster only uses a word in dictionary statement which should be O(len(word_list)).
# word_compare is O(len(hand)), because it must go through hand. find_all_words also searches through
# word_list. Because, it also compares word length it must go through ~len(hand) times. 
# Thus, the order of find_all_word is O(len(hand)^2*len(wordlist)). Then find_all_words is
# approximately 7*7 = 49 times slower, which is consistent with observed word submission times.


# On a side note. If the ctr feature in find_all_words is removed, the average time to find
# good_words increases by ~0.08 seconds. This may be small, but is consistent.

chip (Self-grade: Outstanding)
Submitted 2 years ago | Permalink
# Problem Set 6: Word Game
# Name: chip

import random
import time

VOWELS = 'aeiou'
CONSONANTS = 'bcdfghjklmnpqrstvwxyz'
HAND_SIZE = 7
HAND_LIMIT = 20

SCRABBLE_LETTER_VALUES = {
'a': 1, 'b': 3, 'c': 3, 'd': 2, 'e': 1, 'f': 4, 'g': 2, 'h': 4, 'i': 1, 'j': 8, 'k': 5, 'l': 1, 'm': 3, 'n': 1, 'o': 1, 'p': 3, 'q': 10, 'r': 1, 's': 1, 't': 1, 'u': 1, 'v': 4, 'w': 4, 'x': 8, 'y': 4, 'z': 10
}

WORDLIST_FILENAME = "words.txt"

def get_word_rearrangements(word_list):
    rearrange_dict = []
    for word in word_list:
        rearrange_dict.append("".join(sorted(word)))
    return rearrange_dict

def get_frequency_dict(sequence):
    freq = {}
    for x in sequence:
        freq[x] = freq.get(x, 0) + 1
    return freq

def display_hand(hand):
    for letter in hand.keys():
        for j in range(hand[letter]):
            print(letter)              # print all on the same line

def deal_hand(n):
    hand={}
    num_vowels = n // 3
    for i in range(num_vowels):
        x = VOWELS[random.randrange(0,len(VOWELS))]
        hand[x] = hand.get(x, 0) + 1
    for i in range(num_vowels, n):
        x = CONSONANTS[random.randrange(0,len(CONSONANTS))]
        hand[x] = hand.get(x, 0) + 1
    return hand

def update_hand(hand, word):
    new_hand = hand.copy()
    for c in word:
        if c in new_hand:
            new_hand[c] -= 1;
            if new_hand[c] == 0:
                del new_hand[c]
    return new_hand

def is_valid_word(word, hand, word_list):
    test_hand = hand.copy()
    if word not in word_list:
        return False
    for c in word:
        if c not in test_hand:
            return False
        else:
            if test_hand[c] == 0:
                return False
            test_hand[c] -= 1
    return True

def load_words():
    inFile = open(WORDLIST_FILENAME, encoding='utf-8')
    wordlist = []
    for line in inFile:
        wordlist.append(line.strip().lower())
    return wordlist

def get_word_score(word, n):
    score = 0
    for c in word:
        score += SCRABBLE_LETTER_VALUES[c]
        if len(word) == n:
            score += 50
    return score

def get_words_to_points(word_list):
    points_dict = {}
    for word in word_list:
        points_dict[word] = get_word_score(word,0)
    return points_dict

def pick_best_word(hand, points_dict):
    posibilities = {}    
    for (word, point) in points_dict.items():
        posible_word = True
        hand_copy = hand.copy()
        for ch in word:
            if ch not in hand_copy.keys():
                posible_word = False
                break
            else:
                del hand_copy[ch]
        if posible_word: 
            posibilities[point] = word
    key_vals = posibilities.keys()
    sorted(key_vals)
    if len(posibilities) == 0:
        return "."
    else:
        return posibilities[max(key_vals)]

def pick_best_word_faster(hand, points_dict, rearrange_dict):
    posibilities = {}
    sorted_hand = "".join(sorted("".join(hand)))
    for (word, point) in points_dict.items():        
        if sorted_hand in rearrange_dict:
           posibilities[point] = word
    if len(posibilities) == 0:        
        return "."
    else:
        return posibilities[max(posibilities.keys())]

def play_hand(hand, word_list, rearrange_dict):
    points_dict = get_words_to_points(word_list)
    total_points, time_spend, total_time = 0,0,0
    while True:
        print("Current hand: ")
        display_hand(hand)
        start_time = time.time()
        print("You have " + str(HAND_LIMIT - total_time) + " seconds ramaining!")
        word = pick_best_word_faster(hand, points_dict, rearrange_dict)
#       word = input("Enter word, or . to indicate thar you are finished: ")
        time_spend += (time.time() - start_time)
        total_time += time_spend
        if word == ".":
            print("Total score: ",total_points)
            break
        if total_time > HAND_LIMIT:
            print("Time limit exceeded!")
            break
        if not is_valid_word(word, hand, word_list):
            print("Ivalid word, please try again!")
            continue
        points = get_word_score(word, len(hand))
        print(("It took {0:2f} seconds to provide an answer.").format(time_spend))
        total_points += (points / time_spend)
        print(word, "earned", points,"points. Total:",total_points)
        hand = update_hand(hand, word)
        time_spend = 0

def play_game(word_list, rearrange_dict):
    hand = deal_hand(HAND_SIZE)
    while True:
        cmd = input("Enter n to deal a new hand, r to replay the last hand, or e to end game: ")
        if cmd == "n":
            hand = deal_hand(HAND_SIZE)
            play_hand(hand.copy(), word_list, rearrange_dict)
            print()
        elif cmd == "r":
            play_hand(hand.copy(), word_list, rearrange_dict)
            print()
        elif cmd == "e":
            break
        else:
            print("Invalid command.")

word_list = load_words()
rearrange_dict = get_word_rearrangements(word_list)
play_game(word_list, rearrange_dict)
hendrix (Self-grade: Pretty good)
Submitted 2 years ago | Permalink

This is the first problem set I found really difficult, well I should say that I was finding it easy until the faster computer player part which really foxed me for a long time!

Eventually I got it though and I think it is working pretty good (this has been the most rewarding problem set as well as the hardest). I'm not sure about my complexity analysis either, but I timed both the faster and my old computer player and the difference is pretty substantial, small hands are faster using the get_best_word_faster function but as hand size grows my original function is far better with large hands taking a loooong time in the "faster" function. By the time hand size got up to 24 characters my pick_best_word_faster function was taking almost 20 seconds and I couldn't even get a 25 character hand to finish running!

Can't wait to have a look at other peoples answers.

# Problem Set 6: 6.00 Word Game 2
# Name: Mark Calderwood
# Collaborators: 
# Time:

import random
import string
import time

VOWELS = 'aeiou'
CONSONANTS = 'bcdfghjklmnpqrstvwxyz'
HAND_SIZE = 7

SCRABBLE_LETTER_VALUES = {
    'a': 1, 'b': 3, 'c': 3, 'd': 2, 'e': 1, 'f': 4, 'g': 2, 'h': 4, 'i': 1, 'j': 8, 'k': 5, 'l': 1, 'm': 3, 'n': 1, 'o': 1, 'p': 3, 'q': 10, 'r': 1, 's': 1, 't': 1, 'u': 1, 'v': 4, 'w': 4, 'x': 8, 'y': 4, 'z': 10
}

# -----------------------------------
# Helper code
# (you don't need to understand this helper code)

WORDLIST_FILENAME = "words.txt"

def load_words():
    """
    Returns a list of valid words. Words are strings of lowercase letters.
    
    Depending on the size of the word list, this function may
    take a while to finish.
    """
    print "Loading word list from file..."
    # inFile: file
    inFile = open(WORDLIST_FILENAME, 'r', 0)
    # wordlist: list of strings
    wordlist = []
    for line in inFile:
        wordlist.append(line.strip().lower())
    print "  ", len(wordlist), "words loaded."
    return wordlist

def get_frequency_dict(sequence):
    """
    Returns a dictionary where the keys are elements of the sequence
    and the values are integer counts, for the number of times that
    an element is repeated in the sequence.

    sequence: string or list
    return: dictionary
    """
    # freqs: dictionary (element_type -> int)
    freq = {}
    for x in sequence:
        freq[x] = freq.get(x,0) + 1
    return freq


# (end of helper code)
# -----------------------------------

#---6.0 problem 3 functions

def get_words_to_points(word_list):
    """
    Return a dict that maps every word in word_list to its point value.
    """
    scores = {}
    for word in word_list:
        scores[word] = get_word_score(word, HAND_SIZE)
    return scores
    
def pick_best_word(hand, points_dict):
    """
    Return the highest scoring word from points_dict that can be made with the
    given hand.
    Return '.' if no words can be made with the given hand.
    """
    makeable_words = {}
    for word in points_dict:
        if is_valid_word(word, hand, points_dict):
            makeable_words[word] = points_dict[word]
    current_best = [0,0]
    if makeable_words:
        for word in makeable_words:
            if makeable_words[word] > current_best[1]:
                current_best[0] = word
                current_best[1] = makeable_words[word]        
    return current_best[0]

def get_time_limit(points_dict, k):
    """
    Return the time limit for the computer player as a function of the
    multiplier k.
    points_dict should be the same dictionary that is created by
    get_words_to_points.
    """
    start_time = time.time()
    # Do a computation. The only purpose of the computation is so we can
    # figure out how long your computer takes to perform a known task.
    for word in points_dict:
        get_frequency_dict(word)
        get_word_score(word, HAND_SIZE)
    end_time = time.time()
    return (end_time - start_time) * k

def get_word_rearrangements(word_list):
    """
    Return a dict that maps every word in the wordlist to itself sorted 
    alphabetically e.g. zap to apz for quick access
    """
    rearrange_dict = {}
    for word in word_list:
        temp = [] # to convert the word into a list for sorting
        temp = list(word)
        temp.sort()
        sorted = "" # create a string to put the sorted list back into
        for i in temp:
            sorted = sorted + i # & add each letter from the 1 at a time
        rearrange_dict[sorted] = word #if more than 1 word has the same letters
        # just overwrite the old one and keep only the last (it will score the same)
    return rearrange_dict

def pick_best_word_faster(hand, rearrange_dict):
    """
    Return the highest scoring word from points_dict that can be made with the
    given hand (but faster!).
    Return '.' if no words can be made with the given hand.
    """
    
    def substrings(string):
        """
        Given a string provides all the substrings.
        
        Works on the premiss that given a set of the substrings of a string the
        the subsets of a string with one more char is the formed by taking all the
        substrings in the known subset and also adding to them the set formed by
        adding the character to every element in the old set and then adding the 
        new char
        """
        result = []
        if len(string) == 1:
            result.append(string)
        else:
            for substring in substrings(string[:-1]):
                result.append(substring)
                substring = substring + string[-1]
                result.append(substring)
            result.append(string[-1])
        return result

    
    #prelim, convert the hand into a string
    hand_string = ""
    for letter in hand.keys():
        for j in range(hand[letter]):
            hand_string = hand_string + letter
    
    #1st sort the hand
    sorted_hand = ""
    hand_list = list(hand_string)
    hand_list.sort()
    for i in hand_list:
        sorted_hand = sorted_hand + i
    #2nd determine which words we can make stored as a dictionary with points
    makeable_words = {}
    for sub in substrings(sorted_hand):
        if sub in rearrange_dict:
            makeable_words[rearrange_dict[sub]]=points_dict[rearrange_dict[sub]]
        
    #3rd choose best word
    current_best = [0,0]
    if makeable_words:
        for word in makeable_words:
            if makeable_words[word] > current_best[1]:
                current_best[0] = word
                current_best[1] = makeable_words[word]        
    return current_best[0]
    

#---End of 6.0 problem functions

#
# Problem #1: Scoring a word
#
def get_word_score(word, n):
    """
    Returns the score for a word. Assumes the word is a
    valid word.

    The score for a word is the sum of the points for letters
    in the word, plus 50 points if all n letters are used on
    the first go.

    Letters are scored as in Scrabble; A is worth 1, B is
    worth 3, C is worth 3, D is worth 2, E is worth 1, and so on.

    word: string (lowercase letters)
    returns: int >= 0
    """
    if word == "":
        return 0
    score = 0
    for letter in word:
        score = score + SCRABBLE_LETTER_VALUES[letter]
    if len(word) == n:
        score = score + 50
    return score

#
# Make sure you understand how this function works and what it does!
#
def display_hand(hand):
    """
    Displays the letters currently in the hand.

    For example:
       display_hand({'a':1, 'x':2, 'l':3, 'e':1})
    Should print out something like:
       a x x l l l e
    The order of the letters is unimportant.

    hand: dictionary (string -> int)
    """
    for letter in hand.keys():
        for j in range(hand[letter]):
            print letter,              # print all on the same line
    print                              # print an empty line

#
# Make sure you understand how this function works and what it does!
#
def deal_hand(n):
    """
    Returns a random hand containing n lowercase letters.
    At least n/3 the letters in the hand should be VOWELS.

    Hands are represented as dictionaries. The keys are
    letters and the values are the number of times the
    particular letter is repeated in that hand.

    n: int >= 0
    returns: dictionary (string -> int)
    """
    hand={}
    num_vowels = n / 3
    
    for i in range(num_vowels):
        x = VOWELS[random.randrange(0,len(VOWELS))]
        hand[x] = hand.get(x, 0) + 1
        
    for i in range(num_vowels, n):    
        x = CONSONANTS[random.randrange(0,len(CONSONANTS))]
        hand[x] = hand.get(x, 0) + 1
        
    return hand

#
# Problem #2: Update a hand by removing letters
#
def update_hand(hand, word):
    """
    Assumes that 'hand' has all the letters in word.
    In other words, this assumes that however many times
    a letter appears in 'word', 'hand' has at least as
    many of that letter in it. 

    Updates the hand: uses up the letters in the given word
    and returns the new hand, without those letters in it.

    Has no side effects: does not mutate hand.

    word: string
    hand: dictionary (string -> int)    
    returns: dictionary (string -> int)
    """
    newHand = hand.copy()
    for i in word:
        newHand[i] -= 1
    return newHand

#
# Problem #3: Test word validity
#
def is_valid_word(word, hand, points_dict):
    """
    Returns True if word is in the points_dict and is entirely
    composed of letters in the hand. Otherwise, returns False.
    Does not mutate hand or points_dict.
    
    word: string
    hand: dictionary (string -> int)
    points_dict: dictionary (string -> int)
    """
    helperHand = hand.copy()
    if points_dict.has_key(word):
        for i in word:
            if helperHand.get(i, 0) > 0:
                helperHand[i] -= 1
            else: return False
        return True
    else: return False

#
# Problem #4: Playing a hand
#
def play_hand(hand, points_dict):
    """
    Allows the user to play the given hand, as follows:

    * The hand is displayed.
    
    * The user may input a word.
    
    * The time it takes for the word to be entered is recorded

    * An invalid word is rejected, and a message is displayed asking
      the user to choose another word

    * When a valid word is entered, it uses up letters from the hand.

    * After every valid word: the score for that word and the total
      score so far are displayed, the remaining letters in the hand 
      are displayed, and the user is asked to input another word.

    * The sum of the word scores is displayed when the hand finishes.

    * The hand finishes when there are no more unused letters.
      The user can also finish playing the hand by inputing a single
      period (the string '.') instead of a word.

    * The final score is displayed.

      hand: dictionary (string -> int)
      points_dict: dictionary (string -> int)
    """

    def get_time():
        """
        Asks the player for the amount of time they wish on the clock for this 
        hand
        
        Returns: float
        """
        while True:
            time = raw_input("What should the time limit for this hand be?")
            try:
                time_limit = float(time)
                return time_limit
            except:
                print"time is not in valid format, please input a number"
    
    def get_input():
        """
        Asks the player for a word and returns the word and the time taken
        
        Returns: a two element tuple containing the word as the 1st element and
        the time taken as the second
        """
        start_time = time.time()
        word = raw_input("Enter word, or . to indicate that you are finished: ")
        end_time = time.time()
        total_time = end_time - start_time
        if total_time < 0.001:
            total_time = 0.001
        return (word, total_time)
    
    computers_hand = hand.copy()
    time_limit = get_time()
    totalScore = 0.0
    word = ''
    while word != '.':
        print "Current Hand: ",
        display_hand(hand)
        input = get_input()
        word = input[0]
        input_time = input[1]
        time_limit -= input_time
        if time_limit >= 0:
            if is_valid_word(word, hand, points_dict):
                hand = update_hand(hand, word)
                score = points_dict[word]#/input_time
                totalScore += score
                print word, "in %0.2f seconds earned %0.2f points. Total: %0.2f points." % (
                input_time, score, totalScore)
                print "time remaining is: %0.2f" % time_limit
            elif word == '.':
                slow_time_start = time.time()
                best_word = pick_best_word(computers_hand, points_dict)
                slow_time_stop = time.time()
                slow_time_total = slow_time_stop - slow_time_start
                fast_time_start = time.time()
                fastest_word = pick_best_word_faster(computers_hand, rearrange_dict)
                fast_time_stop = time.time()
                fast_time_total = fast_time_stop - fast_time_start
                print "The computers word would have been %s scoring : %i in %0.5f seconds" \
                % (best_word, points_dict[best_word], slow_time_total)
                print "The fast computers word would have been %s scoring : %i in %0.5f seconds" \
                % (fastest_word, points_dict[best_word], fast_time_total)
                break
            else: 
                print "Invalid word, please try again"
                print "time remaining is: %0.2f" % time_limit
        else:
            print "Sorry you ran out of time"
            slow_time_start = time.time()
            best_word = pick_best_word(computers_hand, points_dict)
            slow_time_stop = time.time()
            slow_time_total = slow_time_stop - slow_time_start
            fast_time_start = time.time()
            fastest_word = pick_best_word_faster(computers_hand, rearrange_dict)
            fast_time_stop = time.time()
            fast_time_total = fast_time_stop - fast_time_start
            print "The computers word would have been %s scoring : %i in %0.5f seconds" \
            % (best_word, points_dict[best_word], slow_time_total)
            print "The fast computers word would have been %s scoring : %i in %0.5f seconds" \
            % (fastest_word, points_dict[best_word], fast_time_total)
            break

#
# Problem #5: Playing a game
# Make sure you understand how this code works!
# 
def play_game(points_dict):
    """
    Allow the user to play an arbitrary number of hands.

    * Asks the user to input 'n' or 'r' or 'e'.

    * If the user inputs 'n', let the user play a new (random) hand.
      When done playing the hand, ask the 'n' or 'e' question again.

    * If the user inputs 'r', let the user play the last hand again.

    * If the user inputs 'e', exit the game.

    * If the user inputs anything else, ask them again.
    """
    hand = deal_hand(HAND_SIZE) # random init
    while True:
        cmd = raw_input('Enter n to deal a new hand, r to replay the last hand, or e to end game: ')
        if cmd == 'n':
            hand = deal_hand(HAND_SIZE)
            play_hand(hand.copy(), points_dict)
        elif cmd == 'r':
            play_hand(hand.copy(), points_dict)
        elif cmd == 'e':
            print "Thanks for playing, maybe you can try again another time?"
            break
        else:
            print "Invalid command."

#
# Build data structures used for entire session and play game
#
if __name__ == '__main__':
    word_list = load_words()
    points_dict = get_words_to_points(word_list)
    rearrange_dict = get_word_rearrangements(word_list)
    #time_limit = get_time_limit(points_dict, 5)
    play_game(points_dict)

#
# Problem 5 #
# I think my original pick_best_word function is running in linear time 
# linked to the size of the word_list.  It's not quite that simple because
# as the hand size grows the number of potential words added to the 
# makeable_words list grows which then have to be itterated over to determine
# the best but I think this is neglible as once the hand size gets very large
# (large enough that there are enough letters so every word can be made) 
# then the number of opperations won't increase any more by adding to the hand 
# size.
#
# I think the pick_best_word_faster function runs in at least exponetial time 
# but linked to the size of the hand.  This is because the function that splits
# the hand into all the substrings takes a looong time to run as hand size gets
# larger and there are clearly more than linear opperations in it.  Also the
# number of substrings produced which then have to be looped through increases
# in a more than linear fashion with respect to hand size, perhaps the overall
# order of growth is something like exponential, it certainly takes a long time
# to run very quickly.  This would reflect the results I got from timing my 
# program, as hand size increases this function becomes very very slow! (I 
# started running it with hand size 25 over 20 minutes ago and it hasn't
# finished yet where it took 17.80048 seconds with hand size 24)
Joe (Self-grade: Could be better)
Submitted 2 years ago | Permalink

Word games 2

Problem 4: i copy the function which find subset from BTheMad, thanks!

this problem is really hard to comprehend what it is talking about.

then i watch lecture 11 in which the prof. said the problem set doesn't tell you everything and you have to do what you think is reasonable.

so i quit guessing what the problem want me to do, and just did what i think can solve the problem. as required, i create the rearrangement_dict whose key is a sorted string, and the value is a list, containing all valid word that can be spelled with the letter in the key

for example: {'aelpp':[apple,appel]}

suppose the given hand is {'a':1,'p':2,'l':1,'e':1}

both 'apple' and 'appel' are correct

first i turn the given hand to a sorted list [a,e,l,p,p]

then find each possible subset of it, turn it back to string and stor in a list.

'aelpp' is one of the subset. then find such value in the rearrangement_dict, and keep the highest score

# Problem Set 6: 6.00 Word Game
# Name: Joe Li
# Time: 8:30
#

import random
import string
import time

VOWELS = 'aeiou'
CONSONANTS = 'bcdfghjklmnpqrstvwxyz'
HAND_SIZE = 7

SCRABBLE_LETTER_VALUES = {
    'a': 1, 'b': 3, 'c': 3, 'd': 2, 'e': 1, 'f': 4, 'g': 2, 'h': 4, 'i': 1, 'j': 8, 'k': 5, 'l': 1, 'm': 3, 'n': 1, 'o': 1, 'p': 3, 'q': 10, 'r': 1, 's': 1, 't': 1, 'u': 1, 'v': 4, 'w': 4, 'x': 8, 'y': 4, 'z': 10
}

# -----------------------------------
# Helper code
# (you don't need to understand this helper code)

WORDLIST_FILENAME = "words.txt"

def load_words():
    """
    Returns a list of valid words. Words are strings of lowercase letters.
    
    Depending on the size of the word list, this function may
    take a while to finish.
    """
    print "Loading word list from file..."
    # inFile: file
    inFile = open(WORDLIST_FILENAME, 'r', 0)
    # wordlist: list of strings
    wordlist = []
    for line in inFile:
        wordlist.append(line.strip().lower())
    print "  ", len(wordlist), "words loaded."
    return wordlist

def get_frequency_dict(sequence):
    """
    Returns a dictionary where the keys are elements of the sequence
    and the values are integer counts, for the number of times that
    an element is repeated in the sequence.

    sequence: string or list
    return: dictionary
    """
    # freqs: dictionary (element_type -> int)
    freq = {}
    for x in sequence:
        freq[x] = freq.get(x,0) + 1
    return freq


# (end of helper code)
# -----------------------------------

def get_word_score(word, n):
    """
    Returns the score for a word. Assumes the word is a
    valid word.

    The score for a word is the sum of the points for letters
    in the word, plus 50 points if all n letters are used on
    the first go.

    Letters are scored as in Scrabble; A is worth 1, B is
    worth 3, C is worth 3, D is worth 2, E is worth 1, and so on.

    word: string (lowercase letters)
    returns: int >= 0
    """
    score=0
    for letter in word:
        score+=SCRABBLE_LETTER_VALUES[letter]
    if len(word)==n:
        score+=50
    return score
    

#
# Make sure you understand how this function works and what it does!
#
def display_hand(hand):
    """
    Displays the letters currently in the hand.

    For example:
       display_hand({'a':1, 'x':2, 'l':3, 'e':1})
    Should print out something like:
       a x x l l l e
    The order of the letters is unimportant.

    hand: dictionary (string -> int)
    """
    for letter in hand.keys():
        for j in range(hand[letter]):
            print letter,              # print all on the same line
    print                              # print an empty line'

#
# Make sure you understand how this function works and what it does!
#
def deal_hand(n):
    """
    Returns a random hand containing n lowercase letters.
    At least n/3 the letters in the hand should be VOWELS.

    Hands are represented as dictionaries. The keys are
    letters and the values are the number of times the
    particular letter is repeated in that hand.

    n: int >= 0
    returns: dictionary (string -> int)
    """
    hand={}
    num_vowels = n / 3
    
    for i in range(num_vowels):
        x = VOWELS[random.randrange(0,len(VOWELS))]
        hand[x] = hand.get(x, 0) + 1
        
    for i in range(num_vowels, n):    
        x = CONSONANTS[random.randrange(0,len(CONSONANTS))]
        hand[x] = hand.get(x, 0) + 1
        
    return hand

def update_hand(hand, word):
    """
    Assumes that 'hand' has all the letters in word.
    In other words, this assumes that however many times
    a letter appears in 'word', 'hand' has at least as
    many of that letter in it. 

    Updates the hand: uses up the letters in the given word
    and returns the new hand, without those letters in it.

    Has no side effects: does not mutate hand.

    word: string
    hand: dictionary (string -> int)    
    returns: dictionary (string -> int)
    """
    hand2=hand.copy()
    for letter in word:
        hand2[letter]-=1
    return hand2
        

def is_valid_word(word, hand, points_dict):
    """
    Returns True if word is in the word_list and is entirely
    composed of letters in the hand. Otherwise, returns False.
    Does not mutate hand or word_list.
    
    word: string
    hand: dictionary (string -> int)
    word_list: list of lowercase strings
    """
    if word not in points_dict:     # if the word is not a actual word
        return False
    for letter in word:
        if not letter in hand:      # if the letter in word is not available
            return False
    hand2=update_hand(hand,word)
    for i in hand2:                 # if the letter is available but not enough
        if hand2.get(i)<0:
            return False
    return True

def play_game(word_list):
    """
    Allow the user to play an arbitrary number of hands.

    * Asks the user to input 'n' or 'r' or 'e'.

    * If the user inputs 'n', let the user play a new (random) hand.
      When done playing the hand, ask the 'n' or 'e' question again.

    * If the user inputs 'r', let the user play the last hand again.

    * If the user inputs 'e', exit the game.

    * If the user inputs anything else, ask them again.
    """
    # TO DO ...
    #play_hand(deal_hand(HAND_SIZE), word_list) # delete this once you've completed Problem #4
    
    ## uncomment the following block of code once you've completed Problem #4
    hand = deal_hand(HAND_SIZE) # random init
    while True:
        cmd = raw_input('Enter n to deal a new hand, r to replay the last hand, or e to end game: ')
        if cmd == 'n':
            hand = deal_hand(HAND_SIZE)
            play_hand(hand.copy(), points_dict,time_limit)
            print
        elif cmd == 'r':
            play_hand(hand.copy(), points_dict,time_limit)
            print
        elif cmd == 'e':
            break
        else:
            print "Invalid command."

            

#################################
#                               #
#   Problem Set 6 Starts here   #
#                               #
#################################



#
# Problem #1: How long?
# Problem #2: Time Limit
#
def play_hand(hand, points_dict, time_limit):
    """
    Allows the user to play the given hand, as follows:

    * The hand is displayed.
    
    * The user may input a word.

    * An invalid word is rejected, and a message is displayed asking
      the user to choose another word.

    * When a valid word is entered, it uses up letters from the hand.

    * After every valid word: the score for that word and the total
      score so far are displayed, the remaining letters in the hand 
      are displayed, and the user is asked to input another word.

    * The sum of the word scores is displayed when the hand finishes.

    * The hand finishes when there are no more unused letters.
      The user can also finish playing the hand by inputing a single
      period (the string '.') instead of a word.

    * The final score is displayed.

      hand: dictionary (string -> int)
      word_list: list of lowercase strings
    """
    total=0                 # total score
    current=hand.copy()     # current hand
    left=1
    #time_limit=8
    print 'Enter time limit, in seconds, for players: '+str(time_limit)
    time_remain=time_limit
    time_exceeds=False      # if time_exceeds: break
    while left!=0:          # there is still letter left in hand
        print 'Current Hand: ',
        display_hand(current)
        start_time = time.time()    # time start
        # word=str(raw_input('Enter word, or a . to indicate that you are finished: '))
        word=pick_best_word_faster(current,points_dict)
        print 'Enter word, or a . to indicate that you are finished: '+word
        end_time = time.time()      # time end
        total_time = end_time - start_time
        print 'It took %0.2f seconds to provide an answer.' % total_time
        time_remain-=total_time
        if time_remain<0:           # time_exceeds
            print 'Total time exceeds '+str(time_limit)+' seconds. You scored '+str(total)+' points.'
            time_exceeds=True
            break
        print 'You have '+str(time_remain)+' seconds remaining.'
        if word!='.':               # test word
            if is_valid_word(word,current,points_dict):
                current=update_hand(current,word)
                left=0              # reset left
                for letter in current:
                    left+=current.get(letter)   # letter left in hand
                score=round(get_word_score(word,HAND_SIZE)/float(total_time),2)
                total+=score
                print word+' earned '+str(score)+' points. Total: '+str(total)+' points'
            else:                   # word invalid
                print 'Invalid word, please try again.'
        else:                       # choose finished
            left=0
    if time_exceeds==False:         # if not time_exceeds: print the total score
                                    # if time_exeeds: have already break
        print 'Total score: '+str(total)+' points.'



#
# Problem #3: Computer Player
#
def get_words_to_points(word_list):
    """
    Return a dict that maps every word in word_list to its point value.
    """
    points = {}
    for word in word_list:
        value=0
        for letter in word:
            value+=SCRABBLE_LETTER_VALUES[letter]
        points[word]=value
    return points
            


def pick_best_word(hand, points_dict):
    """
    Return the highest scoring word from points_dict
    that can be made with the given hand.
    Return '.' if no words can be made with the given hand.
    """
    highest=''
    for word in points_dict:
        if is_valid_word(word,hand,points_dict):
            if highest=='' or points_dict[word]>points_dict[highest]:
                highest=word
    if highest!='':
        return highest
    return '.'  # no word can be spelled with current hand

def get_time_limit(points_dict, k):
    """
    Return the time limit for the computer player as a function of the multiplier k.
    points_dict should be the same dictionary that is created by get_words_to_points.
    """
    start_time = time.time()
    # Do some computation. The only purpose of the computation is so we can # figure out how long your computer takes to perform a known task.
    for word in points_dict:
        get_frequency_dict(word)
        get_word_score(word, HAND_SIZE)
    end_time = time.time()
    return (end_time - start_time) * 1

#
# Problem #4: Even Faster Computer Player
#
def sort_string(word):
    """
    Return a string with the exact letter of input but in sorted order
    word:string
    """
    order=[]
    for letter in word:         # change a string to list
        order.append(letter)
    order.sort()
    sorted_word=''              # sort the list
    for i in range(len(order)): # change the list back to string
        sorted_word+=order[i]
    return sorted_word
        

def get_word_rearrangements(word_list):
    """
    Return a dictionary d
    key: sorted string
    value: a list of the word can be spelled with the letter in the sorted string
    all of the word in word_list is included in this dict
    """
    d={}
    for word in word_list:
        string=sort_string(word)    # sort the letter of every word in word_list
        if d.get(string)==None:     # if the subset hasn't been created yet
            d[string]=[word]        # creat a list containing the subset
        else:                       # if the subset has been created
            d[string].append(word)  # append the value with the new string
    return d
        
def get_sets_recursive(word, word_list):
    """
    Generate all possible string from a given letter list, without order changed
    word: list
    """
    # this function is written by BTheMad, thanks.
    # i have been think this prolem all night
    # i guess i'm just too stupid too figure it out
    if len(word) > 0:
        str_word = "".join(word)
        if str_word not in word_list:
            word_list.append(str_word)
        for letter in word:
            tmp_word = word[:]
            tmp_word.remove(letter)
            if len(tmp_word) > 0 and "".join(tmp_word) not in word_list:
                get_sets_recursive(tmp_word, word_list)
    return word_list
        
    
    
def pick_best_word_faster(hand,points_dict):
    """
    Return the highest scoring word from points_dict
    that can be made with the given hand.
    Return '.' if no words can be made with the given hand.
    """
    sorthand=[]
    highest=''
    for letter in hand.keys():
        for j in range(hand[letter]):
            sorthand.append(letter)
            sorthand.sort()             # make a sorted list of given hand
    s=get_sets_recursive(sorthand,[])   # find every subset
    for p in s:
        if p in rearrange_dict:
            for word in rearrange_dict[p]:  
                if is_valid_word(word,hand,points_dict):
                    if highest=='' or points_dict[word]>points_dict[highest]:   # store the word with highest score
                        highest=word
    if highest!='':
        return highest
    return '.'  # no word can be spelled with current hand


#
# Problem #5: Algorithm Analysis
#
# algorithm of pick_best_word: O(n**2)
# 1.traverse all the words in word_list
# 2.check if the given hand can spell the word
# 3.if it can, keep the score untill find the higher score
#
# algorithm of pick_best_word_faster: O(n)
# 1.map every word in word_list to a ordered string
# 2.find every sorted subset of the given hand
# 3.for each subset, find out the word mapped to it, keep the highest score


        


#
# Build data structures used for entire session and play game
#
if __name__ == '__main__':
    word_list = load_words()
    points_dict=get_words_to_points(word_list)
    rearrange_dict=get_word_rearrangements()
    time_limit=get_time_limit(points_dict,1)
    play_game(word_list)
    
BTheMad (Self-grade: Could be better)
Submitted 2 years ago | Permalink

Very strange results in fast_pick =/ For some reason it takes too much time to generate all the sub-sets and this kills all the benefits from further access to dictionary. This version contains some profiling and launches both methods.

# Problem Set 5: 6.00 Word Game
# Name:
# Collaborators:
# Time:
#

import random
import time

VOWELS = 'aeiou'
CONSONANTS = 'bcdfghjklmnpqrstvwxyz'
HAND_SIZE = 10

SCRABBLE_LETTER_VALUES = {
    'a': 1, 'b': 3, 'c': 3, 'd': 2, 'e': 1, 'f': 4, 'g': 2, 'h': 4, 'i': 1, 'j': 8, 'k': 5, 'l': 1, 'm': 3, 'n': 1, 'o': 1, 'p': 3, 'q': 10, 'r': 1, 's': 1, 't': 1, 'u': 1, 'v': 4, 'w': 4, 'x': 8, 'y': 4, 'z': 10
}

# -----------------------------------
# Helper code
# (you don't need to understand this helper code)

WORDLIST_FILENAME = "words.txt"

def load_words():
    print "Loading word list from file..."
    # inFile: file
    inFile = open(WORDLIST_FILENAME, 'r', 0)
    # wordlist: list of strings
    wordlist = []
    for line in inFile:
        wordlist.append(line.strip().lower())
    print "  ", len(wordlist), "words loaded."
    return wordlist

def get_frequency_dict(sequence):
    # freqs: dictionary (element_type -> int)
    freq = {}
    for x in sequence:
        freq[x] = freq.get(x,0) + 1
    return freq


# (end of helper code)
# -----------------------------------

#
# Problem #1: Scoring a word
#
def get_word_score(word, n):
    score = 0
    if len(word) == 0:
        return score
    elif word == '.':
        return score
    for letter in word:
        score += SCRABBLE_LETTER_VALUES[letter]
    if len(word) == n:
        score += 50
    return score

#
# Make sure you understand how this function works and what it does!
#
def display_hand(hand):
    for letter in hand.keys():
        for j in range(hand[letter]):
            print letter,              # print all on the same line
    print                              # print an empty line

#
# Make sure you understand how this function works and what it does!
#
def deal_hand(n):
    hand={}
    num_vowels = n / 3

    for i in range(num_vowels):
        x = VOWELS[random.randrange(0,len(VOWELS))]
        hand[x] = hand.get(x, 0) + 1

    for i in range(num_vowels, n):
        x = CONSONANTS[random.randrange(0,len(CONSONANTS))]
        hand[x] = hand.get(x, 0) + 1

    return hand

#
# Problem #2: Update a hand by removing letters
#
def update_hand(hand, word):
    for letter in word:
        hand[letter] = hand.get(letter, 0) - 1
        if hand[letter] == 0:
            del hand[letter]
    return hand

#
# Problem #3: Test word validity
#
def is_valid_word(word, hand, points_dict):
    freq = get_frequency_dict(word)
    for letter in word:
        if freq[letter] > hand.get(letter, 0):
            return False
    if points_dict.get(word) > 0:
        return True
    else:
        return False

#
# Problem #6.3: Get points for words
#
def get_words_to_points(word_list):
    """Return a dict that maps every word in a word_list to its point value."""
    points_dict = {}
    for word in word_list:
        points_dict[word] = get_word_score(word, HAND_SIZE)

    return points_dict

#
# Problem #6.3: Get points for words
#
def pick_best_word(hand, points_dict):
    """Find best word with given hand and points dictionary"""
    word = "."
    score = 0
    best_score = 0
    for tmp_word in points_dict.keys():
        if is_valid_word(tmp_word, hand, points_dict):
            score = get_word_score(tmp_word, HAND_SIZE)
            if score > best_score:
                word = tmp_word
                best_score = score
    return word

#
# Problem #6.4: Get a sequence of letters for a word
#
def get_word_rearrangements(word):
    """Get a string of letters, that are contained in a word"""
    tmp_list = list(word)
    tmp_list.sort()
    sequence = "".join(tmp_list)

    return sequence

#
# Problem #6.4: Get a sequence of letters for a hand
#
def get_hand_rearrangements(hand):
    """ Get a rearrangements for a given hand """
    tmp_list = []
    for letter in hand.keys():
        for j in range(hand[letter]):
            tmp_list.append(letter)
    tmp_list.sort()
    sequence = "".join(tmp_list)

    return sequence

#
# Problem #6.4: Create a rearrangement dictionary
#
def get_rearrange_dic(word_list):
    """Create a list of words as sequences of letter for a word list"""
    rearrange_dict = {}
    for word in word_list:
        rearrange_dict[get_word_rearrangements(word)] = word
    return rearrange_dict

def get_sets_recursive(word, word_list):
    """Generate all possible string from a given, without order changed"""
    if len(word) > 0:
        str_word = "".join(word)
        if str_word not in word_list:
            word_list.append(str_word)
        for letter in word:
            tmp_word = word[:]
            tmp_word.remove(letter)
            if len(tmp_word) > 0 and "".join(tmp_word) not in word_list:
                get_sets_recursive(tmp_word, word_list)
    return word_list

#
# Problem #6.4: Generate all sequences from a word
#
def get_all_hand_sets(word):
    """Generate all possible string from a given, without order changed"""
    word_list = get_sets_recursive(list(word), [])

    return word_list

#
# Problem #6.4: Pick word faster, than ever
#
def pick_best_word_faster(hand, rearrange_dict):
    """Faster version of picking word algorithm"""
    hand_rearrangements = get_hand_rearrangements(hand)
    hand_sets = get_all_hand_sets(hand_rearrangements)
    max_score_word = '.'
    max_score = 0
    score = 0
    word = '.'
    for sub_set in hand_sets:
        if sub_set in rearrange_dict.keys():
            word = rearrange_dict[sub_set]
            score = get_word_score(word, HAND_SIZE)
            if score > max_score:
                max_score_word = word
                max_score = score

    return max_score_word

#
# Problem #6.3: Get timelimit for a computer
#
def get_time_limit(points_dict, k):
    """
    Return the time limit for the computer player as a function of the multiplier k.
    points_dict should be the same dictionary that is created by get_words_to_points.
    """
    start_time = time.time()
    # Do some computation. The only purpose of the computation is so we can
    # figure out how long your computer takes to perform a known task.
    for word in points_dict:
        get_frequency_dict(word)
        get_word_score(word, HAND_SIZE)
    end_time = time.time()
    return (end_time - start_time) * k

#
# Problem #4: Playing a hand
#
def play_hand(hand, points_dict, rearrange_dict):
    """
      hand: dictionary (string -> int)
      word_list: list of lowercase strings
    """
    total_score = 0
    game_finished = False
    time_limit = get_time_limit(points_dict, 10)
    print "Time limit is set to %0.2f" %time_limit
    total_time = 0
    time_left = 0

    while not game_finished:
        score = 0
        round_start = 0
        round_end = 0
        round_time = 0
        print "Current hand: ",
        display_hand(hand)
        round_start = time.time()
        round_start = time.time()
        word = pick_best_word(hand, points_dict)
        round_end = time.time()
        round_time = round_end - round_start
        print "###################### %s #########################" %word
        print "###################### %d #########################" %get_word_score(word, HAND_SIZE)
        print "###################### %0.5f #########################" %round_time
        round_start = time.time()
        word = pick_best_word_faster(hand, rearrange_dict)
        round_end = time.time()
        round_time = round_end - round_start
        print "###################### %s #########################" %word
        print "###################### %d #########################" %get_word_score(word, HAND_SIZE)
        print "###################### %0.5f #########################" %round_time
        round_end = time.time()
        round_time = round_end - round_start
        total_time += round_time
        time_left = time_limit - total_time
        if word == ".":
            game_finished = True
        elif len(hand) == 0:
            game_finished = True
        elif total_time > time_limit:
            print "Total time exceeds %d seconds" %time_limit
            game_finished = True
        else:
            if is_valid_word(word, hand, points_dict):
                score = get_word_score(word, HAND_SIZE) / round_time
                total_score += score
                print "It took %0.2f seconds to provide an answer." %round_time
                print "You have %0.2f seconds remaining." %(time_left)
                print "%s earned %0.2f points. Total: %0.2f points." %(word, score, total_score)
                hand = update_hand(hand.copy(), word)
            else:
                print "Invalid word, please try again."
                print "You have %0.2f seconds remaining." %(time_left)



    if game_finished:
        print "Total score: %0.2f" %total_score


#
# Problem #5: Playing a game
# Make sure you understand how this code works!
#
def play_game(points_dict, rearrange_dict):
    hand = deal_hand(HAND_SIZE) # random init
    while True:
        cmd = raw_input('Enter n to deal a new hand, r to replay the last hand, or e to end game: ')
        if cmd == 'n':
            hand = deal_hand(HAND_SIZE)
            play_hand(hand.copy(), points_dict, rearrange_dict)
            print
        elif cmd == 'r':
            play_hand(hand.copy(), points_dict, rearrange_dict)
            print
        elif cmd == 'e':
            break
        else:
            print "Invalid command."

#
# Build data structures used for entire session and play game
#
if __name__ == '__main__':
    word_list = load_words()
    points_dict = get_words_to_points(word_list)
    rearrange_dict = get_rearrange_dic(word_list)
    play_game(points_dict, rearrange_dict)
shaggorama (Self-grade: Outstanding)
Submitted 3 years ago | Permalink
#!/usr/bin/env python
#-*- coding:utf-8 -*-

import random
import string
import time

VOWELS = 'aeiou'
CONSONANTS = 'bcdfghjklmnpqrstvwxyz'
HAND_SIZE = 7

SCRABBLE_LETTER_VALUES = {
    'a': 1, 'b': 3, 'c': 3, 'd': 2, 'e': 1, 'f': 4, 'g': 2, 'h': 4, 'i': 1, 'j': 8, 'k': 5, 'l': 1, 'm': 3, 'n': 1, 'o': 1, 'p': 3, 'q': 10, 'r': 1, 's': 1, 't': 1, 'u': 1, 'v': 4, 'w': 4, 'x': 8, 'y': 4, 'z': 10
}

# -----------------------------------
# Helper code
# (you don't need to understand this helper code)

WORDLIST_FILENAME = "/home/dave/Documents/python/Assignments/ps5and6/words.txt"

def load_words():
    """
    Returns a list of valid words. Words are strings of lowercase letters.
    
    Depending on the size of the word list, this function may
    take a while to finish.
    """
    print "Loading word list from file..."
    # inFile: file
    inFile = open(WORDLIST_FILENAME, 'r', 0)
    # wordlist: list of strings
    wordlist = []
    for line in inFile:
        wordlist.append(line.strip().lower())
    print "  ", len(wordlist), "words loaded."
    return wordlist

def get_frequency_dict(sequence):
    """
    Returns a dictionary where the keys are elements of the sequence
    and the values are integer counts, for the number of times that
    an element is repeated in the sequence.

    sequence: string or list
    return: dictionary
    """
    # freqs: dictionary (element_type -> int)
    freq = {}
    for x in sequence:
        freq[x] = freq.get(x,0) + 1
    return freq

def get_time_limit(points_dict, k):
    """
    Return the time limit for the computer player as a function of the
    multiplier k.
    points_dict should be the same dictionary that is created by
    get_words_to_points.
    """
    start_time = time.time()
    # Do some computation. The only purpose of the computation is so we can
    # figure out how long your computer takes to perform a known task.
    for word in points_dict:
        get_frequency_dict(word)
        get_word_score(word, HAND_SIZE)
    end_time = time.time()
    return (end_time - start_time) * k

# (end of helper code)
# -----------------------------------

#
# Problem #1: Scoring a word
#
def get_word_score(word, n):
	"""
    Returns the score for a word. Assumes the word is a
    valid word.

    The score for a word is the sum of the points for letters
    in the word, plus 50 points if all n letters are used on
    the first go.

    Letters are scored as in Scrabble; A is worth 1, B is
    worth 3, C is worth 3, D is worth 2, E is worth 1, and so on.

    word: string (lowercase letters)
    returns: int >= 0
	"""
	score = 0
	for letter in word:
		score += SCRABBLE_LETTER_VALUES[letter]
	if len(word) == n:
		score += 50
	return score	

def get_words_to_points(word_list):
	"""
	Return a dict that maps every word in word_list to its point value.
	"""
	scores = {}
	for word in word_list:
		score = get_word_score(word, HAND_SIZE)
		scores[word] = score
	return scores

def rearrange_letters(word):
	list1 = []
	for letter in word:
		list1.append(letter)
	list1.sort()
	word = ""
	for letter in list1:
		word = word + letter
	return word

def get_word_rearrangements(word_list):
	d ={}
	for word in word_list:
		word2 = rearrange_letters(word)
		try:
			indict = d[word2]
			dict_score = get_word_score(indict, HAND_SIZE)
			new_score = get_word_score(word, HAND_SIZE)
			if new_score > dict_score:
				d[word2] = word
			else:
				continue
		except KeyError:
			d[word2] = word
	return d
#
# Make sure you understand how this function works and what it does!
#
def display_hand(hand):
    """
    Displays the letters currently in the hand.

    For example:
       display_hand({'a':1, 'x':2, 'l':3, 'e':1})
    Should print out something like:
       a x x l l l e
    The order of the letters is unimportant.

    hand: dictionary (string -> int)
    """
    for letter in hand.keys():
        for j in range(hand[letter]):
# print all on the same line
            print letter,     
# function in template was an empty 'print' statement, 
# but the fucntion kept printing 'None' instead of a new line. This fixed it.	          
    return ""				

#
# Make sure you understand how this function works and what it does!
#
def deal_hand(n):
    """
    Returns a random hand containing n lowercase letters.
    At least n/3 the letters in the hand should be VOWELS.

    Hands are represented as dictionaries. The keys are
    letters and the values are the number of times the
    particular letter is repeated in that hand.

    n: int >= 0
    returns: dictionary (string -> int)
    """
    hand={}
    num_vowels = n / 3
    
    for i in range(num_vowels):
        x = VOWELS[random.randrange(0,len(VOWELS))]
        hand[x] = hand.get(x, 0) + 1
        
    for i in range(num_vowels, n):    
        x = CONSONANTS[random.randrange(0,len(CONSONANTS))]
        hand[x] = hand.get(x, 0) + 1
    return hand

#
# Problem #2: Update a hand by removing letters
#
def update_hand(hand, word):
	"""
    Assumes that 'hand' has all the letters in word.
    In other words, this assumes that however many times
    a letter appears in 'word', 'hand' has at least as
    many of that letter in it. 

    Updates the hand: uses up the letters in the given word
    and returns the new hand, without those letters in it.

    Has no side effects: does not mutate hand.

    word: string
    hand: dictionary (string -> int)    
    returns: dictionary (string -> int)
	"""
	# remove letters used by word. Returns new hand minus 'word.' 
	# Does not deal new letters into hand.
	hand2 = hand.copy()	
	for letter in word:
		hand2[letter] += -1
	return hand2


#
# Problem #3: Test word validity
#
def is_valid_word(word, hand, points_dict):
	"""
    Returns True if word is in the word_list and is entirely
    composed of letters in the hand. Otherwise, returns False.
    Does not mutate hand or word_list.
    
    word: string
    hand: dictionary (string -> int)
    word_list: list of lowercase strings
	"""
	length_hand = sum(hand.values())
	if len(word) > length_hand:
		return False
	else:
		word_dict = get_frequency_dict(word)
		hand_temp = hand.copy()
		for letter in word_dict.keys():
			if letter not in hand.keys():
				return False
			else:
				hand_temp[letter] -= word_dict[letter]
				if hand_temp[letter] < 0:
					return False
		try:
			points_dict[word]
			return True
		except KeyError:
			return False

#
# Problem #4: Playing a hand
#


def pick_best_word(hand, points_dict):
	"""
    Return the highest scoring word from points_dict that can be made with the
    given hand.
    Return '.' if no words can be made with the given hand.
	"""
	best_word = ""
	best_score = 0
	for word in points_dict.keys():
		if is_valid_word(word, hand, points_dict):
			if points_dict[word] > best_score:
				best_word = word
	print best_word
	return best_word

def all_hands_sorted(hand):
	n = len(hand)
	hands = [hand]
	all_hands = build_hand(hand, hands, n)
	for hand in all_hands:
		hand.sort()
	return hands

# There has to be a better way to implement this. It's so dirty...
def build_hand(hand, hands, n):
	for letter in hand:
		hand2 = hand[:]
		hand2.remove(letter)
		if hand2 not in hands:
			hands.append(hand2)
			build_hand(hand2, hands, n-1)
		else:
			build_hand(hand2, hands, n - 1)
	return hands

def pick_best_word_faster(hand_dict, rearrange_dict):
	hand = []
	for letter in hand_dict.keys():
	        for j in range(hand_dict[letter]):
	            hand.append(letter)
	hands = all_hands_sorted(hand)
	best_word = ""
	score = 0
	for hand in hands:
		hand_string = ''
		for letter in hand:
			hand_string = hand_string + letter
		try:                        
			word = rearrange_dict[hand_string]
			new_score = get_word_score(word, HAND_SIZE)
			if new_score > score:
				best_word = word
				score = new_score
			
		except KeyError:
			continue
	print best_word
	return best_word

def play_hand(hand, points_dict, limit, plyr):
	"""
    Allows the user to play the given hand, as follows:

    * The hand is displayed.
    
    * The user may input a word.

    * An invalid word is rejected, and a message is displayed asking
      the user to choose another word.

    * When a valid word is entered, it uses up letters from the hand.

    * After every valid word: the score for that word and the total
      score so far are displayed, the remaining letters in the hand 
      are displayed, and the user is asked to input another word.

    * The sum of the word scores is displayed when the hand finishes.

    * The hand finishes when there are no more unused letters.
      The user can also finish playing the hand by inputing a single
      period (the string '.') instead of a word.

    * The final score is displayed.

      hand: dictionary (string -> int)
      word_list: list of lowercase strings
	"""
	n = sum(hand.values())
	score = 0
	while True:
		if sum(hand.values()) == 0:
			print "\nFinal score: ", score
			return
		else:
			print "Current hand: ", display_hand(hand)
			start_time = time.time()
			if plyr == 'c':
				word = pick_best_word_faster(hand, rearrange_dict)
				if word == '':
					word = '.'
			else:
				word = raw_input("\nEnter word, or a . to indicate that you are finished: ")
			end_time = time.time()
			total_time = end_time - start_time
			if total_time == 0:
				total_time = 0.0001
				print "It took %.2f seconds to provide an answer." % total_time
			if limit <= total_time:
				print "Time limit exceeded."
				print "Final score: ", score
				return
			else:
				limit -= total_time
			if is_valid_word(word, hand, points_dict):
				hand = update_hand(hand, word)
				word_score = get_word_score(word, n)
				word_score_t = word_score/total_time
				score += word_score_t
				print "Word score: ", word_score
				print "Score with time penalty %.2f" % word_score_t
				print "Current score: %.2f" % score
			elif word == '.':
				print "Final score: %.2f" % score
				return
			else:
				print "Not a valid word."
			print "Time remaining: %.2f" % limit

#
# Problem #5: Playing a game
# Make sure you understand how this code works!
# 
def play_game(points_dict):
    """
    Allow the user to play an arbitrary number of hands.

    * Asks the user to input 'n' or 'r' or 'e'.

    * If the user inputs 'n', let the user play a new (random) hand.
      When done playing the hand, ask the 'n' or 'e' question again.

    * If the user inputs 'r', let the user play the last hand again.

    * If the user inputs 'e', exit the game.

    * If the user inputs anything else, ask them again.
    """
    hand = deal_hand(HAND_SIZE) # random init
    k = get_time_limit(points_dict, 1)
    while True:
        cmd = raw_input('Enter n to deal a new hand, r to replay the last hand, or e to end game: ')
	if cmd == 'e':
		break
	else:
		while True:	
			plyr = raw_input('Human player (h) or computer player (c)?')
			if (plyr == 'h') or (plyr == 'c'):
				break
			else:
				print "Invalid input."
	if plyr == 'c':
		limit = k
	else:
		limit = int(raw_input('Enter time limit (sec) for players: '))
	if cmd == 'n':
       		hand = deal_hand(HAND_SIZE)
       		play_hand(hand.copy(), points_dict, limit, plyr)
       		print
       	elif cmd == 'r':
       		play_hand(hand.copy(), points_dict, limit, plyr)
       		print
       	else:
       		print "Invalid command."

#
# Build data structures used for entire session and play game
#
if __name__ == '__main__':
    word_list = load_words()
    points_dict = get_words_to_points(word_list)
    rearrange_dict = get_word_rearrangements(word_list)
    play_game(points_dict)

Comments:

theabraham
3 years ago

I got through this problem set O.K., but I'm stumped by problem 4. I'm not sure what it's asking and what the purpose of creating a dictionary with sorted letters is? Anyone able to explain this to me?

Sign up or log in to comment