Function variables: Whats the range of scope of a function - python

Recently I have been working on a word game in python, nonetheless I am having problems with a little detail.The idea of the game is to create words with letters in a hand given randomly. The function that coordinates the game asks the user to input a letter and according to it, lets him/her to play a new hand, play the same hand again or exit the game. The problem is that I cannot make the game to return to the previous hand and let the user play it again. Instead the code returns a modified version of the previous hand, so that if you inputted a word correctly the letters in that word won't be in it. I thought this could not be possible since the function that "updates the hand" lies on a different scope. I am working in python 3.2.1
I posted the entire code so you could test it and see what it does. The problem, however, lies in the functions: play_game, play_hand and update_hand.
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')
# 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 multiplied by the length of 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 character in word:
score = score + (SCRABBLE_LETTER_VALUES[character])
score = score * len(word)
if len(word) == 7:
score = 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,)
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 = int(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 modify hand.
word: string
hand: dictionary (string -> int)
returns: dictionary (string -> int)
"""
for i in word:
new_hand = hand
new_VOWELS = VOWELS
new_CONSONANTS = CONSONANTS
if i in VOWELS:
new_VOWELS = new_VOWELS.replace("i", "")
new_hand[i] = new_hand.get(i, 0) - 1
if new_hand[i] <= 0:
new_hand.pop(i, None)
else:
new_CONSONANTS = new_CONSONANTS.replace("i", "")
new_hand[i] = new_hand.get(i, 0) - 1
if new_hand[i] <= 0:
new_hand.pop(i, None)
return (new_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
"""
for character in word:
x = 0
if character in hand:
x = x + 1
if (x==(len(word)-1)) and (word in word_list):
return (True)
else:
return (False)
def calculate_handlen(hand):
handlen = 0
for v in hand.values():
handlen += v
return handlen
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
"""
score = 0
han = hand
while True:
print ("Score= ", score)
word = str(input("Enter your word: "))
value = is_valid_word(word, han, word_list)
if value == True:
print ("Congratulations, that word is worth", get_word_score(word, 7), "points")
print ("Please input another word")
han = update_hand(han, word)
print ("Current hand=")
display_hand(han)
score = score + get_word_score(word, 7)
elif word == "." or len(hand)==0:
break
else:
print ("Current hand=")
display_hand(han)
print ("Sorry, that word is not valid")
print ("Please choose another word")
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.
"""
global handy
handy = deal_hand(7)
while True:
print ("Use 'n' to play a new hand, 'r' to play the last hand again or 'e' to exit game")
inp = str(input("Enter a letter:'n' or 'r' or 'e': ="))
if inp == 'n':
hand = deal_hand(7)
handy = hand
print("Current hand =")
display_hand(hand)
play_hand(hand, word_list)
elif inp == 'r':
print("Current hand =")
display_hand(handy)
play_hand(handy, word_list)
elif inp == 'e':
exit(play_game)
else:
print ("Please input a valid letter")
if __name__ == '__main__':
word_list = load_words()
play_game(word_list)

Writing y = x makes y refer to the same thing as x. It does NOT copy x. With "immutable" objects, this looks like a copy because you have to make a new object in order for any "change" to be evident. With "mutable" objects like lists, because x and y both refer to the same object, changes to one name appear in the other.
You probably want to copy the list:
new_hand = hand[:] # this funny slicing notation produces a shallow copy of the original list

Related

Counting number of occurrences in a string

I need to return a dictionary that counts the number of times each letter in a predetermined list occurs. The problem is that I need to count both Upper and Lower case letters as the same, so I can't use .lower or .upper.
So, for example, "This is a Python string" should return {'t':3} if "t" is the letter being searched for.
Here is what I have so far...
def countLetters(fullText, letters):
countDict = {i:0 for i in letters}
lowerString = fullText.lower()
for i in lowerString:
if i in letters:
countDict[i] += 1
return countDict
Where 'letters' is the condition and fullText is the string I am searching.
The obvious issue here is that if the test is "T" rather than "t", my code won't return anything Sorry for any errors in my terminology, I am pretty new to this. Any help would be much appreciated!
To ignore capitalization, you need to input =
input = input.lower ()
.Lists all characters of the input text using list operations.
It can also be used as a word counter if you scan the space character.
input = "Batuq batuq BatuQ" # Reads all inputs up to the EOF character
input = input.replace('-',' ')#Replace (-, + .etc) expressions with a space character.
input = input.replace('.','')
input = input.replace(',','')
input = input.replace("`",'')
input = input.replace("'",'')
#input= input.split(' ') #if you use it, it will sort by the most repetitive words
dictionary = dict()
count = 0
for word in input:
dictionary[word] = input.count(word)
print(dictionary)
#Writes the 5 most repetitive characters
for k in sorted(dictionary,key=dictionary.get,reverse=True)[:5]:
print(k,dictionary[k])
Would something like this work that handles both case sensitive letter counts and non case sensitive counts?
from typing import List
def count_letters(
input_str: str,
letters: List[str],
count_case_sensitive: bool=True
):
"""
count_letters consumes a list of letters and an input string
and returns a dictionary of counts by letter.
"""
if count_case_sensitive is False:
input_str = input_str.lower()
letters = list(set(map(lambda x: x.lower(), letters)))
# dict comprehension - build your dict in one line
# Tutorial on dict comprehensions: https://www.datacamp.com/community/tutorials/python-dictionary-comprehension
counts = {letter: input_str.count(letter) for letter in letters}
return counts
# define function inputs
letters = ['t', 'a', 'z', 'T']
string = 'this is an example with sTrings and zebras and Zoos'
# case sensitive
count_letters(
string,
letters,
count_case_sensitive=True
)
# {'t': 2, 'a': 5, 'z': 1, 'T': 1}
# not case sensitive
count_letters(
string,
letters,
count_case_sensitive=False
)
# {'a': 5, 'z': 2, 't': 3} # notice input T is now just t in dictionary of counts
Try it - like this:
def count_letters(fullText, letters):
countDict = {i: 0 for i in letters}
lowerString = fullText.lower()
for i in lowerString:
if i in letters:
countDict[i] += 1
return countDict
test = "This is a Python string."
print(count_letters(test, 't')) #Output: 3
You're looping over the wrong string. You need to loop over lowerString, not fullString, so you ignore the case when counting.
It's also more efficient to do if i in countDict than if i in letter.
def countLetters(fullText, letters):
countDict = {i.lower():0 for i in letters}
lowerString = fullText.lower()
for i in lowerString:
if i in countDict:
countDict[i] += 1
return countDict
What you can do is simply duplicate the dict with both upper and lowercase like so:
def countLetters(fullText, letters):
countDict = {}
for i in letters:
countDict[i.upper()]=0
countDict[i.lower()]=0
lowerString = fullText.lower()
letters = letters.lower()
for i in lowerString:
if i in letters:
countDict[i] += 1
if (i!=i.upper()):
countDict[i.upper()] +=1
return countDict
print(countLetters("This is a Python string", "TxZY"))
Now some things you can also do is loop over the original string and change countDict[i] += 1 to countDict[i.lower()] +=1
Use the Counter from the collections module
from collections import Counter
input = "Batuq batuq BatuQ"
bow=input.split(' ')
results=Counter(bow)
print(results)
output:
Counter({'Batuq': 1, 'batuq': 1, 'BatuQ': 1})

To look for words in a dictionary based on random input letters, will this code perform effeciently?

I'm new to coding. I tried to build a simple code that can take a subset of alphabet letters and return back a valid words from a text based dictionary.
In the code below, I ask the user to input a number of characters (e.g. abcdef) then the program will make words out of these letters.
Now my question is this the best method to do it in term of performance, code length and the blocks sequence? If not, can you suggest a better way to do?
#Read the dictionary
fh = open('C:\\english-dict2.txt')
dict = []
while True:
line = fh.readline()
dict.append(line.strip())
if not line:
break
fh.close()
#Input letters
letters = input("Please enter your letters: ")
letters_list=[]
for l in letters:
letters_list.append(l)
mini = 2 #default value
maks = len(letters_list)
mini = input("Minimum length of the word (default is 2): ")
if mini == "":
mini = 2 #default value
mini = int(mini)
#Here I create a new dictionary based on the number of letters input or less than.
newdic=[]
for words1 in dict:
if len(words1) <= maks and len(words1)>= mini:
newdic.append(words1)
for words2 in newdic:
ok = 1
for i in words2:
if i in letters_list:
ok = ok * 1
else:
ok = ok * 0
if ok == 1:
print(words2)
Lists are inefficient for lookups. You should use a dict of sets instead to index every word with each letter in the word, so that you can simply use set intersection to find the words that contain all of the given letters:
from functools import reduce
d = {}
with open('C:\\english-dict2.txt') as f:
for l in f:
w = l.strip()
for c in set(w):
d.setdefault(c, set()).add(w)
letters = input("Please enter your letters: ")
print(reduce(lambda a, b: a & d[b], letters[1:], d[letters[0]]))
For example, given a dictionary of the following words:
apple
book
cat
dog
elephant
The index dictionary d would become:
{'p': {'elephant', 'apple'}, 'a': {'cat', 'elephant', 'apple'}, 'l': {'elephant', 'apple'}, 'e': {'elephant', 'apple'}, 'k': {'book'}, 'b': {'book'}, 'o': {'book', 'dog'}, 'c': {'cat'}, 't': {'cat', 'elephant'}, 'd': {'dog'}, 'g': {'dog'}, 'h': {'elephant'}, 'n': {'elephant'}}
Here's a sample input/output of the above code, where both the words apple and elephant are found to contain both of the letters a and e:
Please enter your letters: ae
{'apple', 'elephant'}
From here you can easily filter the resulting set based on a given minimum number of letters if you want.
modification 1: You do not need to loop over letters in letters, just
letters_list=list(letters)
is enough to make list of letters.
modification2: You can make sure any mini can be handled using:
try:
mini = int(mini)
except:
mini = 2
For your dictionary, you don't need to iterate through using readline(), just do:
with open(path) as fh:
dict = readlines()
This will also safely close your file, even if there's an error. If you want to do lookups for words, I'd use a set rather than a list, as the lookups in sets are O(1), whereas lookups in list are not, they are O(n).
d_set = set(dict)
This way if you want to create all combinations of letters, you can look them up like so:
import itertools
letters = input("Input your letters, please ")
def check_for_match(combos):
for combo in combos:
if combo in d_set:
yield combo
i = len(letters)
my_list = []
while i:
combos = itertools.permutations(words, i)
results = list(check_for_match(combos))
my_list = [*my_list, *results]
i-=1
This will give you all of the permutations of letters, check if they are in your dictionary, and build my_list if they are. I think that's what you are looking for

Code not running properly

The code I have below is supposed to run the number of words that start with a certain letter, but when I run it, the counts are all 0, instead of what they should be: {'I': 2, 'b': 2, 't': 3, 'f': 1}. I appreciate any help. Thanks!
def initialLets(keyStr):
'''Return a dictionary in which each key is the initial letter of a word in t and the value is the number of words that begin with that letter. Upper
and lower case letters should be considered different letters.'''
inLets = {}
strList = keyStr.split()
firstLets = []
for words in strList:
if words[0] not in firstLets:
firstLets.append(words[0])
for lets in firstLets:
inLets[lets] = strList.count(lets)
return inLets
text = "I'm born to trouble I'm born to fate"
print(initialLets(text))
You can try this:
text = "I'm born to trouble I'm born to fate"
new_text = text.split()
final_counts = {i[0]:sum(b.startswith(i[0]) for b in new_text) for i in new_text}
Output:
{'I': 2, 'b': 2, 't': 3, 'f': 1}
You don't have a counter as you append the letter but not its number of occurrences.
So to simplify:
def initialLets(keyStr):
'''Return a dictionary in which each key is the initial letter of a word in t and the value is the number of words that begin with that letter. Upper
and lower case letters should be considered different letters.'''
strList = keyStr.split()
# We initiate the variable that gonna take our results
result = {}
for words in strList:
if words[0] not in result:
# if first letter not in result then we add it to result with counter = 1
result[words[0]] = 1
else:
# We increase the number of occurence
result[words[0]] += 1
return result
text = "I'm born to trouble I'm born to fate"
print(initialLets(text))
Firstly, you are checking if the first letter of the word is in the list before putting it in. That would just make the list comprise of only 1 of each letter. Secondly, your strList is a list of each word, instead of inLets[lets] = strList.count(lets), it should be inLets[lets] = firstLets.count(lets)... While your current code isn't the cleanest way to do it, this minor modification would have worked.
def initialLets(keyStr):
'''Return a dictionary in which each key is the initial letter of a word in t and the value is the number of words that begin with that letter. Upper
and lower case letters should be considered different letters.'''
inLets = {}
strList = keyStr.split()
firstLets = []
for words in strList:
firstLets.append(words[0])
for lets in firstLets:
inLets[lets] = firstLets.count(lets)
return inLets
text = "I'm born to trouble I'm born to fate"
print(initialLets(text))

Python - Neaten up Code - Loops and Recursive features

I have the below code that works fine. However Whilst i'm learning im trying to find ways to continually improve my code.
Would it be possible to make the below code neater in any way?
def isValidWord(word, hand, wordList):
"""
Returns True if word is in the wordList and is entirely
composed of letters in the hand. Otherwise, returns False.
Does not mutate hand or wordList.
word: string
hand: dictionary (string -> int)
wordList: list of lowercase strings
"""
x=''
if word in wordList:
x=True
else:
x=False
#dont mutate the hand so copy
hand2=hand.copy()
for letter in word:
if letter in hand2.keys():
hand2[letter]-=1
if hand2[letter]<0:
x=False
break
else:
x = False
return x
To run the code:
hand = {'r': 1, 'a': 3, 'p': 2, 'e': 1, 't': 1, 'u':1}
word = "rapture"
wordList = ['rapture','alex']
isValidWord(word, hand, wordList)
With all and a Counter:
from collections import Counter
def isValidWord(word, hand, wordList):
wc = Counter(word)
return word in wordList and all(wc[c] <= hand.get(c, -1) for c in wc)
If you have very large lists of words, consider using a set instead for the O(1) lookup time.
To handle counts properly:
from collections import Counter
def isValidWord(word, hand, wordList):
# Check for membership first and early out if fails
# Otherwise, see if the count of any letter in word exceeds that of the hand
return word in wordList and all(hand.get(let, -1) >= cnt for let, cnt in Counter(word).items())

Scrabble wordgame mistake in python

I'm having trouble with a word game I'm trying to make, scrabble.
A part of my code,where it eventually gives the error, is:
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
"""
hand_copy = hand.copy()
if word not in wordList:
return False
for char in word:
if char not in hand_copy:
return False
else:
hand_copy[char] -= 1
if hand_copy[char] < 0:
return False
return True
def play_hand(hand, words):
"""
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)
words: list of lowercase strings
"""
n = HAND_SIZE
display_hand(hand)
print "You may enter '.' if you cannot create a word."
word = raw_input("Please enter a word:")
score = 0
totalscore = 0
while not len(hand) == 0 and not word == ".":
print word
if is_valid_word(word, hand, load_words()) == False:
print "That is not a valid word"
word = raw_input("Please enter a word:")
elif is_valid_word(word, hand, load_words()) == True:
score = get_word_score(word, n)
print "You word was worth " + str(score) + " points."
totalscore = totalscore + score
print "Your total score is: " + str(totalscore) + "."
print "Here is your updated hand."
hand = update_hand(hand,word)
display_hand(hand)
if len(hand) != 0:
word = raw_input("Please enter a word:")
print "Your total score is: " + str(totalscore) + "."
return
def play_game(words):
"""
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, words)
print
elif cmd == 'r':
play_hand(hand, words)
print
elif cmd == 'e':
break
else:
print "Invalid command."
In my opninion this should work. I can start the game, deal a new hand, replay the last hand or end a game. But when I try to make a word from the given letters it gives this error:
Traceback (most recent call last):
File "wordgames.py", line 257, in <module>
play_game(words)
File "wordgames.py", line 241, in play_game
play_hand(hand, words)
File "wordgames.py", line 204, in play_hand
if is_valid_word(word, hand, load_words()) == False:
File "wordgames.py", line 163, in is_valid_word
if word not in wordList:
NameError: global name 'wordlist' is not defined
I've been trying a lot, but nothing helps... Please can somebody help me fixing this?
Thanks in advance!
The error is clearly stated at the bottom of the traceback:
File "wordgames.py", line 163, in is_valid_word
if word not in wordList:
NameError: global name 'wordlist' is not defined
ie you don't define wordList - the input variable is called word_list.
Also you should fix the indentation in your question to make it clear where the function definition ends and you return to global scope.
As jonsharpe points out, your traceback is also inconsistent (wordlist instead of wordList)

Categories