String index out of range and I can't fix the error - python-3.x

So below is my code. Somewhere in the "for digit in observed" loop there is an indexing error that I can't seem to find or fix. I believe it's something to do with the PINS list, but I'm not sure, as none of my edits have made a difference. Test case that fails is observed = '11'. Single cases all pass. Unfortunately as I'm using codewars, there is no line given for the error, just the following:
Traceback:
in
in get_pins
IndexError: string index out of range
def get_pins(observed):
# Let's see what we're working with
print("observed")
print(observed)
print(" ")
# Dictionary of possible numbers for a half-assed observation of a key press.
possible = {'0':'08','1':'124','2':'1235','3':'236',
'4':'1457','5':'24568','6':'3569',
'7':'478','8':'05789','9':'689'}
# Single digit pwd case
PINS=[]
if len(observed) == 1:
for digit in possible[observed]:
PINS.append(digit)
return PINS
# Find number of possible PINs
num_possibles = 1
# Step through observed digits
for digit in observed:
num_possibles*=len(possible[digit])
# Populate PINS to allow string manipulation
PINS = " "*num_possibles
print(PINS[num_possibles])
num_change = num_possibles
change = []
count = 0
# Step through observed, determine change of digit,
for digit in observed:
# Last digit in observed means it iterates every time
if digit != observed[len(observed)-1]:
# Develop array for checking position
num_change = num_change/len(possible[digit])
for i in range(1,len(possible[digit])):
change.append(i*num_change)
print(change)
# Populate PINS with possible digit, full pin is created after final iteration of digit/observed loop
for pin in range(0,num_possibles-1):
PINS[pin] = PINS[pin] + possible[digit][count]
if (pin+1) in change:
count+=1
change=[]
count =0
else:
for pin in range(0,num_possibles-1):
PINS[pin] = PINS[pin] + possible[digit][count]
count+=1
if count == len(possible[digit]):
count = 0
return PINS

The problem is here:
PINS = " "*num_possibles
print(PINS[num_possibles])
The first line creates a string of spaces of length num_possibles. This means that the valid indices are 0, 1, ..., num_possibles - 1. However, in the very next line you try to index the string at the non-existent index num_possibles.
I would simply drop that print. What is the point of it? You know that PINS is a string of all spaces, so why bother?
Strings are immutable, since PINS is a string, the line
PINS[pin] = PINS[pin] + possible[digit][count]
would trigger the error:
TypeError: 'str' object does not support item assignment
What you should do is to initialize PINS as
PINS = [' ']*num_possibles
Or, probably even better
PINS = ['']*num_possibles
In which case
PINS[pin] = PINS[pin] + possible[digit][count]
would be legal (although it could be shortened by use of +=) -- although I'm not sure if this is what you really want to do since you are concatenating the strings stored as values in the possible and not adding the numbers represented by those strings.
At the end of the function, replace return PINS by return''.join(PINS)`
That will fix some of your bugs, but since I know neither intended input nor intended output I can't say any more.

Related

Keeping the same distance no matter the string length [duplicate]

I'm sure this is covered in plenty of places, but I don't know the exact name of the action I'm trying to do so I can't really look it up. I've been reading an official Python book for 30 minutes trying to find out how to do this.
Problem: I need to put a string in a certain length "field".
For example, if the name field was 15 characters long, and my name was John, I would get "John" followed by 11 spaces to create the 15 character field.
I need this to work for any string put in for the variable "name".
I know it will likely be some form of formatting, but I can't find the exact way to do this. Help would be appreciated.
This is super simple with format:
>>> a = "John"
>>> "{:<15}".format(a)
'John '
You can use the ljust method on strings.
>>> name = 'John'
>>> name.ljust(15)
'John '
Note that if the name is longer than 15 characters, ljust won't truncate it. If you want to end up with exactly 15 characters, you can slice the resulting string:
>>> name.ljust(15)[:15]
If you have python version 3.6 or higher you can use f strings
>>> string = "John"
>>> f"{string:<15}"
'John '
Or if you'd like it to the left
>>> f"{string:>15}"
' John'
Centered
>>> f"{string:^15}"
' John '
For more variations, feel free to check out the docs: https://docs.python.org/3/library/string.html#format-string-syntax
You can use rjust and ljust functions to add specific characters before or after a string to reach a specific length.
The first parameter those methods is the total character number after transforming the string.
Right justified (add to the left)
numStr = '69'
numStr = numStr.rjust(5, '*')
The result is ***69
Left justified (add to the right)
And for the left:
numStr = '69'
numStr = numStr.ljust(3, '#')
The result will be 69#
Fill with Leading Zeros
Also to add zeros you can simply use:
numstr.zfill(8)
Which gives you 00000069 as the result.
string = ""
name = raw_input() #The value at the field
length = input() #the length of the field
string += name
string += " "*(length-len(name)) # Add extra spaces
This will add the number of spaces needed, provided the field has length >= the length of the name provided
name = "John" // your variable
result = (name+" ")[:15] # this adds 15 spaces to the "name"
# but cuts it at 15 characters
I know this is a bit of an old question, but I've ended up making my own little class for it.
Might be useful to someone so I'll stick it up. I used a class variable, which is inherently persistent, to ensure sufficient whitespace was added to clear any old lines. See below:
2021-03-02 update: Improved a bit - when working through a large codebase, you know whether the line you are writing is one you care about or not, but you don't know what was previously written to the console and whether you want to retain it.
This update takes care of that, a class variable you update when writing to the console keeps track of whether the line you are currently writing is one you want to keep, or allow overwriting later on.
class consolePrinter():
'''
Class to write to the console
Objective is to make it easy to write to console, with user able to
overwrite previous line (or not)
'''
# -------------------------------------------------------------------------
#Class variables
stringLen = 0
overwriteLine = False
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
def writeline(stringIn, overwriteThisLine=False):
import sys
#Get length of stringIn and update stringLen if needed
if len(stringIn) > consolePrinter.stringLen:
consolePrinter.stringLen = len(stringIn)+1
ctrlString = "{:<"+str(consolePrinter.stringLen)+"}"
prevOverwriteLine = consolePrinter.overwriteLine
if prevOverwriteLine:
#Previous line entry can be overwritten, so do so
sys.stdout.write("\r" + ctrlString.format(stringIn))
else:
#Previous line entry cannot be overwritten, take a new line
sys.stdout.write("\n" + stringIn)
sys.stdout.flush()
#Update the class variable for prevOverwriteLine
consolePrinter.overwriteLine = overwriteThisLine
return
Which then is called via:
consolePrinter.writeline("text here", True)
If you want this line to be overwriteable
consolePrinter.writeline("text here",False)
if you don't.
Note, for it to work right, all messages pushed to the console would need to be through consolePrinter.writeline.
I generally recommend the f-string/format version, but sometimes you have a tuple, need, or want to use printf-style instead. I did this time and decided to use this:
>>> res = (1280, 720)
>>> '%04sx%04s' % res
'1280x 720'
Thought it was a touch more readable than the format version:
>>> f'{res[0]:>4}x{res[1]:>4}'
First check to see if the string's length needs to be shortened, then add spaces until it is as long as the field length.
fieldLength = 15
string1 = string1[0:15] # If it needs to be shortened, shorten it
while len(string1) < fieldLength:
rand += " "
Just whipped this up for my problem, it just adds a space until the length of string is more than the min_length you give it.
def format_string(str, min_length):
while len(str) < min_length:
str += " "
return str

How do I achieve this following function only using while loop?

I'm currently working on this problem that ask me to generate an arrow pattern using loops function that looks something like this:
"How many columns? 3"
*
*
*
*
*
I know I can do this with for loop(probably more efficient too), but that is not what I aimed for. I wanted to achieve this only using while loop.
I have some ideas:
1. I set up a control variable and an accumulator to control the loop
2. I then write 2 separate loops to generate the upper and lower part of the pattern. I was thinking about inserting the space before the asterisks using method like this:
(accumulator - (accumulator - integer)) * spaces.
#Ask the user how many column and direction of column
#they want to generate
Keep_going = True
Go = 0
while keep_going:
Column_num = int(input("How many columns? "))
if Column_num <= 0:
print("Invalid entry, try again!")
else:
print()
Go = 1
#Upper part
while Keep_going == True and Go == 1:
print("*")
print(""*(Column_num - (Column_num - 1) + "*")
...but I soon realized it wouldn't work because I don't know the user input and thus cannot manually calculate how many spaces to insert before asterisks. Now everything on the internet tells me to use for loop and range function, I could do that, but I think that is not helpful for me to learn python since I couldn't utilize loops very well yet and brute force it with some other method just not going to improve my skills.
I assume this is achievable only using while loop.
#Take your input in MyNumber
MyNumber = 5
i = 1
MyText = '\t*'
while i <=MyNumber:
print(MyText.expandtabs(i-1))
i = i+1
i = i-1
while i >=1:
print(MyText.expandtabs(i-1))
i = i-1
Python - While Loop
Well first you have to understand that a while loop loops until a requirement is met.
And looking at your situation, to determine the number of spaces before the * you should have an ongoing counter, a variable that counts how many spaces are needed before you continue. For example:
###Getting the number of columns###
while True:
number=int(input('Enter number of rows: '))
if number<=0:
print('Invalid')
else:
###Ending the loop###
break
#This will determine the number of spaces before a '*'
counter=0
#Loops until counter equals number
while counter!=number:
print(" "*counter + "*")
#Each time it loops the counter variable increases by 1
counter=counter+1
counter=counter-1
#Getting the second half of the arrow done
while counter!=0:
counter=counter-1
print(" "*counter + "*")
Please reply if this did not help you so that i can give a more detailed response

How can I make my function work for any number?

I am having some issues with some code I wrote for this problem:
“Write a function namedd calc that will evaluate a simple arithmetic expression. The input to your program will be a string of the form:
operand1 operator operand2
where operand1 and operand2 are non-negative integers and operator is a single-character operator, which is either +, -, or *. You may assume that there is a space between each operand and the operator. You may further assume that the input is a valid mathemat- ical expression, i.e. your program is not responsible for the case where the user enters gibberish.
Your function will return an integer, such that the returned value is equal to the value produced by applying the given operation to the given operands.
Sample execution:
calc("5 + 10") # 15
“You may not use the split or eval functions in your solution.
Hint: the hard part here is breaking the input string into its three component. You may use the find and rfind functions to find the position of the first and last space, and then use the slice operator (that is, s[startindex:endindex]) to extract the relevant range of characters. Be careful of off-by-one errors in using the slice operator.
Hint: it’s best to test your code as you work. The first step should be to break the input string into its three components. Write a program that does that, have it print out the operator and the two operands on separate lines, and test it until you are convinced that it works. Then, modifying it to perform the desired mathematical operation should be straightforward. Test your program with several different inputs to make sure it works as you expect.”
Here is my code:
def calc(exp):
operand1 = int(exp[:1])
operand2 = int(exp[4:6])
operator = exp[2:3]
if(operator == "+"):
addition = operand1+operand2
return addition
if(operator == "-"):
subtraction = operand1-operand2
return subtraction
if(operator == "*"):
multiplication = operand1*operand2
return multiplication
print(calc("5 + 10"))
print(calc("4 - 8"))
print(calc("4 * 3"))
My code does not fully meet the criteria of this question. It only works for single digit numbers. How can I make my code work for any number?
Like:
“504 + 507”
”5678 + 76890”
and so on?
Thank you. Any help is appreciated.
As the hint says, get the position of the first and last space of the expression, use it to extract the operand and the operators, and then evaluate accordingly.
def calc(exp):
#Get the position for first space with find
low_idx = exp.find(' ')
#Get the position for last space with rfind
high_idx = exp.rfind(' ')
#Extract operators and operand with slice, converting operands to int
operand1 = int(exp[0:low_idx])
operator = exp[low_idx+1:high_idx]
operand2 = int(exp[high_idx:])
result = 0
#Evaluate based on operator
if operator == '+':
result = operand1 + operand2
elif operator == '-':
result = operand1 - operand2
elif operator == '*':
result = operand1 * operand2
return result
print(calc("5 + 10"))
print(calc("4 - 8"))
print(calc("4 * 3"))
print(calc("504 + 507"))
print(calc("5678 + 76890"))
#15
#-4
#12
#1011
#82568
The answer is in the specification:
You may use the find and rfind functions to find the position of the first and last space, and then use the slice operator (that is, s[startindex:endindex]) to extract the relevant range of characters.
find and rfind are methods of string objects.
You could split it into three components using this code: (note: this doesn't use split or eval)
def splitExpression(e):
numbers = ["1","2","3","4","5","6","7","8","9","0"] # list of all numbers
operations = ["+","-","*","/"] # list of all operations
output = [] # output components
currentlyParsing = "number" # the component we're currently parsing
buildstring = "" # temporary variable
for c in e:
if c == " ":
continue # ignore whitespace
if currentlyParsing == "number": # we are currently parsing a number
if c in numbers:
buildstring += c # this is a number, continue
elif c in operations:
output.append(buildstring) # this component has reached it's end
buildstring = c
currentlyParsing = "operation" # we are expecting an operation now
else:
pass # unknown symbol!
elif currentlyParsing == "operation": # we are currently parsing an operation
if c in operations:
buildstring += c # this is an operation, continue
elif c in numbers:
output.append(buildstring) # this component has reached it's end
buildstring = c
currentlyParsing = "number" # we are expecting a number now
else:
pass # unknown symbol!
if buildstring: # anything left in the buffer?
output.append(buildstring)
buildstring = ""
return output
Usage: splitExpression("281*14") returns ["281","*","14"]
This function also accepts spaces between numbers and operations
You can simply take the string and use the split method for the string object, which will return a list of strings based on some separator.
For example:
stringList = "504 + 507".split(" ")
stringList will now be a list such as ["504", "+", "507"] due to the separator " " which is a whitespace. Then just use stringList[1] with your conditionals to solve the problem. Additionally, you can use int(stringList[0]) and int(stringList[2]) to convert the strings to int objects.
EDIT:
Now I realized that your problem said to use find() instead of split(). Simply use the logic above but instead find(" ") the first whitespace. You will then need to find the second whitespace by slicing past the first whitespace using the two additional arguments available for find().
You need to split the string out instead of hard coding the positions of the indexes.
When coding you want to try to make your code as dynamic as possible, that generally means not hard coding stuff that could be a variable or in this case could be grabbed from the spaces.
Also in the if statements I modified them to elif as it is all one contained statement and thus should be grouped.
def calc(exp):
vals = exp.split(' ')
operand1 = int(vals[0])
operand2 = int(vals[2])
operator = vals[1]
if operator == '+':
return operand1+operand2
elif operator == '-':
return operand1-operand2
else:
return operand1*operand2

Implement a while loop in my hangman type/puzzle game

I'm new to python programming. I would like to display a win message after every correct letter input and no message if an incorrect letter is input.
I've written my code such that it will only accept one letter at a time and reduce an attempt by 1, regardless of if it is wrong or right.
How would I be able to implement a while loop into this so that I don't keep getting this error:
builtins.TypeError: 'str' object does not support item assignment
word="banana"
word_list=list(word)
length=len(word_list)
word_list= set(word_list)
word_list=list(word_list)
answer=["_"]*length
answer=list(answer)
guess=[]
count = 4
win=False # boolean so we do not use an identifier in our if statements
user_guess=window.input_string("Guess a letter: ", x, y)
y = y + font_height
guess.append(user_guess)
while count > 0:
# Removes guesses if they are not in the word so that the blanks do not fill in with incorrect letters
for letter in guess:
if letter not in word_list:
guess.remove(letter)
else:
win=True
# Replaces blanks in empty list with the letter guess
for place,letter in enumerate(list(word)):
for i in range(len(guess)):
if letter == guess[i]:
answer[place]=guess[i]
answer=" ".join(answer)
update_message = 'The answer so far is: '
window.draw_string(update_message + answer,x,y)
y = y + font_height
#End Game
win_message = 'Good job! You got the word.'
lose_message = 'Not quite, the correct word was: '+word +' Better luck next time'
if win:
window.draw_string(win_message,x,y)
y = y + font_height
count -=1
else:
window.draw_string(lose_message,x,y)
y = y + font_height
count -=1
Please notice this assignment: answer=" ".join(answer). Before the assignment, answer is a list of string. After the assignment, answer becomes a string.
So, in the next iteration of the while loop, answer[place]=guess[i] turns invalid, because python does not allow modifying a string by assigning a "character" to some place of the string.
It really takes some time to find the fault. You'd better provide the information, like, "which line in the program targeted the error message", when asking questions in future.

How to get longest alphabetically ordered substring in python

I am trying to write a function that returns the longest substring of s in which the letters occur in alphabetical order. For example, if s = 'azcbobobegghakl', the function should return 'beggh'
Here is my function, which is still not complete but it does not return the list of sub;
the return error is:
"IndexError: string index out of range"
def longest_substring(s):
sub=[]
for i in range (len(s)-1):
subs=s[i]
counter=i+1
while ord(s[i])<ord(s[counter]):
subs+=s[counter]
counter+=1
sub.append(subs)
return sub
It is not optimal (works in linear time O(n)) but i made some modification to your code (in Python 3):
def longest_substring(s):
length = len(s)
if length == 0 : # Empty string
return s
final = s[0]
for i in range (length-1):
current = s[i]
counter = i+1
while counter < length and ord(s[i]) <= ord(s[counter]):
current += s[counter]
counter +=1
i+=1
if len(final) < len(current):
final = current
return final
s = 'azcbobobegghakl'
print(longest_substring(s))
Output:
beggh
Modifications:
You are comparing character with fixed position i.e. in while loop you are incrementing only counter not i so I incremented
the ith position also.(So we avoid checking the characters which are already checked, So it does this in linear time O(n) I think..)
Also you are only checking less than for condition while ord(s[i])<ord(s[counter]): But you also have to check for equals too.
You created one list where you append every sequence which is unnecessary unless you want do any other calculations on the
sequence, So I take string and if previous sequence's length is small
then I updated it with new sequence.
Note : If two sequence's length is same then 1st occurring sequence is shown as output.
Another Input:
s = 'acdb'
Output:
acd
I hope this will help you.

Resources