IndexError: list index out of range - python 3.8 - python

Alright. I have seen quite a few questions like this on StackOverflow, but none really helped me. I am working on a python script that moves some base32 code around. Here is the problematic function:
def getCode():
i = 0
j = 0
k = 0
base32 = input("Please paste your code below:\n")
base32 = base32.split("#")
while i < len(base32):
base32[i] = base32[i].split(",")
i += 1
while j < len(base32[i]):
base32[i[j]] = base32[i[j]].split(" ")
j += 1
while k < len(base32[i[j]]):
if base32[i[[j[k]]]] == "":
base32.list_splice(k, 1)
k += 1
if base32[i[j]].len() == 0:
base32[i].list_splice(j, 1)
return base32
I defined list_splice() earlier. I also declared getCode() after this. When I run it, it asks me for my code, but when I hit enter after, it gives me this:
Traceback (most recent call last):
File "C:\Users\gabri\SynologyDrive\track_mover.py", line 43, in <module>
getCode()
File "C:\Users\gabri\SynologyDrive\track_mover.py", line 32, in getCode
while j < len(base32[i]):
IndexError: list index out of range
I am not sure why. From what I understand, a IndexError generally refers to passing in a list index that is too high, like if you have a string with 3 characters and pass list[3]. I have no idea why it is saying this. I would think that the
while loop would keep it from getting out of hand, but maybe not? I have tried all kinds of lengths of strings that I paste into it, but that doesn't seem to be the answer.
===
EDIT
I tried Mustafa Aydın's answer, and while that does fix some things, it now outputs this:
Traceback (most recent call last):
File "C:\Users\gabri\SynologyDrive\track_mover.py", line 44, in <module>
getCode()
File "C:\Users\gabri\SynologyDrive\track_mover.py", line 33, in getCode
base32[i[j]] = base32[i[j]].split(" ")
TypeError: 'int' object is not subscriptable
I have no idea why it is doing this.
I also got some questions regarding the list_splice() function. What it is is the python equivalent of the javascript .splice() built-in function. Here is the code for it:
def list_splice(target, start, delete_count=None, *items):
"""Remove existing elements and/or add new elements to a list.
target the target list (will be changed)
start index of starting position
delete_count number of items to remove (default: len(target) - start)
*items items to insert at start index
Returns a new list of removed items (or an empty list)
"""
if delete_count == None:
delete_count = len(target) - start
# store removed range in a separate list and replace with *items
total = start + delete_count
removed = target[start:total]
target[start:total] = items
return removed
If it helps any, here is the JavaScript I am converting from:
function loadCode(input) {
var base32 = document.getElementById(input).value;
var base32 = base32.split("#");
for (var i = 0; i < base32.length; i++) {
base32[i] = base32[i].split(",");
for (var j = 0; j < base32[i].length; j++) {
base32[i][j] = base32[i][j].split(" ");
for (var k = 0; k < base32[i][j].length; k++) {
if (base32[i][j][k] == "") {
base32[i][j].splice(k, 1);
}
}
if (base32[i][j].length == 0) {
base32[i].splice(j, 1);
}
}
}
return base32;
}
Thanks in advance for any help!

You should increment i after you are done with the i'th item of the base32 list. Also (response to your edit), accessing a list element in a 2-dimensional fashion is done as base32[i][j] i.e. you first reach to i'th item, then reach to j'th item of that item. Similar applies to base32[i][j][k].
Edit: Seeing your javascript code, I'd say that you should be using for loops in python also. Those will be less error prone. Also, the splicing you are doing (only removing one item) can be done with pop. Lastly, removing elements whilst iterating over a list is not a good idea; one solution is to iterate in reverse order. So, putting all these together, you end up with this:
def getCode():
base32 = input("Please paste your code below:\n")
base32 = base32.split("#")
for i in reversed(range(len(base32))):
base32[i] = base32[i].split(",")
for j in reversed(range(len(base32[i]))):
base32[i][j] = base32[i][j].split(" ")
for k in reversed(range(len(base32[i][j]))):
if base32[i][j][k] == "":
base32[i][j].pop(k)
if len(base32[i][j]) == 0:
base32[i].pop(j)
return base32

while i < len(base32):
base32[i] = base32[i].split(",")
i += 1
while j < len(base32[i]):
On the final iteration of this loop, the loop condition i < len(base32) is still true, so the loop executes one last time. But then i is incremented inside the loop, so now base32[i] is out-of-bounds.
Generally, incrementing a loop variable should be done at the bottom of the loop, so that no code executes with a possibly-too-large value of the loop variable.

Related

Python dropping values from string

I'm trying to assign a string value to an array in python but it keeps dropping the last two values of the string.
arr = ['5702564' 'z_jets' '1' '102549' '-2.9662' 'j,335587,132261,-1.57823,1.02902' 'j,270540,58844.5,2.20566,1.6064' 'e-,186937,131480,0.888915,-0.185666' 'j,148467,23648,-2.52332,-1.70799' 'j,107341,106680,-0.0989776,-2.67901' 'j,85720.1,62009,0.840127,-1.73805' 'e+,80014.3,79281.7,0.135844,0.275231' 'j,55173.9,52433.5,-0.183147,2.62501']
for i in range(5, len(arr)):
if arr[i][0].isalpha():
values_to_update = arr[i].split(',')
values_to_update[1] = str(np.log(float(values_to_update[1])))
values_to_update[2] = str(np.log(float(values_to_update[2])))
part = ','.join(values_to_update)
print(part)
print(arr[i])
arr[i] = part
print(arr[i])
Trying to debug I find that:
part = j,12.723636516233727,11.792532522065033,-1.57823,1.02902
arr[i] = j,335587,132261,-1.57823,1.02902
when I make arr[i] = part I get j,12.723636516233727,11.792532522065
Why do the final two values get dropped? Is this some sort of weird bug or have I made a mistake somewhere?

Turn python code into a generator function

How can I turn this code into a generator function? Or can I do it in a other way avoiding reading all data into memory?
The problem right now is that my memory gets full. I get KILLED after a long time when executing the code.
Code:
data = [3,4,3,1,2]
def convert(data):
for index in range(len(data)):
if data[index] == 0:
data[index] = 6
data.append(8)
elif data[index] == 1:
data[index] = 0
elif data[index] == 2:
data[index] = 1
elif data[index] == 3:
data[index] = 2
elif data[index] == 4:
data[index] = 3
elif data[index] == 5:
data[index] = 4
elif data[index] == 6:
data[index] = 5
elif data[index] == 7:
data[index] = 6
elif data[index] == 8:
data[index] = 7
return data
for i in range(256):
output = convert(data)
print(len(output))
Output:
266396864
290566743
316430103
346477329
376199930
412595447
447983143
490587171
534155549
582826967
637044072
692630033
759072776
824183073
903182618
982138692
1073414138
1171199621
1275457000
1396116848
1516813106
Killed
To answer the question: to turn a function into a generator function, all you have to do is yield something. You might do it like this:
def convert(data):
for index in range(len(data)):
...
yield data
Then, you can iterate over the output like this:
iter_converted_datas = convert(data)
for _, converted in zip(range(256), iter_converted_datas):
print(len(converted))
I also would suggest some improvements to this code. The first thing that jumps out at me, is to get rid of all those elif statements.
One helpful thing for this might be to supply a dictionary argument to your generator function that tells it how to convert the data values (the first one is a special case since it also appends).
Here is what that dict might look like:
replacement_dict = {
0: 6,
1: 0,
2: 1,
3: 2,
4: 3,
5: 4,
6: 5,
7: 6,
8: 7,
}
By the way: replacing a series of elif statements with a dictionary is a pretty typical thing to do in python. It isn't always appropriate, but it often works well.
Now you can write your generator like this:
def convert(data, replacement_dict):
for index in range(len(data)):
if index==0:
lst.append(8)
data[index] = replacement_dict[index]
yield data
And use it like this:
iter_converted_datas = convert(data, replacement_dict)
for _, converted in enumerate(iter_converted_datas):
print(len(converted))
But we haven't yet addressed the underlying memory problem.
For that, we need to step back a second: the reason your memory is filling up is you have created a routine that grows very large very fast. And if you were to keep going beyond 256 iterations, the list would get longer without end.
If you want to compute the Xth output for some member of the list without storing the entire list into memory, you have to change things around quite a bit.
My suggestion on how you might get started: create a function to get the Xth iteration for any starting input value.
Here is a generator that just produces outputs based on the replacement dict. Depending on the contents of the replacement dict, this could be infinite, or it might have an end (in which case it would raise a KeyError). In your case, it is infinite.
def process_replacements(value, replacement_dict):
while True:
yield (value := replacement_dict[value])
Next we can write our function to process the Xth iteration for a starting value:
def process_xth(value, xth, replacement_dict):
# emit the xth value from the original value
for _, value in zip(range(xth), process_replacements(value, replacement_dict)):
pass
return value
Now you can process the Xth iteration for any value in your starting data list:
index = 0
xth = 256
process_xth(data[index], xth, data, replacement_dict)
However, we have not appended 8 to the data list anytime we encounter the 0 value. We could do this, but as you have discovered, eventually the list of 8s will get too big. Instead, what we need to do is keep COUNT of how many 8s we have added to the end.
So I suggest adding a zero_tracker function to increment the count:
def zero_tracker():
global eights_count
eights_count += 1
Now you can call that function in the generator every time a zero is encountered, but resetting the global eights_count to zero at the start of the iteration:
def process_replacements(value, replacement_dict):
global eights_count
eights_count = 0
while True:
if value == 0:
zero_tracker()
yield (value := replacement_dict[value])
Now, for any Xth iteration you perform at some point in the list, you can know how many 8s were appended at the end, and when they were added.
But unfortunately simply counting the 8s isn't enough to get the final sequence; you also have to keep track of WHEN (ie, which iteration) they were added to the sequence, so you can know how deeply to iterate them. You could store this in memory pretty efficiently by keeping track of each iteration in a dictionary; that dictionary would look like this:
eights_dict = {
# iteration: count of 8s
}
And of course you can also calculate what each of these 8s will become at any arbitrary depth:
depth = 1
process_xth(8, depth, data, replacement_dict)
Once you know how many 8s there are added for every iteration given some finite number of Xth iterations, you can construct the final sequence by just yielding the correct value the right number of times over and over again, in a generator, without storing anything. I leave it to you to figure out how to construct your eights_dict and do this final part. :)
Here are a few things you can do to optimize it:
Instead of range(len(data)) you can use enumerate(data). This gives you access to both the element AND it's index. Example:
EDIT: According to this post, range is faster than enumerate. If you care about speed, you could ignore this change.
for index, element in enumerate(data):
if element == 0:
data[index] = 6
Secondly, most of the if statements have a predictable pattern. So you can rewrite them like this:
def convert(data):
for idx, elem in enumerate(data):
if elem == 0:
data[idx] = 6
data.append(8)
if elem <= 8:
data[index] = elem - 1
Since lists are mutable, you don't need to return data. It modifies it in-place.
I see that you ask about generator functions, but that ain't solve your memory issues. You run out of memory because, well, you keep everything in memory...
The memory complexity of your solution is O*((8/7)^n) where n is a number of calls to convert. This is because every time you call convert(), the data structure gets expanded with 1/7 of its elements (on average). This is the case because every number in your structure has (roughly) a 1/7 probability of being zero.
So memory complexity is O*((8/7)^n), hence exponential. But can we do better?
Yes we can (assuming that the conversion function remains this "nice and predictable"). We can keep in memory just the number of zeros that were present in a structure when we called a convert(). That way, we will have a linear memory complexity O*(n). Does that come with a cost?
Yes. Element access time no longer has a constant complexity O(1) but it has linear complexity O(n) where n is a number of calls to convert() (At least that's what I came up with).
But it resolves out-of-memory issue.
I also assumed that there would be need to iterate over the computed list. If you are only interested in the length, it is sufficient to keep count of digits in a number and work over those. That way you would use just a few integers of memory.
Here is a code:
from copy import deepcopy # to keep original list untouched ;)
class Data:
def __init__(self, seed):
self.seed = deepcopy(seed)
self.iteration = 0
self.zero_counts = list()
self.len = len(seed)
def __len__(self):
return self.len
def __iter__(self):
return SeededDataIterator(self)
def __repr__(self):
"""not necessary for a solution, but helps with debugging"""
return "[" + (", ".join(f"{n}" for n in self)) + "]"
def __getitem__(self, index: int):
if index >= self.len:
raise IndexError
if index < len(self.seed):
ret = self.seed[index] - self.iteration
else:
inner_it_idx = index - len(self.seed)
for i, cnt in enumerate(self.zero_counts):
if inner_it_idx < cnt:
ret = 9 + i - self.iteration
break
else:
inner_it_idx -= cnt
ret = ret if ret > 6 else ret % 7
return ret
def convert(self):
zero_count = sum((self[i] == 0) for i, _ in enumerate(self.seed))
for i, count in enumerate(self.zero_counts):
i = 9 + i - self.iteration
i = i if i > 6 else i % 7
if i == 0:
zero_count += count
self.zero_counts.append(zero_count)
self.len += self.zero_counts[self.iteration]
self.iteration += 1
class DataIterator:
"""Iterator class for the Data class"""
def __init__(self, seed_data):
self.seed_data = seed_data
self.index = 0
def __next__(self):
if self.index >= self.seed_data.len:
raise StopIteration
ret = self.seed_data[self.index]
self.index += 1
return ret
There is code that tests logical equality and prints required output:
original_data = [3,4,3,1,2]
data = deepcopy(original_data)
d = Data(data)
for _ in range(30):
output = convert(data)
d.convert()
print("---------------------------------------")
print(len(output))
assert len(output) == len(d)
for i, e in enumerate(output):
assert e == d[i]
data = deepcopy(original_data)
d = Data(data)
for _ in range(256):
d.convert()
print(len(d))
Results after your program crashed are:
1516813106
1662255394 <<< Killed here
1806321765
1976596756
2153338313
2348871138
2567316469
2792270106
3058372242
3323134871
3638852150
3959660078
4325467894
4720654782
5141141244
5625688711
6115404977
6697224392
7282794949
7964320044
8680314860
9466609138
10346343493
11256546221
12322913103
13398199926
14661544436
15963109809
17430929182
19026658353
20723155359
22669256596
24654746147
26984457539

Python: How to enumerate over a list of lists and use the length of that list to find values

I'm building a slot machine simulator in Python. I have setup the reels as follows:
Reels = [[10,9,4,5,7,4,9,2,6,7,3,4,9,3,4,9,6,5,4,11,8,9,11,2,4,1,9,10,4,9,10,6,4,9,1,5,4,9,1,10,3,8,6,4,9,1,8],
[4,3,5,4,3,5,2,8,4,1,8,10,1,2,9,8,11,2,8,5,6,11,3,4,2,8,4,7,6,10,8,7,9,4,1,6,8,4,2,9,8,3,5,4,10,8],
[1,9,4,2,5,1,6,9,2,5,9,2,10,9,4,8,9,11,2,5,8,9,10,4,1,10,9,2,10,5,9,7,5,6,8,9,7,3,10,6,2,9,5,8,3,1,10,3],
[8,10,3,8,7,3,9,8,10,11,3,10,9,6,8,10,11,6,5,3,8,1,4,9,5,8,1,4,3,8,1,5,9,10,8,3,9,4,3,8,9,4,6,11,3,8,9,7,10,11],
[4,11,1,6,3,9,5,10,9,5,8,11,10,3,1,4,10,3,9,4,7,3,9,10,4,3,1,5,10,6,5,8,4,6,9,1,5,10,8,9,5,4,6,8,9,4,8,5,7,9]]
Now I need to iterate through them from 1 through 5 and build a 3X5 matrix. I want to start by producing a random number that determines where on that reel to stop. That value will be the middle value on that reel. Then, I need to add the top and bottom values (but have to account for the middle number potentially being at the beginning or end of the reel strip. I'm getting the error "list index out of range" on the if StopValue == Reels[i][len(Reels[i])]: line:
def spin():
SpinValues = [[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0]]
for i, object in enumerate(Reels):
length = len(Reels[i])
StopValue = random.randint(0,length)
SpinValues[i][1] = Reels[i][StopValue]
if StopValue == 0:
SpinValues[i][0] = Reels[i][len(Reels[i])]
else:
SpinValues[i][0] = Reels[i][StopValue - 1]
if StopValue == Reels[i][len(Reels[i])]:
SpinValues[i][2] = Reels[i][0]
else:
SpinValues[i][2] = Reels[i][StopValue +1]
print(SpinValues)
spin()
Initially I thought I could do this with just "for i in reels," but I read a post here suggesting to use the "for index, object in enumerate(Reels)" method.
len(Reels[i]) is not a valid index for Reels[i]. The last valid index is len(Reels[i]) - 1
To refer to the last item in a list called my_list, you must use
my_list[-1]
or
my_list[len(my_list)-1]
and not:
my_list[len(my_list)]
The reason is that in Python, all indexing starts from 0, and not from 1

Python simple for loop issue

Currently learning python. Normally a C++ guy.
if wallpaper == "Y":
charge = (70)
print ("You would be charged £70")
wallpaperList.append(charge)
elif wallpaper == "N" :
charge = (0)
else:
wallPaper ()
surfaceArea
totalPapers = 0
for item in range (len(wallpaperList)):
totalPapers += wallpaperList[item]
I am trying do a for loop for the if statement.
In c++ this will just be a simple
for (I=0; i<pRooms; i++){
}
I am trying to add the above code in a for loop but I seem to fail.
Thanks
Python loops iterate over everything in the iterable:
for item in wallpaperList:
totalPapers += item
In modern C++, this is analogous to:
std::vector<unsigned int> wallpaperList;
// ...
for(auto item: wallpaperList) {
totalPapers += item
}
You could also just use the sum function:
cost = sum(wallpaperList)
If the charge is 70 every time, why not just do multiplication?
while wallPaper == 'Y':
# ...
# Another satisfied customer!
i += 1
cost = i * 70
For the exact equivalent of your for loop, use range:
for (i=0; i<pRooms; i++){ # C, C++
}
for i in range(pRooms): # Python
...
Both loops iterate over the values 0 to pRooms-1. But python gives you other options. You can iterate over the elements of the list without using an index:
for room in listOfRooms:
if room.wallpaper == "Y":
...
List comprehensions are also nice. If you don't care about the print calls in your code, you could compute the cost in one line with something like this:
totalPapers = sum(70 for room in listOfRooms if room.wallpaper == "Y")

Python - List Binary Search without bisect

I have a list and I want to binary_search a key(number).
My code is below but I don't have a clue what to do where the bold text on code is:
(What to do with this? Is an other function? int imid = midpoint(imin, imax))
List = []
x = 1
#Import 20 numbers to list
for i in range (0,20):
List.append (i)
print (List)
key = input("\nGive me a number for key: ")
def midpoint(imin, imax):
return point((imin+imax)/2)
def binary_search(List,key,imin,imax,point):
while (imax >= imin):
int imid = midpoint(imin, imax)
if(List[imid] == key):
return imid;
elif (List[imid] < key):
imin = imid + 1;
else:
imax = imid - 1;
return KEY_NOT_FOUND;
print (binary_search(key))
midpoint(imin, imax)
binary_search(List,key,imin,imax,point)
It doesn't seem to be doing anything for you; remove the call to midpoint, and point, and just have
def binary_search(List,key,imin,imax,point):
while (imax >= imin):
imid = (imin + imax) / 2
(However, there are some things wrong with your code, and it won't work with just that change;
You create a list called List then try to append to an uninitialized variable called myList
You 'import 20 random' numbers, but range() is not random, it's a simple sequence 1, 2, 3, 4...
range already returns a list, no need to count through it and copy it, just use it
You call binary_search with an empty List, a key, and three uninitialized variables
binary_search assumes the list is sorted, which it is, but if the comment about 'random numbers' was correct, it wouldn't be.
)

Categories

Resources