Iterative deletion from list (Python 2) - python

I've just started programming, and I'm solving Project Euler problems with Python for practice.
(This is problem #2, finding the sum of the even fibonacci numbers within 4 million.)
My problem appears in the loop at the bottom, where I'm trying to locate the odd numbers in the list, and delete them.
del fiblist[i] gives me the following error message:
Traceback (most recent call last):
File ".../euler.py", line 35, in
del fiblist[i]
IndexError: list assignment index out of range
I don't see what I'm doing wrong here, and would really appreciate if anyone could help me see what I'm doing wrong here.
#euler2
def fibonacciList(limit):
'''generates a list of fib numbers up to N'''
mylist = []
a,b = 1,2
while True:
if a <= limit:
mylist.append(a)
a,b = b,a+b
else:
break
return mylist
fiblist = fibonacciList(4000000)
for i in fiblist:
if i%2 != 0: #if not even, delete from list
print i
del fiblist[i]
print fiblist

One problem here is that i is the item from the list, not it's index. So when you do del fiblist[i] you are not removing i, but the value that is at index i (which doesn't exist so you get an error). This can be fixed by using enumerate() to get indices to use, however, doing so introduces a new problem.
The main issue here is that you can't modify the length of a list while you iterate over it, as it messes with Python's iteration. One solution would be to copy the list and work on a copy, but the better one is to use a list comprehension to do what you want:
[i for i in fiblist if i%2 == 0]
This produces a new list with only the elements you want in. List comprehensions are a powerful tool, so I suggest you watch the video I linked for more.

Related

Python: My program is not executing a for loop properly

I have a list, num_list, which contains random numbers identified by using random.
One for loop is responsible for selecting numbers from the original list and my compare function is supposed to compare the obtained number with the numbers present in my new list. If the number is greater than the first number obtained from the for loop, I check if the same num is less than the next num in the new list. This means that the num is between the numbers and is therefore inserted there. However, I am stuck in a loop and can't understand what is going on. I have tried everything I know but, sadly, I'm still just a beginner. I hope someone here can make head or tail of this unfortunate mess.
new_list.append((num_list)[0])
def compare(num, new_list, index, temp_char):
for char in new_list:
print(char)
if num > char:
index = new_list.index(char)
try:
temp_char = (new_list)[index + 1]
if num <= temp_char:
new_list.insert(index+1, num)
except:
new_list.insert(index, num)
break
for num in num_list:
#print(num)
compare(num, new_list, index, temp_char)
print(new_list)
As Sayse said in comment, you shouldn't modify a list while iterating over it. This leads to undefined behavior. Instead, copy the list new_list before iterating over it. To copy the list, there are many solutions, but here's a simple one: copied = [i for i in new_list]
This won't work if your list is a nested list.

How to return a list that is made up of extracted elements from another list in python?

I am building a function to extract all negatives from a list called xs and I need it to add those extracted numbers into another list called new_home. I have come up with a code that I believe should work, however; it is only showing an empty list.
Example input/output:
xs=[1,2,3,4,0,-1,-2,-3,-4] ---> new_home=[1,2,3,4,0]
Here is my code that returns an empty list:
def extract_negatives(xs):
new_home=[]
for num in range(len(xs)):
if num <0:
new_home= new_home+ xs.pop(num)
return
return new_home
Why not use
[v for v in xs if v >= 0]
def extract_negatives(xs):
new_home=[]
for num in range(len(xs)):
if xs[num] < 0:
new_home.append(xs[num])
return new_home
for your code
But the Chuancong Gao solution is better:
def extract_negative(xs):
return [v for v in xs if v >= 0]
helper function filter could also help. Your function actually is
new_home = filter(lambda x: x>=0, xs)
Inside the loop of your code, the num variable doesn't really store the value of the list as you expect. The loop just iterates for len(xs) times and passes the current iteration number to num variable.
To access the list elements using loop, you should construct loop in a different fashion like this:
for element in list_name:
print element #prints all element.
To achieve your goal, you should do something like this:
another_list=[]
for element in list_name:
if(element<0): #only works for elements less than zero
another_list.append(element) #appends all negative element to another_list
Fortunately (or unfortunately, depending on how you look at it) you aren't examining the numbers in the list (xs[num]), you are examining the indexes (num). This in turn is because as a Python beginner you probably nobody haven't yet learned that there are typically easier ways to iterate over lists in Python.
This is a good (or bad, depending on how you look at it) thing, because had your code taken that branch you would have seen an exception occurring when you attempted to add a number to a list - though I agree the way you attempt it seems natural in English. Lists have an append method to put new elements o the end, and + is reserved for adding two lists together.
Fortunately ignorance is curable. I've recast your code a bit to show you how you might have written it:
def extract_negatives(xs):
out_list = []
for elmt in xs:
if elmt < 0:
out_list.append(elmt)
return out_list
As #ChuangongGoa suggests with his rather terse but correct answer, a list comprehension such as he uses is a much better way to perform simple operations of this type.

Nested list sorting in Python

This is a question of nested loop where I have to find the names of students in alphabetical order with 2nd lowest marks.
I am getting following error:
Traceback (most recent call last):
File "solution.py", line 12, in <module>
if (l[i][0]==l[0][0]):
IndexError: list index out of range
Following is my complete code.
l=list()
p=list()
q=list()
for i in range (int(raw_input())):
p=[raw_input(),int(raw_input())]
p.reverse()
l.append(p)
l.sort()
print len(l)
for i in range(0,len(l)):
print "i= ",i
if (l[i][0]==l[0][0]):
l.remove(l[i])
print l
for i in range(0,len(l)):
if (l[i][0]==l[0][0]):
q.append(l[i][1])
q.sort()
for i in range(0,len(q)):
print q[i]
I have even printed the index which shows the values are in range. Please run the function to find the following output:
4
i= 0
i= 1
i= 2
i= 3
I will happy if I get any better method from my the community ,But my main concern is the error I am getting "Index Out of Range" .It doesn't seem right here
The problem is that you are removing items from a loop. The thumb rule is that you shall never remove items from a list while iterating over it.
I don't really understand your code right now, but you can just change the way you are doing the first loop and apply the same logic for the next ones, at least you will have what you want.
The idea here is that with the while statement, after each iteration, you will have another verification of the size of the list. Meanwhile with the for loop, only at the first time, since range will return a list and the for loop will just iterate over the list which was already created.
l=list()
p=list()
q=list()
for i in range (int(raw_input())):
p=[raw_input(),int(raw_input())]
p.reverse()
l.append(p)
l.sort()
print len(l)
i = 0
while i < len(l):
print "i= ",i
if (l[i][0]==l[0][0]):
l.remove(l[i])
i += 1
print l
You are using remove, because of that l, in some moment, have less elements than you expect.

Why does this give me an IndexError?

I have the following code that opens a csv, and appends all the values to a list. I then remove all the values that do not start with '2'. However, on the line if lst[k][0] != '2':, it raises an error:
Traceback (most recent call last):
File "historical_tempo1.py", line 23, in <module>
if lst[k][0] != '2':
IndexError: list index out of range
Here is the code:
y = open('today.csv')
lst = []
for k in y:
lst.append(k)
lst = ' '.join(lst).split()
for k in range(0, len(lst)-1):
if lst[k][0] != '2':
lst[k:k+1] = ''
Here is the first bit of content from the csv file:
Date,Time,PM2.5 Mass concentration(ug/m3),Status
3/15/2014,4:49:13 PM,START
2014/03/15,16:49,0.5,0
3/15/2014,4:49:45 PM,START
2014/03/15,16:50,5.3,0
2014/03/15,16:51,5.1,0
2014/03/15,16:52,5.0,0
2014/03/15,16:53,5.0,0
2014/03/15,16:54,5.4,0
2014/03/15,16:55,6.4,0
2014/03/15,16:56,6.4,0
2014/03/15,16:57,5.0,0
2014/03/15,16:58,5.2,0
2014/03/15,16:59,5.2,0
3/15/2014,5:03:48 PM,START
2014/03/15,17:04,4.8,0
2014/03/15,17:05,4.9,0
2014/03/15,17:06,4.9,0
2014/03/15,17:07,5.1,0
2014/03/15,17:08,4.6,0
2014/03/15,17:09,4.9,0
2014/03/15,17:10,4.4,0
2014/03/15,17:11,5.7,0
2014/03/15,17:12,4.4,0
2014/03/15,17:13,4.0,0
2014/03/15,17:14,4.6,0
2014/03/15,17:15,4.7,0
2014/03/15,17:16,4.8,0
2014/03/15,17:17,4.5,0
2014/03/15,17:18,4.4,0
2014/03/15,17:19,4.5,0
2014/03/15,17:20,4.8,0
2014/03/15,17:21,4.6,0
2014/03/15,17:22,5.1,0
2014/03/15,17:23,4.2,0
2014/03/15,17:24,4.6,0
2014/03/15,17:25,4.5,0
2014/03/15,17:26,4.4,0
Why do you get an IndexError? Because when you write lst[k:k+1] = '', you have just removed the k+1 element from your list, which means your list is shorter by 1 element, and your loop is still going up to the old len(lst), so the index variable k is guaranteed to go over.
How can you fix this? Loop over a copy and delete from the original using list.remove().
The following code loops over the copy.
for s in lst[:]:
if k[0] != '2':
list.remove(k)
The expressions lst[k][0] raises an IndexError, which means that either:
# (1) this expressions raises it
x = lst[k]
# or (2) this expression raises it
x[0]
If (1) raises it, it means len(lst) <= k, i.e. there are fewer items than you expect.
If (2) raises it, it means x is an empty string, which means you can't access its item at index 0.
Either way, instead of guessing, use pdb. Run your program using pdb, and at the point your script aborts, examine the values of lst, k, lst[k], and lst[k][0].
Basically, your list, 'lst', starts out at length 43. The 'slice' operation lst[k:k+1] doesn't replace two separate indexed values with '', but wipes out one of the list entries. If you did a lst[k:k+5], you would wipe out five entries. Try it in the interpreter.
I'd recommend you don't try to wipe out those entries particularly in the list you are performing operations. It is shrinking in this case which means you go out of range and get an "IndexError". Store the values you want into another a list if you have to remove the lines that don't begin with "2".
List comprehensions work great in this case...
mynewlist = [x for x in lst if x[0] == '2']

python : list index out of range error while iteratively popping elements

I have written a simple python program
l=[1,2,3,0,0,1]
for i in range(0,len(l)):
if l[i]==0:
l.pop(i)
This gives me error 'list index out of range' on line if l[i]==0:
After debugging I could figure out that i is getting incremented and list is getting reduced.
However, I have loop termination condition i < len(l). Then why I am getting such error?
You are reducing the length of your list l as you iterate over it, so as you approach the end of your indices in the range statement, some of those indices are no longer valid.
It looks like what you want to do is:
l = [x for x in l if x != 0]
which will return a copy of l without any of the elements that were zero (that operation is called a list comprehension, by the way). You could even shorten that last part to just if x, since non-zero numbers evaluate to True.
There is no such thing as a loop termination condition of i < len(l), in the way you've written the code, because len(l) is precalculated before the loop, not re-evaluated on each iteration. You could write it in such a way, however:
i = 0
while i < len(l):
if l[i] == 0:
l.pop(i)
else:
i += 1
The expression len(l) is evaluated only one time, at the moment the range() builtin is evaluated. The range object constructed at that time does not change; it can't possibly know anything about the object l.
P.S. l is a lousy name for a value! It looks like the numeral 1, or the capital letter I.
You're changing the size of the list while iterating over it, which is probably not what you want and is the cause of your error.
Edit: As others have answered and commented, list comprehensions are better as a first choice and especially so in response to this question. I offered this as an alternative for that reason, and while not the best answer, it still solves the problem.
So on that note, you could also use filter, which allows you to call a function to evaluate the items in the list you don't want.
Example:
>>> l = [1,2,3,0,0,1]
>>> filter(lambda x: x > 0, l)
[1, 2, 3]
Live and learn. Simple is better, except when you need things to be complex.
What Mark Rushakoff said is true, but if you iterate in the opposite direction, it is possible to remove elements from the list in the for-loop as well. E.g.,
x = [1,2,3,0,0,1]
for i in range(len(x)-1, -1, -1):
if x[i] == 0:
x.pop(i)
It's like a tall building that falls from top to bottom: even if it is in the middle of collapse, you still can "enter" into it and visit yet-to-be-collapsed floors.
I think the best way to solve this problem is:
l = [1, 2, 3, 0, 0, 1]
while 0 in l:
l.remove(0)
Instead of iterating over list I remove 0 until there aren't any 0 in list
List comprehension will lead you to a solution.
But the right way to copy a object in python is using python module copy - Shallow and deep copy operations.
l=[1,2,3,0,0,1]
for i in range(0,len(l)):
if l[i]==0:
l.pop(i)
If instead of this,
import copy
l=[1,2,3,0,0,1]
duplicate_l = copy.copy(l)
for i in range(0,len(l)):
if l[i]==0:
m.remove(i)
l = m
Then, your own code would have worked.
But for optimization, list comprehension is a good solution.
The problem was that you attempted to modify the list you were referencing within the loop that used the list len(). When you remove the item from the list, then the new len() is calculated on the next loop.
For example, after the first run, when you removed (i) using l.pop(i), that happened successfully but on the next loop the length of the list has changed so all index numbers have been shifted. To a certain point the loop attempts to run over a shorted list throwing the error.
Doing this outside the loop works, however it would be better to build and new list by first declaring and empty list before the loop, and later within the loop append everything you want to keep to the new list.
For those of you who may have come to the same problem.
I am using python 3.3.5. The above solution of using while loop did not work for me. Even if i put print (i) after len(l) it gave me an error. I ran the same code in command line (shell)[ window that pops up when we run a function] it runs without error. What i did was calculated len(l) outside the function in main program and passed the length as a parameter. It worked. Python is weird sometimes.
I think most solutions talk here about List Comprehension, but if you'd like to perform in place deletion and keep the space complexity to O(1); The solution is:
i = 0
for j in range(len(arr)):
if (arr[j] != 0):
arr[i] = arr[j]
i +=1
arr = arr[:i]
x=[]
x = [int(i) for i in input().split()]
i = 0
while i < len(x):
print(x[i])
if(x[i]%5)==0:
del x[i]
else:
i += 1
print(*x)
Code:
while True:
n += 1
try:
DATA[n]['message']['text']
except:
key = DATA[n-1]['message']['text']
break
Console :
Traceback (most recent call last):
File "botnet.py", line 82, in <module>
key =DATA[n-1]['message']['text']
IndexError: list index out of range
I recently had a similar problem and I found that I need to decrease the list index by one.
So instead of:
if l[i]==0:
You can try:
if l[i-1]==0:
Because the list indices start at 0 and your range will go just one above that.

Categories