This is a very tricky problem to explain. I was playing around with Python's list methods, particularly the del index object. I wanted to create a simple script that would create a list of integers from 1 to 100, and then a for loop which would delete the odd numbers from the list.
Here is the script I wrote:
def main():
num = list(range(1,101))
print(num)
for i in range(0,101):
del num[i]
print(num)
main()
Seems like it would work right? I thought so too, until I ran it.
I am not sure why, but when i was passed to the del num[i] index, the number itself doubled.
When I ran it, I received IndexError: list assignment index out of range.
When I changed the parameters from range(0,101) to range(0,10), I discovered that it deleted all the odd numbers from 1 to 20.
In other words, i in the index is doubling when it shouldn't. Can I get some information about this?
The size of the list is reduced when you delete items. After about 50 loop iterations, you have about 50 items in the list, so the nest iteration tries to delete something outside the list.
Here's a simulated run:
>>> a = [1, 2, 3, 4, 5]
>>> a
[1, 2, 3, 4, 5]
>>> del a[0]
>>> a
[2, 3, 4, 5]
>>> del a[1]
>>> a
[2, 4, 5]
>>> del a[2]
>>> a
[2, 4]
>>> del a[3]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list assignment index out of range
When you use the del keyword inside of your for loop, it's completely removing the item from the original num list you made. By doing so, the length of your list is getting smaller and smaller with each iteration through the loop.
This also explains why it's only removing the odd numbers, since the indexes are shifting down after each delete, allowing the even numbers to slip through the cracks of your program:
num = range(1, 5) # num = [1, 2, 3, 4]
del num[0] # results in num = [2, 3, 4]
del num [1] # deletes 3, and num now = [2, 4]
etc. for a longer loop
To delete the odd numbers, consider using a conditional to check the %2 == 0 status and the list.remove() method:
num = range(1, 101)
for i in num:
if i % 2 != 0:
num.remove(i)
print(num)
Or a list comprehension inside your main() function:
return [x for x in num if x % 2 == 0]
Your iterate through the range from 0 to 101, and delete one item from num on each iteration, so the length of the num is reducing and you finally get an IndexError.
This problem is verymuch similar to mutating a list while iterating over the same list, go through below link which helps you to understand this situation better.
http://gsb-eng.com/why-python-list-mutation-is-not-a-good-idea/
for the second for loop, you should use half of the total indices, because at the 50th iteration, 50 elements would be removed, so the total index of the list is reduced to 50 , hence at the 51st iteration , it displays an out of range error.
Related
image
please refer to the image.
I want to create a list(say [1,2,3,4,5]). The code checks the divisibility by 2 of every element of the list one by one. if it is divisible by 2, it removes the those elements and returns a list of odd numbers . Please try to keep the code as simple as possible as i am just a beginner....
The problem with your code is that once you remove the even number from the list, It reduces the length of the list. So in the second iteration of your code, 2 is removed from the list l = [1,2,3,4,5] which modifies the list l to [1,3,4,5], so now, the length of the list is 4 instead of 5. But the for loop, which will run for the values "0, 1, 2, 3, 4" (because of variable x, which is 5), your code produces that error.
To solve this error, you can create a new list and append the even numbers in it.
It's a lot easier and more Pythonic to build a new list of the odd numbers than it is to modify the original list to remove the even numbers.
If you understand list comprehensions, there's a simple answer.
l = [1, 2, 3, 4, 5]
l2 = [x for x in l if x%2 == 1]
print(l2)
You can also do it imperatively:
l = [1, 2, 3, 4, 5]
l2 = []
for x in l:
if x%2 == 1:
l2.append(x)
print(l2)
Either solution will print [1, 3, 5].
l = [1,2,3,4,5]
x = len(l)
new_l = []
for a in range(x):
if l[a]%2!=0:
new_l.append(l[a])
new_l
Use the above code instead of removing elements from list create a new one.
I took a look at the image and couldn't figure out much. Going by your definition
l = [1,2,3,4]
for i in l:
if i % 2 == 0: # check if i is divisible by 2
l.remove(i) # remove the item if the number passes above check.
I've got a question regarding how to improve something small I wrote. I'm fairly new to Python and I tried to make this with a for loop but failed, I was wondering if there was a more efficient way to write this function(I wanted to do it with a for loop but if u guys have a better idea I'd appreciate if you posted it). Thanks!
Btw, the list recieved is only numbers, there aren't any strings, chars, etc.
def newList(n1,n2,n3,List):
avg = (n1+n2+n3)/3
i=0
while i<len(List):
List[i] -= avg
i += 1
print D
Python’s list comprehensions are useful when you want to perform the same operation on every item in a list. For example, suppose you had a list of numbers defined as follows:
list_of_numbers = [1, 2, 3, 4, 5]
To add 5 to every number in this list and put it into a variable called new_list, you’d use a list comprehension:
new_list = [number + 5 for number in list_of_numbers]
If you check the value of new_list, you’ll see it is:
[6, 7, 8, 9, 10]
You can use a list comprehension in your newList() method:
def newList(n1, n2, n3, list):
average = (n1 + n2 + n3) / 3
result_list = [item - average for item in list]
return result_list
When you call it like so:
newList(1, 2, 3, [4, 5, 6])
you’ll get this result:
[2, 3, 4]
use comprehensive list such as :
def newList(n1,n2,n3,List):
avg = (n1+n2+n3)/3
print [i-avg for i in List]
To explain List comprehension using this example :
let's explain 'for i in List'
It's a For loop meaning, for each element of 'List' where 'i' will be
equal to this element.
List = [1,2,3]
First loop : i = 1, Second loop i=2 and third loop i=3
then part 'i-avg' means for each loop, return value of i-avg
Let say 'avg = 1'
Fist loop : (i=1) 1-1 (i-avg) is returned; 0
Second loop : (i=2) 2-1 is returned; 1
Third loop : (i=3) 3-1 is returned;2
I'm looking to break down the reverse() function and write it out in code for practice. I eventually figured out how to do it (step thru the original list backwards and append to the new 'reversed' list) but wondering why this doesn't work.
def reverse(list):
newlist = []
index = 0
while index < len(list):
newlist[index] = list[(len(list)) - 1 - index]
index = index + 1
return newlist
list = [1, 2, 3, 4, 5]
print(reverse(list))
In Python, you cannot access/update an element of a list, if the index is not in the range of 0 and length of the list - 1.
In your case, you are trying to assign to element at 0, but the list is empty. So, it doesn't have index 0. That is why it fails with the error,
IndexError: list assignment index out of range
Instead, you can use append function, like this
newlist.append(list[(len(list)) - 1 - index])
Apart from that, you can use range function to count backwards like this
for index in range(len(list) - 1, -1, -1):
newlist.append(list[index])
you don't even have to increment the index yourself, for loop takes care of it.
As suggested by #abarnert, you can actually iterate the list and add the elements at the beginning every time, like this
>>> def reverse(mylist):
... result = []
... for item in mylist:
... result.insert(0, item)
... return result
...
>>> reverse([1, 2, 3, 4, 5])
[5, 4, 3, 2, 1]
If you want to create a new reversed list, you may not have to write a function on your own, instead you can use the slicing notation to create a new reversed list, like this
>>> mylist = [1, 2, 3, 4, 5]
>>> mylist[::-1]
[5, 4, 3, 2, 1]
but this doesn't change the original object.
>>> mylist = [1, 2, 3, 4, 5]
>>> mylist[::-1]
[5, 4, 3, 2, 1]
>>> mylist
[1, 2, 3, 4, 5]
if you want to change the original object, just assign the slice back to the slice of the original object, like this
>>> mylist
[1, 2, 3, 4, 5]
>>> mylist[:] = mylist[::-1]
>>> mylist
[5, 4, 3, 2, 1]
Note: reversed actually returns a reverse iterator object, not a list. So, it doesn't build the entire list reversed. Instead it returns elements one by one when iterated with next protocol.
>>> reversed([1, 2, 3, 4, 5])
<list_reverseiterator object at 0x7fdc118ba978>
>>> for item in reversed([1, 2, 3, 4, 5]):
... print(item)
...
...
5
4
3
2
1
So, you might want to make it a generator function, like this
>>> def reverse(mylist):
... for index in range(len(mylist) - 1, -1, -1):
... yield mylist[index]
...
...
>>> reverse([1, 2, 3, 4, 5])
<generator object reverse at 0x7fdc118f99d8>
So the reverse function returns a generator object. If you want a list, then you can create one with list function, like this
>>> list(reverse([1, 2, 3, 4, 5]))
[5, 4, 3, 2, 1]
if you are just going to process it one by one, then iterate it with a for loop, like this
>>> for i in reverse([1, 2, 3, 4, 5]):
... print(i)
...
...
5
4
3
2
1
First off don't override build-ins (list in your case) second newlist has a len of 0 therefore cannot be accessed by index.
def reverse(mylist):
newlist = [0] * len(mylist)
index = 0
while index < len(mylist):
newlist[index] = mylist[(len(mylist)) - 1 - index]
index = index + 1
return newlist
mylist = [1, 2, 3, 4, 5]
print(reverse(mylist))
you can create a list with values of the same lenght as your input list like so
newlist = [0] * len(mylist)
You need to use list.append. newlist[0] is a valid operation, if the list has atleast one element in it, but newlist is empty in this very first iteration. Also, list is not a good name for a variable, as there is a python builtin container with the same name:
def reverse(lst):
newlist = []
index = 0
while index < len(lst):
newlist.append(lst[(len(list)) - 1 - index])
index += 1
return newlist
list = [1, 2, 3, 4, 5]
print(reverse(list))
You can't assign to an arbitrary index for a 0-length list. Doing so raises an IndexError. Since you're assigning the elements in order, you can just do an append instead of an assignment to an index:
newlist.append(l[(len(l)) - 1 - index])
Append modifies the list and increases its length automatically.
Another way to get your original code to work would be to change the initialization of newlist so that it has sufficient length to support your index operations:
newlist = [None for _ in range(len(l))]
I would also like to note that it's not a good idea to name things after built-in types and functions. Doing so shadows the functionality of the built-ins.
To write the function you're trying to write, see thefourtheye's answer.
But that isn't how reverse works, or what it does. Instead of creating a new list, it modifies the existing list in-place.
If you think about it, that's pretty easy: just go through half the indices, for each index N, swap the Nth from the left and the Nth from the right.*
So, sticking with your existing framework:
def reverse(lst):
index = 0
while index < len(lst)/2:
lst[index], lst[len(lst) - 1 - index] = lst[len(lst) - 1 - index], lst[index]
index = index + 1
As a side note, using while loops like this is almost always a bad idea. If you want to loop over a range of numbers, just use for index in range(len(lst)):. Besides reducing three lines of code to one and making it more obvious what you're doing, it removes multiple places where you could make a simple but painful-to-debug mistake.
Also, note that in most cases, in Python, it's easier to use a negative index to mean "from the right edge" than to do the math yourself, and again it will usually remove a possible place you could easily make a painful mistake. But in this particular case, it might not actually be any less error-prone…
* You do have to make sure you think through the edge cases. It doesn't matter whether for odd lists you swap the middle element with itself or not, but just make sure you don't round the wrong way and go one element too far or too short. Which is a great opportunity to learn about how to write good unit tests…
probably check this out:
def reverse(lst):
newList = []
countList = len(lst) - 1
for x in range(countList,-1,-1):
newList.append(lst[x])
return newList
def main():
lst = [9,8,7,6,5,4,2]
print(reverse(lst))
main()
I'm trying to sort a list from smallest to biggest integers. Unfortunately I get the error stated above when I try to run it.
Traceback (most recent call last):
File "lesson_4/selection_sort.py", line 24, in <module>
print selection_sort([-8, 8, 4, -4, -2, 2]) # [-8, -4, -2, 2, 4, 8]
File "lesson_4/selection_sort.py", line 14, in selection_sort
lst.remove(min)
ValueError: list.remove(x): x not in list
Here is the code of selection_sort.py
def selection_sort(lst):
sorted = []
list_len = len(lst) # Store this now because our loop will make it
# smaller
min = lst[0]
i = 1
while list_len > 0:
while i < list_len:
item = lst[i]
if item < min:
min = item
i += 1
lst.remove(min)
sorted.append(min)
return sorted
# Test Code
print "Testing"
print selection_sort([-8, 8, 4, -4, -2, 2]) # [-8, -4, -2, 2, 4, 8]
Thank for helping me out!
On your first pass through the list, you find the minimum element. However, on your second pass, min is still set to the minimum element in the original list. As a result, item < min is never true, and min forever remains the minimum element of the original list. Then when you try to remove it, you can't, because you already got rid of that item on the previous pass (unless there is a tie for the minimum, in which case this will happen as soon as all those elements are removed).
To solve this, just move min = lst[0] inside the first loop, so you reset it to a valid value each time.
You've also got some other issues, which I will mention here briefly:
You never update list_len, so you'll get an error at the end of the second pass through the outer loop (when you will attempt to go beyond the length of the list). You'd also loop forever if it didn't break bist. Luckily this whole variable is unneeded: you can use len(lst) in the outer loop, and replace your inner while loop with this:
for item in lst: # But see below regarding variable names!
if item < min:
min = item
This eliminates the need to track i separately and avoids any issues with the length of the list.
Next: this looks like homework, so it's probably not critical at this moment, but it's definitely worth mentioning: if I pass a list to a function called selection_sort, I would be very surprised to discover that after being sorted, my original list is now empty!! It's generally bad form to modify an input unless you're doing so explicitly (e.g. an in-place sort), so I highly recommend that you do all your work on a copy of the input, to avoid deleting all the content of the original:
lst_copy = lst[:] # If `lst` contains mutable objects (e.g. other lists), use deepcopy instead!
# Do stuff with lst_copy and avoid modifying lst
Finally, you've got two variables shadowing built-in functions: sorted and min. While this will technically work, it's poor form, and it's best to get into the habit of not naming local variables the same as builtins. By convention, if it's really the best name for the object, you can just add an underscore to the name to distinguish it from the builtin: min_ and sorted_ (or maybe better, output), for example.
If you simply want to sort the list, you can use inbuilt sort() function:
>>> lst=[-8, 8, 4, -4, -2, 2]
>>> lst.sort()
>>> lst
[-8, -4, -2, 2, 4, 8]
If you want to sort by your method, there are two slight mistakes in your code: you need to decrement lst_len every time you remove an element and reinitialize min to lst[0]. Also outer while should be while lst_len > 1 because list of length 1 is trivially sorted. Demo is given below:
>>> def selection_sort(lst):
sorted = []
list_len = len(lst) # Store this now because our loop will make it
# smaller
min = lst[0]
i = 1
while list_len > 1:
while i < list_len:
item = lst[i]
if item < min:
min = item
i += 1
lst.remove(min)
list_len-=1 # decrement length of list
min=lst[0] # reinitialize min
sorted.append(min)
return sorted
>>> selection_sort([-8, 8, 4, -4, -2, 2])
[8, 4, -4, -2, 2]
I wrote some code in which the forloop index varies over a range depending on the length of a list, and the length of the list is changeable inside the loop. Here is a simplified code:
>>> ml = [1,2,3]
>>> for i in range(0, len(ml)):
... if ml[i] == 2:
... ml.pop(i)
... i
...
0
2
1
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
IndexError: list index out of range
I guess that len(ml) is evaluated once at the first iteration of the forloop, and not in the following iterations, so the index goes out of range.
But could it be possible to update the index range along the iterations? Thanks.
new_list = [x for x in my_list if x != 2]
is much better or
new_list = filter(lambda x:x!=2,my_list)
you should not really remove elements while iterating through a list unless you have a very very very good (so good I have never heard of it) reason
if you want the elements to replace the current list
my_list[:] = [x for x in my_list if x != 2]
or you can simply overwrite it and let pythons very good garbage collector delete the old one
my_list = [x for x in my_list if x != 2]
range(0, len(ml)) is evaluated once, and generates the list of values for i.
You can simply replace the for loop by a while loop:
i = 0
while i < len(ml):
if ml[i] == 2:
ml.pop(i)
continue
i+=1
This behavior makes sense because you're actualy not iterating through the list, but rather the range:
ml = [1, 2, 3]
for i in range(0, len(ml)):
print(i)
Give the following output:
0
1
2
Using for item in ml: instead actually iterates through the list:
ml = [1, 2, 3]
for i in ml:
if i == 2:
ml.pop(i)
print(i)
Result:
1
2
In response to comments here's a better idea of what's happening:
ml = [1, 2, 3, 4, 5, 6, 7]
for i in ml:
if i == 2:
ml.pop(1) # pop second element in list
print(i)
gives the following output:
1
2
4
5
6
7
Using a while loop gives you the same result:
ml = [1, 2, 3, 4, 5, 6, 7]
i = 0
while i < len(ml):
print(ml[i])
if ml[i] == 2:
ml.pop(i)
i+=1
1
2
4
5
6
7
This means that you can pop elements, however you cannot gaurentee that all the elements are correctly processed.