I am trying to create a sort of version of Wordle in python (just for practice).
I am having difficulty communicating to the player which letters in their guess match (or closely match) the letters in the target word.
I can highlight matches (i.e. where the letter is in the right place) using uppercase, but I don't know how to differentiate between letters which have a match somewhere in the target word and letters which do not appear at all. The relevant code is below:
def compare_words(word,guess):
W = list(word)# need to turn the strings into list to try to compare each part
G = list(guess)
print(W) # printing just to track the two words
print(G)
result =[ ] # define an empty list for our results
for i in range(len(word)):
if guess[i] == word[i]:
result.append(guess[i].upper())
elif guess[i] in word:
result.append(guess[i])
else:
result.append(" ")
print (result)
return result
# note, previous functions ensure the length of the "word" and "guess" are the same and are single words without digits
x = compare_words("slide","slips")
['s', 'l', 'i', 'd', 'e']
['s', 'l', 'i', 'p', 's']
['S', 'L', 'I', ' ', 's']
As you can see, the direct matches are upper, the other matches are unchanged and the "misses" are left out. This is not what I want, are usually the whole guess is spat back out with font change or colours to indicate the matches.
I have looked into bolding and colours but it all at the point of printing. I need something built into the list itself, but I am unsure if I can do this. Any ideas?
Cheers
Related
I'm very new to python and I'm practicing different exercises.
I need to write a program to decode a string. The original string has been modified by adding, after each vowel (letters ’a’, ’e’, ’i’, ’o’ and ’u’), the letter ’p’ and then that same vowel again.
For example, the word “kemija” becomes “kepemipijapa” and the word “paprika” becomes “papapripikapa”.
vowel = ['a', 'e', 'i', 'o', 'u']
input_word = list(input())
for i in range(len(input_word)):
if input_word[i] in vowel:
input_word.pop(i + 1)
input_word.pop(i + 2)
print(input_word)
The algorithm I had in mind was to detect the index for which the item is a vowel and then remove the following 2 items after this item ,so if input_word[0] == 'e' then the next 2 items (input_word[1], input_word[2]) must be removed from the list. For the sample input zepelepenapa, I get this error message : IndexError: pop index out of range even when I change the for loop to range(len(input_word) - 2) ,again I get this same error.
thanks in advance
The loop will run a number of times equal to the original length of input_word, due to range(len(input_word)). An IndexError will occur if input_word is shortened inside the loop, because the code inside the loop tries to access every element in the original list input_word with the expression input_word[i] (and, for some values of input_word, the if block could even attempt to pop items off the list beyond its original length, due to the (i + 1) and (i + 2)).
Hardcoding the loop definition with a specific number like 2, e.g. with range(len(input_word) - 2), to make it run fewer times to account for removed letters isn't a general solution, because the number of letters to be removed is initially unknown (it could be 0, 2, 4, ...).
Here are a couple of possible solutions:
Instead of removing items from input_word, create a new list output_word and add letters to it if they meet the criteria. Use a helper list skip_these_indices to keep track of indices that should be "removed" from input_word so they can be skipped when building up the new list output_word:
vowel = ['a', 'e', 'i', 'o', 'u']
input_word = list("zepelepenapa")
output_word = []
skip_these_indices = []
for i in range(len(input_word)):
# if letter 'i' shouldn't be skipped, add it to output_word
if i not in skip_these_indices:
output_word.append(input_word[i])
# check whether to skip the next two letters after 'i'
if input_word[i] in vowel:
skip_these_indices.append(i + 1)
skip_these_indices.append(i + 2)
print(skip_these_indices) # [2, 3, 6, 7, 10, 11]
print(output_word) # ['z', 'e', 'l', 'e', 'n', 'a']
print(''.join(output_word)) # zelena
Alternatively, use two loops. The first loop will keep track of which letters should be removed in a list called remove_these_indices. The second loop will remove them from input_word:
vowel = ['a', 'e', 'i', 'o', 'u']
input_word = list("zepelepenapa")
remove_these_indices = []
# loop 1 -- find letters to remove
for i in range(len(input_word)):
# if letter 'i' isn't already marked for removal,
# check whether we should remove the next two letters
if i not in remove_these_indices:
if input_word[i] in vowel:
remove_these_indices.append(i + 1)
remove_these_indices.append(i + 2)
# loop 2 -- remove the letters (pop in reverse to avoid IndexError)
for i in reversed(remove_these_indices):
# if input_word has a vowel in the last two positions,
# without a "p" and the same vowel after it,
# which it shouldn't based on the algorithm you
# described for generating the coded word,
# this 'if' statement will avoid popping
# elements that don't exist
if i < len(input_word):
input_word.pop(i)
print(remove_these_indices) # [2, 3, 6, 7, 10, 11]
print(input_word) # ['z', 'e', 'l', 'e', 'n', 'a']
print(''.join(input_word)) # zelena
pop() removes an item at the given position in the list and returns it. This alters the list in place.
For example if I have:
my_list = [1,2,3,4]
n = my_list.pop()
will return n = 4 in this instance. If I was to print my_list after this operation it would return [1,2,3]. So the length of the list will change every time pop() is used. That is why you are getting IndexError: pop index out of range.
So to solve this we should avoid using pop() since it's really not needed in this situation. The following will work:
word = 'kemija'
vowels = ['a', 'e', 'i', 'o', 'u']
new_word = []
for w in word:
if w in vowels:
new_word.extend([w,'p',w])
# alternatively you could use .append() over .extend() but would need more lines:
# new_word.append(w)
# new_word.append('p')
# new_word.append(w)
else:
new_word.append(w)
decoded_word = ''.join(new_word)
print(decoded_word)
Pig Latin translator is basically an introductory code for many projects, and it involves getting words that start with consonants to move the first letter to the end of the word and add 'ay', and adding only 'ay' to a word that starts with a vowel. I have basically finished the whole project except for some places, and one main thing is getting the program to complete a whole sentence with a for loop, especially in moving to the next list item
I have tried to do simple things such as n+1 at the end of the for loop, and researched online (majority on Stackoverflow)
latin = ""
n = 0
sentence = input ("Insert your sentence ")
word = sentence.split()
first_word = word [n]
first_letter = first_word [0]
rest = first_word [1:]
consonant = ['b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'z')
vowel = ['a', 'e', 'i', 'o', 'u')
for one in consonant :
latin = latin + " " + rest + one + "ay"
print (latin)
n + 1
for one in vowel :
latin = latin + " " + first_word +ay
print (latin)
n + 1
There was no error message, however, the computer ran the variable 'one' not as the first (zeroth) letter of the variable first_word, rather, just running it from a - z. Is there anyway to fix this? Thank you
#!/usr/bin/env python
sentence = input("Insert your sentence ")
# don't forget "y" :)
consonants = ['b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z']
# this empty list will hold our output
output_words = list()
# we'll take the user's input, strip any trailing / leading white space (eg.: new lines), and split it into words, using spaces as separators.
for word in sentence.strip().split():
first_letter = word[0]
# check if the first letter (converted to lower case) is in the consonants list
if first_letter.lower() in consonants:
# if yes, take the rest of the word, add the lower case first_letter, then add "ay"
pig_word = word[1:] + first_letter.lower() + "ay"
# if it's not a consonant, do nothing :(
else:
pig_word = word
# add our word to the output list
output_words.append(pig_word)
# print the list of output words, joining them together with a space
print(" ".join(output_words))
The loop loops over each word in the sentence - no need to count anything with n. There's also no need to loop over the consonants or the vowels, all we care about is "does this word start with a consonant" - if yes, modify it as per your rules.
We store our (possibly) modified words in an output list, and when we're done, we print all of them, joined by a space.
Note that this code is very buggy - what happens if your user's input contains punctuation?
I opehay hattay elpshay, eyuleochoujay
The function hangman_guessed(guessed, secret) is supposed to take a string of guessed characters and a list of "secret" characters.
The function checks every character in the secret list and compares it with each character in the guessed character string to check if the character is in both. If the characters are not the same then the function places a - in a temporary list equal to the secret list (so that we can still compare other characters in the guessed list to the original secret list later).
def hangman_guessed(guessed, secret):
modified = secret
for i1 in range(len(secret)):
for i2 in range(len(guessed)):
if secret[i1] == guessed[i2]:
modified[i1] = secret[i1]
break
else:
modified[i1] = '-'
return modified
For example, when I run hangman_guessed('hl', ['h','e','l','l','o']), it should return ['h', '-', 'l', 'l', '-'], but currently it returns ['h', '-', '-', '-', '-'].
The problem here is that only the first character in the guessed list is considered, but I do not know why. It this case, it is expected that the program checks over the 'l' characters in ['h','e','l','l','o']) and sets the corresponding characters in the temporary list modified to -, but to my understanding after the for loop runs again and checks the original secret list for l characters it should overwrite the - in the modified list and the result should have the 'l' characters rather than the - characters.
A list-comprehension is perfectly suited to what you want to do. We want to create a list of each character (let this be i) in secret if i is in guessed else we want to have a hyphen ("-").
def hangman_guessed(guessed, secret):
return [i if i in guessed else "-" for i in secret]
and a test to show it works:
>>> hangman_guessed('hl', ['h','e','l','l','o'])
['h', '-', 'l', 'l', '-']
As you get more used to the flow of Python, you will find that comprehensions in general are extremely useful as well as being very readable for a whole variety of things.
If for some reason however, you had to use nested for-loops and weren't allowed to use the really simple in operator, then you need to / can make a couple of corrections to your current code:
make a copy of the secret list first
iterate through the characters in guessed, rather than the indexes
After making these two corrections, the function will look something like:
def hangman_guessed(guessed, secret):
modified = secret[:]
for i in range(len(secret)):
for g in guessed:
if secret[i] == g:
modified[i] = secret[i]
break
else:
modified[i] = '-'
return modified
which now works:
>>> hangman_guessed('hl', ['h','e','l','l','o'])
['h', '-', 'l', 'l', '-']
I wrote a function with two parameters. One is an empty string and the other is a string word. My assignment is to use to recursion to reverse the word and place it in the empty string. Just as I think ive got it, i received an "out of memory error". I wrote the code so that so it take the word, turn it into a list, flips it backwards, then places the first letter in the empty string, then deletes the letter out of the list so recursion can happen to each letter. Then it compares the length of the the original word to the length of the empty string (i made a list so they can be compared) so that when their equivalent the recursion will end, but idk
def reverseString(prefix, aStr):
num = 1
if num > 0:
#prefix = ""
aStrlist = list(aStr)
revaStrlist = list(reversed(aStrlist))
revaStrlist2 = list(reversed(aStrlist))
prefixlist = list(prefix)
prefixlist.append(revaStrlist[0])
del revaStrlist[0]
if len(revaStrlist2)!= len(prefixlist):
aStr = str(revaStrlist)
return reverseString(prefix,aStr)
When writing something recursive I try and think about 2 things
The condition to stop the recursion
What I want one iteration to do and how I can pass that progress to the next iteration.
Also I'd recommend getting the one iteration working then worry about calling itself again. Otherwise it can be harder to debug
Anyway so applying this to your logic
When the length of the output string matches the length of the input string
add one letter to the new list in reverse. to maintain progress pass list accumulated so far to itself
I wanted to just modify your code slightly as I thought that would help you learn the most...but was having a hard time with that so I tried to write what i would do with your logic.
Hopefully you can still learn something from this example.
def reverse_string(input_string, output_list=[]):
# condition to keep going, lengths don't match we still have work to do otherwise output result
if len(output_list) < len(list(input_string)):
# lets see how much we have done so far.
# use the length of current new list as a way to get current character we are on
# as we are reversing it we need to take the length of the string minus the current character we are on
# because lists are zero indexed and strings aren't we need to minus 1 from the string length
character_index = len(input_string)-1 - len(output_list)
# then add it to our output list
output_list.append(input_string[character_index])
# output_list is our progress so far pass it to the next iteration
return reverse_string(input_string, output_list)
else:
# combine the output list back into string when we are all done
return ''.join(output_list)
if __name__ == '__main__':
print(reverse_string('hello'))
This is what the recursion will look like for this code
1.
character_index = 5-1 - 0
character_index is set to 4
output_list so far = ['o']
reverse_string('hello', ['o'])
2.
character_index = 5-1 - 1
character_index is set to 3
output_list so far = ['o', 'l']
reverse_string('hello', ['o', 'l'])
3.
character_index = 5-1 - 2
character_index is set to 2
output_list so far = ['o', 'l', 'l']
reverse_string('hello', ['o', 'l', 'l'])
4.
character_index = 5-1 - 3
character_index is set to 1
output_list so far = ['o', 'l', 'l', 'e']
reverse_string('hello', ['o', 'l', 'l', 'e'])
5.
character_index = 5-1 - 4
character_index is set to 0
output_list so far = ['o', 'l', 'l', 'e', 'h']
reverse_string('hello', ['o', 'l', 'l', 'e', 'h'])
6. lengths match just print what we have!
olleh
I am trying to create a word game that involves finding words in a matrix of 5x5 characters, like so:
[['a', 'a', 'u', 'r', 'a'],
['m', 'v', 'g', 'n', 'x'],
['a', 'q', 'h', 'y', 'o'],
['p', 'r', 'h', 'l', 'h'],
['v', 'h', 'y', 'o', 'j']]
which I have represented as a list of lists. "xylon" should be found but not "nylon" since this reuses the 'n'. I found a similar problem here but I do not know C.
My current solution involves creating a dictionary for each letter in the word, consisting of a list of tuples of its location on the board like so: {'letter':[(row,column),(row,column)]}. Then, for each letter in the word, I check if each of its locations on the board are compatible with the locations of the previous letter. If it is, I add that location to a new path dictionary.
This fails though for repeating letters and other cases like "nylon" which does return True. It is already rather bulky and confusing and I should probably just start over. Is there a more succinct solution I can employ?
Edits:
To clarify: A word is "in" the grid if there exists a path connecting each letter in the word in the grid. Up, down, left, right, and diagonals are allowed. 'x' is adjacent to 'y', which is adjacent to 'l', and so on. The path needs to have no particular shape as long as each letter is adjacent and no specific letter on the board is used twice. A word can have repeating letters, so "pama" would be allowed because there are multiple 'a's that can be used.
#MSW is correct, the game is boggle, though I am finding this out for the first time!
If you want to check for word membership, your starting point of a dictionary mapping letters to positions is a good one:
letter_positions = {}
for (y, row) in enumerate(board):
for (x, letter) in enumerate(row):
letter_positions.setdefault(letter, []).append((x, y))
From there, your function should keep track of which letters have already been used to make sure it doesn't duplicate them:
def move_valid(position, last_position):
if last_position is None:
return True
return (
abs(position[0] - last_position[0]) <= 1 and
abs(position[1] - last_position[1]) <= 1
)
def find_word(word, used=None):
if word == "":
return []
if used is None:
used = []
letter, rest = word[:1], word[1:]
for position in letter_positions.get(letter) or []:
if position in used:
continue
if not move_valid(position, used and used[-1]):
continue
path = find_word(rest, used + [position])
if path is not None:
return [position] + path
return None
And for example:
>>> find_word("xylon")
[(4, 1), (3, 2), (3, 3), (4, 2), (3, 1)]
>>> find_word("bad")
None
Now, note that the runtime here will be O(not great) because of the position in used (used is a list and will require an O(N) search for each letter position) and the used + [position] and [position] + path (each of which will result in an allocation + copy). In practice this will be ~O(word length ^ 2), but could be improved to ~O(word length) with some more sensible data structures.