For loop with changing range - python

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.

Related

How to solve the error list index out of range ? (On python using pygame and sys) [duplicate]

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.

Python: Function to find out if a tuple contains even numbers?

So what I want is a function that gets a tuple input from a user and then figures out if that tuple contains an even number. I want to know how to do it with a for or while loop. I've tried it but the code doesn't work. I've kind of got something but it's not working:
def ifEven(x):
i = -1
if isinstance(x, tuple):
while i < len(x):
for i in x:
i = i + 1
if x[i] % 2 == 0:
return True
else:
return False
You should read the documentation about for statement in Python: https://docs.python.org/2/tutorial/controlflow.html#for-statements.
Here is a working code:
def ifEven(x):
if isinstance(x, tuple):
for i in x:
if i % 2 == 0:
return True
return False
That being said, it can be rewritten as a one-liner using Python's generator expressions:
def isEven(x):
return any(v % 2 == 0 for v in x)
You indentation looks a bit off but that might just be how you copy/pasted. True is "True" in python not "true" as in your return function which might also be messing you up.
In for loop i itself initializes and start iterating over the tuple. The content of i at each iteration is one of the element from the tuple.
Let's have a deeper look into it. let your tuple be (3, 4, 6, 7)
In first iteration :-
for i in x # i=3
i = i+1 # i = 4
if x[i] %2 == 0 : # x[4] % 2 == 0 => This will give you IndexError, since there is no index 4 in the tuple x(python uses zero based indexing)
So you don't need to increment i separately as for increments the iterating variable upto the end of the sequence provided (in your case it's upto the last element in the tuple x)
And since i posses the element from the tuple itself, so by incrementing it you are incrementing the value in i. And then you are accessing the element with incremented index from the tuple. That is, if tuple element is 3, you are first incrementing it by 1, so it becomes 4, and then you are checking whether the element in 4th index of the tuple is even or not i.e. if x[4] is even or not. But since the tuple that I have considered, is of lengthe 4, so it will throw IndexError
So, here you don't need to initialize i = -1, don't need to use while, no need to increment i inside for loop. Rest solution is given by Selcuk.

Python: Unable to check for duplicates using list indexes

For some reason the same method is working for one function and not the other. The function that works already is defined as the following:
def is_unique1(lst):
for item in lst:
current_index = lst.index(item)
if lst[current_index] in lst[current_index + 1:-1]:
return False
return True
You pass in a list and checks the uniqueness of it, if it is unique then return TRUE if not return FALSE. However I am then asked to create another function, except this time copy the list and sort it. Then iterate over every index for values in the list to check whether the value at that index is equal to the value at the next higher index. But it keeps returning TRUE no matter what input. The function looks like this:
def is_unique2 ( myList ):
list_copy = list(myList)
list_copy.sort()
print(list_copy)
for item in list_copy:
index = list_copy.index(item)
if list_copy[index] in list_copy[index + 1: -1]:
return False
return True
Why is this happening. Am I using the slice incorrectly. I am checking if the current value at list_copy[index] is in the index + 1. I am testing it like so:
print('\nTesting is_unique2')
print (is_unique2(['raisin', 'apricot', 'celery', 'carrot']) )
print (is_unique2(['raisin', 'apricot', 'raisin', 'carrot']) )
Your bug is that by checking if list_copy[index] in list_copy[index + 1: -1] you're not checking the very last item in the list.
Remember, in Python, it's always "upper bound excluded": so somelist[a:b] will span somelist[a] included to somelist[b] excluded... for any a and b.
Easy fix: use in list_copy[index + 1:]. IOW, give no upper bound for the slice, so the slice will run all the way to the end of the list.
Incidentally, your approach is very dubious if the list's items are hashable -- in which case,
def is_unique3(myList):
return len(myList) == len(set(myList))
will be much faster and much less bug-prone too:-)
Try this line instead of your -1 indexed list for unique1:
lst[current_index] in lst[current_index + 1:None:-1]:

How to change index of a for loop?

Suppose I have a for loop:
for i in range(1,10):
if i is 5:
i = 7
I want to change i if it meets certain condition. I tried this but didn't work.
How do I go about it?
For your particular example, this will work:
for i in range(1, 10):
if i in (5, 6):
continue
However, you would probably be better off with a while loop:
i = 1
while i < 10:
if i == 5:
i = 7
# other code
i += 1
A for loop assigns a variable (in this case i) to the next element in the list/iterable at the start of each iteration. This means that no matter what you do inside the loop, i will become the next element. The while loop has no such restriction.
A little more background on why the loop in the question does not work as expected.
A loop
for i in iterable:
# some code with i
is basically a shorthand for
iterator = iter(iterable)
while True:
try:
i = next(iterator)
except StopIteration:
break
# some code with i
So the for loop extracts values from an iterator constructed from the iterable one by one and automatically recognizes when that iterator is exhausted and stops.
As you can see, in each iteration of the while loop i is reassigned, therefore the value of i will be overridden regardless of any other reassignments you issue in the # some code with i part.
For this reason, for loops in Python are not suited for permanent changes to the loop variable and you should resort to a while loop instead, as has already been demonstrated in Volatility's answer.
This concept is not unusual in the C world, but should be avoided if possible.
Nonetheless, this is how I implemented it, in a way that I felt was clear what was happening. Then you can put your logic for skipping forward in the index anywhere inside the loop, and a reader will know to pay attention to the skip variable, whereas embedding an i=7 somewhere deep can easily be missed:
skip = 0
for i in range(1,10):
if skip:
skip -= 1
continue
if i=5:
skip = 2
<other stuff>
Simple idea is that i takes a value after every iteration irregardless of what it is assigned to inside the loop because the loop increments the iterating variable at the end of the iteration and since the value of i is declared inside the loop, it is simply overwritten. You'd probably wanna assign i to another variable and alter it. For e.g,
for i in range(1,10):
if i == 5:
u = 7
and then you can proceed to break the loop using 'break' inside the loop to prevent further iteration since it met the required condition.
Just as timgeb explained, the index you used was assigned a new value at the beginning of the for loop each time, the way that I found to work is to use another index.
For example, this is your original code:
for i in range(1,10):
if i is 5:
i = 7
you can use this one instead:
i = 1
j = i
for i in range(1,10):
i = j
j += 1
if i == 5:
j = 7
also, if you are modifying elements in a list in the for loop, you might also need to update the range to range(len(list)) at the end of each loop if you added or removed elements inside it. The way I do it is like, assigning another index to keep track of it.
list1 = [5,10,15,20,25,30,35,40,45,50]
i = 0
j = i
k = range(len(list1))
for i in k:
i = j
j += 1
if i == 5:
j = 7
if list1[i] == 20:
list1.append(int(100))
# suppose you remove or add some elements in the list at here,
# so now the length of the list has changed
k = range(len(list1))
# we use the range function to update the length of the list again at here
# and save it in the variable k
But well, it would still be more convenient to just use the while loop instead.
Anyway, I hope this helps.

How to check if end of list was reached?

If have a list, say a=[1,2,3], and I want to see if a[4] is null, is there a way to do that, without using an exception or assertion?
len will tell you the length of the list. To quote the docs:
len(s)
    Return the length (the number of items) of an object. The argument may be a sequence     (string, tuple or list) or a mapping (dictionary).
Of course, if you want to get the final element in a list, tuple, or string, since indexes are 0 based, and the length of an item is the element count, a[len(a)-1] will be the last item.
As an aside, generally, the proper way to access the last element in an object which allows numeric indexing (str, list, tuple, etc) is using a[-1]. Obviously, that does not involve len though.
with a = [1,2,3] :
a[2:3] is [3]
a[3:4] is [ ]
So a[i:i+1] != [ ] tells if is an index of a
a[i:] does the same, but a[i:] creates another list, possible very long, while a[i:i+1] is 1 element if not empty
Here is an approach which I applied in one of the Arcade Challenges from Code Fights.
Basically, the end of a list is defined by:
list length - current index (iteration) == 1
#!/usr/bin/python3
numbers = [1, 3, 5, 8, 10, 13, 16]
list_len = len(numbers)
for n in numbers:
current_idx = numbers.index(n)
print("Current Number:", numbers[current_idx])
list_end = list_len - current_idx
if list_end != 1:
next_idx = current_idx + 1
print("Next Number: ", numbers[next_idx])
else:
print("End Of List!")
Use len
if len(a) <= index:
...
Note: Your question asks how you would find out "if a[4] is null". a[4] isn't anything, which is why you get an IndexError when you try to check it.
a[4] in this case will throw a IndexError exception, which isn't the same as comparing the value of a at index 4 to None. You can have values of None in a list, and if you were to compare values of a, then when you encounter a None, it doesn't mean that the index is not found in the list. For example:
>>> a=[1,None,2]
>>> a[1]==None
True
>>> a[3]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
Since lists are contiguous and indexed sequentially, the correct way to check if an index is in a list is to compare it to the len() of a list, but depending on the application, there are other ways around it, like catching an IndexError, or iteration.
>>> for index, value in enumerate(a):
... print index, value
...
0 1
1 None
2 2
You could write a function which behaves kind of like dict.get() does for dictionaries:
def listget(list_, index, default=None):
"""Return the item for index if index is in the range of the list_,
else default. If default is not given, it defaults to None, so that
this method never raises an IndexError."""
if index >= len(list_) or index < -len(list_):
return default
else:
return list_[index]
Example usage:
>>> names = ["Mark","Frank","James"]
>>> listget(names, 2)
'James'
>>> listget(names,-3)
'Mark'
>>> listget(names,3) # returns None
>>> listget(names,4,0)
0
So it will always return a value and you get no exceptions.
You're not providing a specific use-case, but generally for a list your would use len to see how many elements are in the list.
if len(a) > 3:
# Do something
The general way to check if you're currently looking at the element at the end of a list (in any language) is to compare the current index you're looking at with the length of the list minus one (since indexes start at 0).
a[4] isn't really anything, because it doesn't exist - some languages may implement that as being null (or undefined) but many will simply throw an exception if you try to access it instead.
Here is the logic statement I use to check whether the end of your list has been reached:
arr = [1,3,2,4,5]
#counter for the array
arr_counter = 0
for ele in array:
# check if end of list has been reached
if (arr_counter+1) != len(arr):
#put all your code here
pass
# increment the array counter
arr_counter += 1
Hope this helps ! :)
look here:
https://www.geeksforgeeks.org/python-how-to-get-the-last-element-of-list/
test_list = [1, 4, 5, 6, 3, 5]
# printing original list
print ("The original list is : " + str(test_list))
# First naive method
# using loop method to print last element
for i in range(0, len(test_list)):
if i == (len(test_list)-1):
print ("The last element of list using loop : "+ str(test_list[i]))
# Second naive method
# using reverse method to print last element
test_list.reverse() `enter code here`
print("The last element of list using reverse : "+ str(test_list[0]))

Categories

Resources