Consider the following code:
def anadist(string1, string2):
string1_list = []
string2_list = []
for i in range(len(string1)):
string1_list.append(string1[i])
for i in range(len(string2)):
string2_list.append(string2[i])
# Test returns for checking
# return (string1_list,string2_list)
# return len(string1_list)
# return len(string2_list)
for i in range(0,len(string1_list)):
try:
if (string1_list[i]) in string2_list:
com = string1_list.pop(i)
com_index = string2_list.index(com)
string2_list.pop(com_index)
else:
pass
except ValueError:
pass
return string1_list
def main():
str1 = input("Enter string #1 >>> ")
str2 = input("Enter string #2 >>> ")
result = anadist(str1, str2)
print(result)
#Boilerplate Check
if __name__ == "__main__":
main()
Running in Python 3.5.2 raises an IndexError:
Traceback (most recent call last):
File "E:\CSE107L\Practice\anadist.py", line 34, in <module>
main()
File "E:\CSE107L\Practice\anadist.py", line 29, in main
result = anadist(str1, str2)
File "E:\CSE107L\Practice\anadist.py", line 15, in anadist
if (string1_list[i]) in string2_list:
IndexError: list index out of range
And I can't find what is going wrong. I wrote another code similar and that works:
def main():
lst = [1,2,3,4,5]
lst2 = [5,6,7,8,9]
for i in range(len(lst)):
if lst[i] in lst2:
com = lst.pop(i)
lst2_index = lst2.index(com)
lst2.pop(lst2_index)
else:
pass
print(lst)
if __name__ == "__main__":
main()
I feel the error is coming from the way I am forming string1_list. This code is for how many steps it takes to form an anagram of a pair of words.
In some cases, you are shortening string_list1 while you're iterating over it:
if (string1_list[i]) in string2_list:
com = string1_list.pop(i) # string1_list gets shorter here
However, your range doesn't change. It's still going to go from 0 until it counts up to the original length of string1_list (exclusive). This will cause the IndexError any time string1_list.pop(i) is called.
One possible solution would be to use a while loop instead:
i = 0
while i < len(string1_list):
try:
if string1_list[i] in string2_list:
com = string1_list.pop(i)
com_index = string2_list.index(com)
string2_list.pop(com_index)
else:
pass
except ValueError:
pass
i += 1
This will cause the loop termination condition to be checked after each iteration. If you remove some elements from string1_list, it'll still be OK because the loop will terminate before i gets big enough to overrun the bounds of it's container.
Your issue is that you are mutating the string1_list within the for loop by performing string1_list.pop(i). The length of the list is being reduced within the for loop by doing string1_list.pop(i), but you are still iterating over the length of the original list.
Your second lot of code works only because you only pop on the last iteration of the loop.
Your second attempt works simply because a "match" is found only in the last element 5 and as such when you mutate your list lst it is during the last iteration and has no effect.
The problem with your first attempt is that you might remove from the beginning of the list. The counter doesn't get updated and at some point might reach a value that no longer exists in the list (it has been popped). Try running it with no matches in the input and see that it works.
In general, don't mutate your list while iterating through it. Instead of the:
for i in range(len(lst)):
# mutate list
'idiom', you should opt for the:
for i in list(list_object): # or for i in list_object[:]:
which makes a copy first and allows you to mutate the original list_object by keeping mutating and looping separate.
Related
I am following a DP&recursion course with JS as the teaching language,
but I encountered some differences with Python..
When returning the call stack, everything goes as it should,
meaning the values in the stack are returning as a whole array:
(the following problem is about finding a combination of elements in the array that sums to totalsum)
def best_sum(totalsum, arr):
if totalsum == 0:
return []
if totalsum < 0:
return None
for num in arr:
remainder = totalsum - num
results = best_sum(remainder, arr)
if results is not None:
return [*results, num]
return None
print(best_sum(7, [2,3,4]))
The output here is:
[3, 2, 2]
But, when I try to save the call stack to an array, I only get 1 item per line:
if results is not None:
comb = [*results, num]
print(comb)
Output:
[3]
[2]
[2]
My answer is.. is there a way to wait for the call stack to finish, before printing the results?
Because I would like to use that array "comb" to do further coding, but I can't since it fills completely only when returned..
I think I misinterpreted something about how a call stack works in Python :)
But, when I try to save the call stack to an array, I only get 1 item per line:
You changed more than only that. You also removed the return statement. So now the for loop is not interrupted, and the function will now return None... a different return value than intended.
That you only get to print lists with one item, is because the base case of your code is still executed correctly, as it returns []. And [*results, num] is therefor equal to [num]. But that is as far as it goes as now your function can only return [] or None, nothing else. This is why you only see lists with one element in your output.
Once you reinstate that return (like return comb), it will work better. The logic of if results is not None depends on those return statements. Printing is not a replacement for a return value when the caller is going to check the return value like it does with that if.
So basically, I'm creating a function that takes a list and 2 values. The function replaces all occurrences of the first value with the second, The function has to then return the number of replacements that were completed.
I've managed to make the code work, but when it prints the number of replacements that were completed, it says 1, when there were in fact 2 replacements completed. It's as if it only checks for the first index and then stops. I don't where I went wrong.
def functionTwo(numberList, checkNum, checkNumTwo):
try:
while True:
num = numberList.index(checkNum)
numberList[num] = checkNumTwo
amount = numberList.count(checkNumTwo)
return amount
except ValueError:
pass
numberList = [4,8,22,43,42,12,1,10,1,10,32,28,8,42,13]
result = functionTwo(numberList, 1, 3)
print(result)
Using index repeatedly is an overly complex way to do it, it searches the entire list over and over, which is O(n2). Just iterate over the list once and replace the elements as you go, incrementing a counter.
def functionTwo(numberList, checkNum, checkNumTwo):
counter = 0
for i, val in enumerate(numberList):
if val == checkNum:
numberList[i] = checkNumTwo
counter += 1
return counter
However, to fix your code, just move return amount into the except ValueError: block. Your code was returning after the first replacement, so of course it returned only 1.
def functionTwo(numberList, checkNum, checkNumTwo):
try:
while True:
num = numberList.index(checkNum)
numberList[num] = checkNumTwo
except ValueError:
return numberList.count(checkNumTwo)
Note that using count() assumes that the list didn't contain any occurrences of checkNumTwo initially, because they'll be counted.
I got a little question: I got 2 lists with equal lengths.
As you can see below in my code, if appropriate circumstances take place, I delete some of the elements from the list. I have to do my work very carefully so my question is - does the for loop check by every iteration if len(whole_st_gen) is changing ?? Won't it skip some items??
whole_st_gen = []
whole_end_gen = [] // the length of both of them is 38273
if_merge = 0
here_merge = 0
deleting = False
for x in range (0, len(whole_st_gen)):
if_merge = x
if x == len(whole_st_gen)-1:
break
for y in range (x+1, len(whole_st_gen)):
if whole_end_gen[x]>whole_end_gen[y]:
deleting = True
here_merge = y
continue
else:
break
if deleting == True:
deleting = False
del whole_st_gen[x:here_merge]
del whole_end_gen[x:here_merge]
if_merge = 0
here_merge = 0
print len(whole_st_gen) # here's length is 1852, so i think it could work properly, just want to be sure!
sys.exit()
No , when you are using range() method, it does not check the length of the array in each iteration. Especially in Python 2.x , range() returns a list , which is what you iterate over, and this list is created at the start of the loop , it does not get recalculated in each iteration. So there can be multiple issues you can run into when using above method.
One being that you can skip some elements, since if you delete an element from the list, the indices of the list are rearranged to make a contigous sequence, so you would end up missing some elements.
Secondly, you can end up getitng IndexError , Simple example of that -
>>> l = [1,2,3,4]
>>> for i in range(0,len(l)):
... del l[i]
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
IndexError: list assignment index out of range
This is because like I said in starting range() computes the complete range (even xrange() in Python 2.x) at the start itself. This does not happen for you because of -
if x == len(whole_st_gen)-1:
break
Like said in the comments an easier way for you to go would be to use while loop , which does not increment when deleting the item. Example -
if_merge = 0
here_merge = 0
deleting = False
x = 0
while x < len(whole_st_gen):
for y in range (x+1, len(whole_st_gen)):
if whole_end_gen[x]>whole_end_gen[y]:
deleting = True
here_merge = y
else:
break
if deleting == True:
deleting = False
del whole_st_gen[x:here_merge]
del whole_end_gen[x:here_merge]
else:
x += 1
here_merge = 0
print len(whole_st_gen)
sys.exit()
In the for statement the expression list (after the in) is evaluated first to get an iterator (or iterable) which is then used for the iteration, the expression list is not evaluated again in each iteration.
In your case you call the range function to retrieve an iterator (in python3) or iterable (in python2 in the form of a list). In both cases the iterator/iterable will yield len(whole_st_gen) (as it evaluates when you reach the for statement from above) numbers (ranging from zero). If you alter the whole_st_len in the loop it will not affect the iterator and it will yield the same numbers anyway (leading to you skipping numbers, and eventually get numbers that are beyond the indices for whole_st_gen).
As opposed to the situation where you iterate over the container itself the behaviour when you do this is fully specified. You may delete and insert elements in the container if you are careful.
Here is a straight answer to your question, you should not delete items on a list while iterating on the same list, it yields unexpected results to you as some items will be skipped. Inorder to avoid this you can use copy of same list in the iteration.
I am new to python program. the below code has some error with list.
len = []
def collatz_length(n,count):
++count
if len[n]:
return len[n]
if n == 1:
len[n] = count
elif n & 1:
len[n] = count + collatz_length(3 * n + 1,0)
else:
len[n] = count + collatz_length(n >> 1,0)
return len[n]
print collatz_length(13,0)
i am trying to figure out the length.But the gives error
OUTPUT
Traceback (most recent call last):
File "collatz.py", line 21, in <module>
print collatz_length(13,0)
File "collatz.py", line 9, in collatz_length
if len[n]:
IndexError: list index out of range
It means that n is beyond the length of the list (which is initially zero). Rather than doing len[n], you want to see if n is in your list:
# Renaming your array to my_array so that it doesn't shadow the len() function,
# It's also better to put it as a parameter, not a global
def collatz_length(n, count, my_array):
if n < len(my_array):
return my_array[n]
# ... all your elses
If you have an array with 5 things in it, and try to access array[5], you are reading outside the bounds of the array. I think that is what is happening, but your code is also very hard to understand. If that's not what is happening, you may need to add some comments or otherwise clarify what you are doing. It looks like you have an empty array and are trying to access location 13 in it, which makes no sense.
I'm doing some tutorials online, and I'm stuck with an exercise :Write a function getBASIC() which takes no arguments, and does the following: it should keep reading lines from input using a while loop; when it reaches the end it should return the whole program in the form of a list of strings. Example of list of strings:
5 GOTO 30
10 GOTO 20
20 GOTO 10
30 GOTO 40
40 END
I wrote a program, but it doesn't work, however I will post it too:
def getBASIC():
L=[]
while "END" not in L:
L.append(str(input()))
if str(input()).endswith("END"):
break
return L
Also I notice you that I'm not allowed to use IS or RECURSION .
There are several errors:
you call input() twice without appending it to the list the second time
'END' in L determines whether there is 'END' (whole) line in the list L (there isn't)
Note: input()already returns a str object; you don't need to call str() on its returned value.
To read input until you've got empty line you could:
def getBASIC():
return list(iter(input, ''))
Or to read until END is encountered at the end of line:
def getBASIC():
L = []
while True:
line = input()
L.append(line)
if line.endswith("END"):
break #NOTE: it doesn't break even if `line` is empty
return L
Back when I was learning Pascal, we used a priming read for loops that needed at least one iteration. This still works well in Python (I prefer it to a while True / break loop).
By simply testing the last line in the list (rather than the last line read) we eliminate the need for a variable to store the input and can combine the reading and appending operations.
def getBASIC():
lines = [input("]")] # use Applesoft BASIC prompt :-)
while not lines[-1].upper().rstrip().endswith("END"):
lines.append(input("]"))
return lines
try this one:
def get_basic():
L = []
while True:
line = str( input() )
L.append( line )
if "END" in line:
break
return L
Use raw_input() instead of input(). input() function takes string from standard input and tries to execute it as a python source coode. raw_input() will return a string as expected.
You use input() 2 time inside a loop. That means you await two string to be input inside one cycle iteration. You don't need last condition (if statement) inside your while loop. It'll end up when "END" is encountered in L.
The next code should do the job:
def getBASIC():
L=[]
while True:
inp = raw_input()
L.append(inp)
if inp.endswith('END'):
break
return L
Your code has the following problems.
"while "END" not in L" will not work if your input is "40 END"
In Python 2.7, "input()" is equivalent to "eval(raw_input()))". So, Python is trying to evaluate the "GOTO" statements.
"if str(input()).endswith("END"):" does not append input to L
So, here is an updated version of your function:
def getBASIC():
L = []
while True:
# Grab input
L.append(str(raw_input()))
# Check if last input ends with "END"
if L[-1].endswith("END"):
break
return L