Python: weird list index out of range error [duplicate] - python

This question already has answers here:
Strange result when removing item from a list while iterating over it
(8 answers)
Closed 7 years ago.
l = range(100)
for i in l:
print i,
print l.pop(0),
print l.pop(0)
The above python code gives the output quite different from expected. I want to loop over items so that I can skip an item while looping.
Please explain.

Never alter the container you're looping on, because iterators on that container are not going to be informed of your alterations and, as you've noticed, that's quite likely to produce a very different loop and/or an incorrect one. In normal cases, looping on a copy of the container helps, but in your case it's clear that you don't want that, as the container will be empty after 50 legs of the loop and if you then try popping again you'll get an exception.
What's anything BUT clear is, what behavior are you trying to achieve, if any?! Maybe you can express your desires with a while...?
i = 0
while i < len(some_list):
print i,
print some_list.pop(0),
print some_list.pop(0)

I've been bitten before by (someone else's) "clever" code that tries to modify a list while iterating over it. I resolved that I would never do it under any circumstance.
You can use the slice operator mylist[::3] to skip across to every third item in your list.
mylist = [i for i in range(100)]
for i in mylist[::3]:
print(i)
Other points about my example relate to new syntax in python 3.0.
I use a list comprehension to define mylist because it works in Python 3.0 (see below)
print is a function in python 3.0
Python 3.0 range() now behaves like xrange() used to behave, except it works with values of arbitrary size. The latter no longer exists.

The general rule of thumb is that you don't modify a collection/array/list while iterating over it.
Use a secondary list to store the items you want to act upon and execute that logic in a loop after your initial loop.

Use a while loop that checks for the truthfulness of the array:
while array:
value = array.pop(0)
# do some calculation here
And it should do it without any errors or funny behaviour.

Try this. It avoids mutating a thing you're iterating across, which is generally a code smell.
for i in xrange(0, 100, 3):
print i
See xrange.

I guess this is what you want:
l = range(100)
index = 0
for i in l:
print i,
try:
print l.pop(index+1),
print l.pop(index+1)
except IndexError:
pass
index += 1
It is quite handy to code when the number of item to be popped is a run time decision.
But it runs with very a bad efficiency and the code is hard to maintain.

This slice syntax makes a copy of the list and does what you want:
l = range(100)
for i in l[:]:
print i,
print l.pop(0),
print l.pop(0)

Related

Is there a python 3 for loop one-liner that doesn't produce a list? [duplicate]

This question already has answers here:
Pythonic way to cycle through purely side-effect-based comprehension
(6 answers)
Closed 1 year ago.
I'm brand new to python 3 & my google searches have been unproductive. Is there a way to write this:
for x in range(10):
print(x)
as this:
print(x) for x in range(10)
I do not want to return a list as the arr = [x for x in X] list comprehension syntax does.
EDIT: I'm not actually in that specific case involving print(), I'm interested in a generic pythonic syntactical construction for
method(element) for element in list
No, there isn't. Unless you consider this a one liner:
for x in range(6): print(x)
but there's no reason to do that.
Looks like you are looking for something like map. If the function you are calling returns None, then it won't be too expensive.
Map will return an iterator, so you are just trying to consume it.
One way is:
list(map(print, range(6)))
Or using a zero length deque if you don't want the actual list elements stored.
from collections import deque
deque(map(print, range(6)), maxlen=0)
For your specific case to print a range of 6:
print(*range(6), sep='\n')
This is not a for loop however #Boris is correct.
What you are looking for is a generator expression that returns a generator object.
print(i for i in range(10)) #<generator object <genexpr> at 0x7f3a5baacdb0>
To see the values
print(*(i for i in range(10))) # 0 1 2 3 4 5 6 7 8 9
Pros:
Extremely Memory Efficient.
Lazy Evaluation - generates next element only on demand.
Cons:
Can be iterated over till the stop iteration is hit, after that one cannot reiterate it.
Cannot be indexed like a list.
Hope this helps!

When iterating over a list using a for loop, why does "for x in range(len(spam)) work but not "for x in spam"?

Coming from JS, looping over a list using a for loop does work as I would expect.
spam = ['bat', 'nap', 'hat']
for x in spam:
print(spam[x])
Prints the error
File "", line 2, in
print(spam[x])
TypeError: list indices must be integers or slices, not str
While
for x in range(len(spam)):
print(spam[x])
Prints out
bat
nap
hat
I understand now how to iterate over a list, but I am not totally sure on the logic of it. Why does
range(len(spam))
print the correct result? From the way I understand it, len(spam) is just returning an integer value (3) in this case, which then simplifies down to range(3)?
Maybe I just answered my own question - but is it because python now knows how many times to iterate over spam, with spam[x]?
When you have
spam = ['bat', 'nap', 'crap']
for x in spam:
print(spam[x])
...in the first iteration of the loop you're asking for print spam['bat'] which doesn't make any sense.
In the version that works, it's expanded to for x in range(3), in which the first iteration would be print spam[0] which is correct.
if you want the index, use enumerate, otherwise just print x in your original code.
for i, entry in enumerate(spam):
print spam[i]
# or just "print entry"
try this, and things will become clear:
for x in spam:
print(x)
For loops in python don't just work based on an index
for x in spam:
print(x)
The above will work because the for loops iterates over each element in spam. X is each element not the index of the element in spam.
Your second one works because range returns a list of indexes which you are using. The first method is the best one as it avoids the unneeded indexes.
If you need both you can use enumerate() to do this
for index, element in enumerate(spam):
#do stuff

I want to exclude all numbers that aren't divisible by 7 and that are not multiples of 5 in a range of numbers from (0,300)

basically I have to get a list of numbers that are divisible by 7 but not multiples of 5. but for some reason when i put the conditions it tells me i have error.
for i in [x for x in xrange(0,100) if x%7 ==0 and if x%5 !=0 ]:
print i
I know you posted something along the lines of a list comprehension but it's a bit hard to read. So a few things...
I would try writing this as a multi-line for loop before condensing it down to a list comprehension.
I'm not sure why you have an 'x' in here, and 'xrange' doesn't make sense.
Edit: Just realized why I don't recognize xrange and it's because I never worked with Python 2.x
So thinking through this, you are basically looking for any number from 0-300 that is divisible by 7 but is not a multiple of 5.
Means we have a few things...
range(0,301): Since range is not inclusive of our last value we want n+1
Our number, let's say "i" is both... "i%7==0" and "i%5!=0"
So let's look line-by-line
for i in range(0,301):
Okay cool, now you don't need a nested for loop list comprehension like you did in your example. Now, you need to know "if" i is ____... So we need an if statement.
if i%7==0 and i%5!=0:
See the logic? And of course that if statement is inside of our for loop to loop over all the values in our range.
Finally, if our "i" meets our criteria, then we can print all the values.
print(i)
So, our final code looks like...
for i in range(0,301):
if (i % 7 == 0) and (i % 5 != 0):
print(i)
Of course, there are ways you can make this more elegant, but this is the general idea.
List Comprehension:
party = [i for i in range(0,301) if i%7==0 and i%5!=0]
print(party)
That stores them all in a list so you can access them whenever. Or you can print it without assigning it of course.
Edit: The title and then what you say in the body are kind of conflicting. After reading over my own answer, I'm not completely sure if that's what you're looking for, but that is how it came across to me. Hope it helps!
Your list comprehension is incorrect. It should be something similar to:
[x for x in xrange(100) if x%5 and not x%7]
Even better (more efficient) will be something similar to
[x for x in xrange (7, 100, 7) if x%5]
Even better will be ... Nah, we'll just stop here for now.

Python 3 Error Missunderstanding (IndexError)

Here's my code
def abc(l,z):
L=[]
länge= len(L)
for x in range(0, länge+1):
L[x]+z
print(L)
abc(["1","2","3"],"-")
I want the program to output "1-2-3"
l in abc(l,z) should be a List out of Strings which combines "l" and "z" to a single String.
I'm getting an Index Error: list index out of range.
There are a couple of issues here.
First, range(0, länge+1) will stop at länge but your list only has indexes from 0 tolänge - 1, so that's one source for an IndexError.
Second, L[x]+z will give you another IndexError because L is empty but you try to access L[0] (and in the next iteration, where you don't get because of the error, L[1], L[2], and so on).
Third, even without an IndexError the statement L[x]+z will do nothing. You compute the value of L[x]+z but then you don't assign any variable to it, so it is immediately lost.
Fourth, in order to print your final result, put the call to print after the for loop, not inside. Consider using return instead of print if you actually want to do something with the result the function produces (make sure to educate yourself on the difference between print and return).
Fifth, you want to build a string, so the usage of the list L does not make much sense. Start with an empty string and add the current item from l and z to it in the loop body (coupled with a re-assignment in order to not lose the freshly computed value).
Lastly, there's no point in using range here. You can iterate over values in a list direclty by simply writing for x in l:.
That should give you enough to work with and fix your code for now.
(If you don't care why your function does not work and this is not an exercise, simply use str.join as suggested in the comments.)

python 2.7 for loop to generate a list

I have tested in Python 2.7, the two styles are the same. My confusion is, when reading first method to generate a list, I am always a bit confused if i%2 == 0 controls if we should execute the whole loop of i in range(100), or i%2 == 0 is under loop of i in range(100). I have the confusion maybe in the past I write Java and C++, thinking methods from there.
Looking for advice how to read list generation code, normally the pattern is [<something before loop> <the loop> <something after the loop>], in this case "something before loop" is 1, and "the loop" is for i in range(100) and "something after the loop" is i%2 == 0.
Also asking for advice if writing code in method 1 is good coding style in Python 2.7? Thanks.
a = [1 for i in range(100) if i%2 == 0]
print a
a=[]
for i in range(100):
if i%2==0:
a.append(1)
print a
Edit 1,
I also want to compare of using xrange in an explicit loop (compare to first method of list comprehension for pros and cons), for example,
a=[]
for i in xrange(100):
if i%2==0:
a.append(1)
print a
Edit 2,
a = [1 for i in xrange(100) if i%2 == 0]
1) as already mentioned in python 2.7 it is usually suggested to use xrange since it will (like in C) only keep a counter that will be incremented.
Instead the range is really creating in memory a whole list from 0 till 99!
Maybe here you have to think, if you need the 100 included --> then please use 101 ;)
2) You got my point, the question is valid and you have to think that operation will be executed indeed "under" the loop!!
Bearing in mind that the list comprehension is quite powerful in order to create the needful!! Anyway be careful that in some cases is not so easy to read especially when you are using inside multiple variable like x,y and so on.
I would chose your first line, just take care of min and max of your array. As said maybe you have to incorporate the 100th element and you can speed up using the xrange function instead of range.
a = [1 for i in range(100) if i%2 == 0]
3) A good suggestion is also to document yourself on xrange and while loop --> on stackoverflow you can find plenty of discussions looking for the speed of the two mentioned operation!! (This is only suggestion)
Hope this clarify your query! Have a nice day!

Categories