unable to delete all element satisfying condition in a python list using enumerate - python-3.x

i am trying to delete zero values from the list using the below code
for id,row in enumerate(list):
if row[0]=="0":
del list(id)
this works fine for input like
[0,1,3,0,9,10,0,3,0,6]
but doesn't work as expected for inputs like
[0,0,1,3,4,0,0,4,5,6,0,0].
output: [0,1,3,4,0,4,5,6,0]
I guess its because the element right after the deleted one gets the id of the deleted element and enumerate increments the id which leaves the element after the one which is deleted unchecked.
so what can be done to check all the elements ? is there a better way ?

I made a little change to your code:
mylist = [0,0,1,3,4,0,0,4,5,6,0,0]
for id,row in reversed(list(enumerate(mylist))):
if(row==0):
del mylist[id]
If you loop your list in the way you did (from start to end) and delete an element while doing it, you'll end up jumping indexes because python does not recognize that an element has been deleted from the list.
If you have an array with 10 elements inside and you delete the first (idx 0), in the next iteration you will be at index 1, but the array has been modified, so your idx 1 is the idx 2 of your array before the deletion, and the real idx 1 will be skipped.
So you just need to loop your array in reverse mode, and you won't miss indexes.
If you print the value of mylist, you'll get [1, 3, 4, 4, 5, 6].

This problem is documented on this python page under 8.3:
https://docs.python.org/3/reference/compound_stmts.html
They suggest doing it this way by using a slice. It works for me:
a = [-2,-4,3,4]
for x in a[:]:
if x < 0: a.remove(x)
print ('contents of a now: ')
print(*a)

enumerate returns an object called enumerate object and it is iterable not actually a list. second thing row is not a list it is not subscriptable.
for i,row in enumerate(l):
if row==0:
del(l[i])
you will not get result you want this way.
try this:
t=[] #a temporary list
for i in l:
if i!=0:
t.append(i)
t will contain sublist of l with non zero elements.
put the above inside a function and return the list t .

Related

comparing int value in list throws index out of range Error

I'm struggling to grasp the problem here. I already tried everything but the issue persist.
Basically I have a list of random numbers and when I try to compare the vaalues inside loop it throws "IndexError: list index out of range"
I even tried with range(len(who) and len(who) . Same thing. When put 0 instead "currentskill" which is int variable it works. What I don't understand is why comparing both values throws this Error. It just doesn't make sence...
Am I not comparing a value but the index itself ???
EDIT: I even tried with print(i) / print(who[i] to see if everything is clean and where it stops, and I'm definitelly not going outside of index
who = [2, 0, 1]
currentskill = 1
for i in who:
if who[i] == currentskill: # IndexError: list index out of range
who.pop(i)
The problem is once you start popping out elements list size varies
For eg take a list of size 6
But you iterate over all indices up to len(l)-1 = 6-1 = 5 and the index 5 does not exist in the list after removing elements in a previous iteration.
solution for this problem,
l = [x for x in l if x]
Here x is a condition you want to implement on the element of the list which you are iterating.
As stated by #Hemesh
The problem is once you start popping out elements list size varies
Problem solved. I'm just popping the element outside the loop now and it works:
def deleteskill(who, currentskill):
temp = 0
for i in range(len(who)):
if who[i] == currentskill:
temp = i
who.pop(temp)
There are two problems in your code:
mixing up the values and indexes, as pointed out by another answer; and
modifying the list while iterating over it
The solution depends on whether you want to remove just one item, or potentially multiple.
For removing just one item:
for idx, i in enumerate(who)::
if i == currentskill:
who.pop(idx)
break
For removing multiple items:
to_remove = []
for idx, i in enumerate(who)::
if i == currentskill:
to_remove.append[idx]
for idx in reversed(to_remove):
who.pop(idx)
Depending on the situation, it may be easier to create a new list instead:
who = [i for i in who if i != currentskill]
Your logic is wrong. To get the index as well as the value, use the built-in function enumerate:
idx_to_remove = []
for idx, i in enumerate(who)::
if i == currentskill:
idx_to_remove.append[idx]
for idx in reversed(idx_to_remove):
who.pop(idx)
Edited after suggestion from #sabik

The best way of iterating through an array whose length changes in Python

I am implementing an algorithm which might affect the size of some array, and I need to iterate through the entire array. Basically a 'for x in arrayname' would not work because it does not update if the contents of arrayname are changed in the loop. I came up with an ugly solution which is shown in the following example:
test = np.array([1,2,3])
N = len(test)
ii=0
while ii < N:
N = len(test)
print(test[ii])
if test[ii] ==2:
test = np.append(test,4)
ii+=1
I am wondering whether a cleaner solution exists.
Thanks in advance!
Assuming all the elements are going to be added at the end and no elements are being deleted you could store the new elements in a separate list:
master_list = [1,2,3]
curr_elems = master_list
while len(curr_elems) > 0: # keep looping over new elements added
new_elems = []
for item in curr_elems: # loop over the current list of elements, initially the list but then all the added elements on second run etc
if should_add_element(item):
new_elems.append(generate_new_element(item))
master_list.extend(new_elems) # add all the new elements to our master list
curr_elems = new_elems # and prep to iterate over the new elements for next iteration of the while loop
The while loop seems the best solution. As the condition is re-evaluated at each iteration, you don’t need to reset the length of the list in the loop, you can do it inside the condition:
import random
l = [1, 2, 3, 4, 5]
i = 0
while i < len(l):
if random.choice([True, False]):
del l[i]
else:
i += 1
print(f'{l=}')
This example gives a blueprint for a more complex algorithm. Of course, in this simple case, it could be coded more simply with a filter, or like this:
l = [1, 2, 3, 4, 5]
[x for x in l if random.choice([True, False])]
You might want to check this related post for more creative solutions: How to remove items from a list while iterating?

If condition working differently for same value in python

I am trying to write a function which will return True or False if the given number is not greater than 2.
So simple, but the if condition is returning different outputs for same value '2'. The code I used is:
The code I used is:
ele_list = [1,2,3,2]
for i in ele_list:
if not i>2:
print(i,False)
ele_list.remove(i)
print(ele_list)
The ouput I am receiving is:
1 False
[2, 3, 2]
2 False
[3, 2]
I am confused to see that the first 2 in the list is passing through the if condition but the second 2 in the list is not passing through the condition. Please help me figure out this..
Removing elements from the list you're looping over is generally a bad idea.
What's happening here is that when you're removing an element, you're changing the length of the array, and therefor changing what elements are located at what indexes as well as changing the "goal" of the forloop.
Lets have a look at the following example:
ele_list = [4,3,2,1]
for elem in ele_list:
print(elem)
ele_list.remove(elem)
In the first iteration of the loop elem is the value 4 which is located at index 0. Then you're removing from the array the first value equal to elem. In other words the value 4 at index 0 is now removed. This shifts which element is stored at what index. Before the removal ele_list[0] would be equal to 4, however after the removal ele_list[0] will equal 3, since 3 is the value that prior to the removal was stored at index 1.
Now when the loop continues to the second iteration the index that the loop "looks at" is incremented by 1. So the variable elem will now be the value of ele_list[1] which in the updated list (after the removal of the value 4 in the previous iteration) is equal to 2. Then you're (same as before) removing the value at index 1 from the list, so now the length of the list just 2 elements.
When the loops is about to start the third iteration it checks to see if the new index (in this case 2) is smaller than the length of the list. Which its not, since 2 is not smaller than 2. So the loop ends.
The simplest solutions is to create a new copy of the array and loop over the copy instead. This can easily be done using the slice syntax: ele_list[:]
ele_list = [1,2,3,2]
for elem in ele_list[:]:
if not elem > 2:
print(elem, False)
ele_list.remove(elem)
print(ele_list)
the problem is that you're modifying your list as you're iterating over it, as mentioned in #Olian04's answer.
it sounds like what you really want to do, however, is only keep values that are > 2. this is really easy using a list comprehension:
filtereds_vals = [v for v in ele_list if v > 2]
if you merely want a function that gives you True for numbers greater than 2 and False for others, you can do something like this:
def gt_2(lst):
return [v > 2 for v in lst]
or, finally, if you want to find out if any of the values is > 2 just do:
def any_gt_2(lst):
return any(v > 2 for v in lst)
I think the problem here is how the remove function interacts with the for function.
See the documentation, read the "note" part:
https://docs.python.org/3.7/reference/compound_stmts.html?highlight=while#grammar-token-for-stmt
This can lead to nasty bugs that can be avoided by making a temporary copy using a slice of the whole sequence
A possible solution, as suggested into the documentation:
ele_list = [1,2,3,2]
for i in ele_list[:]:
if not i>2:
print(i,False)
ele_list.remove(i)
print(ele_list)
"""
1 False
[2, 3, 2]
2 False
[3, 2]
2 False
[3]
"""

How can I remove non-unique elements from a list entered in through the input function?

Currently working through the springboard data science career track admissions test and one of the questions I got asked was to removes all on non-duplicates from a list of numbers entered via a one line of standard input separated by a space, and return a list of the the duplicates only.
def non_unique_numbers(line):
for i in line:
if line.count(i) < 2:
line.remove(i)
return line
lin = input('go on then')
line = lin.split()
print(non_unique_numbers(line))
The output is inconsistent it seems to remove every other non-duplicate at times but never removes all the non-duplicates, please can you let me know where I am going wrong.
What happens when doing for i in line is that every iteration i gets the value from an iterator created on the variable line. So by changing line you are not changing the iterator.
So, when removing an element at index, say j, all items in index i > j are moved one index down. So now your next item will be again in index j, but the loop will still continue and go to index j+1.
A good way to see this is running your function on an all-non-duplicate values:
>>> l = [0, 1, 2, 3, 4, 5]
>>> print(non_unique_numbers(l))
[1, 3, 5]
You can see that only even-indexed values were removed according to the logic described above.
What you want to do is work on a new, separate list to stack your results. For that you could use simple list comrehension:
lin = input('go on then')
line = lin.split()
print([x for x in line if line.count(x) > 1])
It is not safe to modify a list while iterating through it. The actual problem, I think, is that remove() only removes the first instance of the value, which would make the < 2 check on the last element fail and not call the remove().
Better to use a hash table to find the counts and return those with < 2 then.

python3 functional programming: Accumulating items from a different list into an initial value

I have some code that performs the following operation, however I was wondering if there was a more efficient and understandable way to do this. I am thinking that there might be something in itertools or such that might be designed to perform this type of operation.
So I have a list of integers the represents changes in the number of items from one period to the next.
x = [0, 1, 2, 1, 3, 1, 1]
Then I need a function to create a second list that accumulates the total number of items from one period to the next. This is like an accumulate function, but with elements from another list instead of from the same list.
So I can start off with an initial value y = 3.
The first value in the list y = [3]. The I would take the second
element in x and add it to the list, so that means 3+1 = 4. Note that I take the second element because we already know the first element of y. So the updated value of y is [3, 4]. Then the next iteration is 4+2 = 6. And so forth.
The code that I have looks like this:
def func():
x = [0, 1, 2, 1, 3, 1, 1]
y = [3]
for k,v in enumerate(x):
y.append(y[i] + x[i])
return y
Any ideas?
If I understand you correctly, you do what what itertools.accumulate does, but you want to add an initial value too. You can do that pretty easily in a couple ways.
The easiest might be to simply write a list comprehension around the accumulate call, adding the initial value to each output item:
y = [3 + val for val in itertools.accumulate(x)]
Another option would be to prefix the x list with the initial value, then skip it when accumulate includes it as the first value in the output:
acc = itertools.accumulate([3] + x)
next(acc) # discard the extra 3 at the start of the output.
y = list(acc)
Two things I think that need to be fixed:
1st the condition for the for loop. I'm not sure where you are getting the k,v from, maybe you got an example using zip (which allows you to iterate through 2 lists at once), but in any case, you want to iterate through lists x and y using their index, one approach is:
for i in range(len(x)):
2nd, using the first append as an example, since you are adding the 2nd element (index 1) of x to the 1st element (index 0) of y, you want to use a staggered approach with your indices. This will also lead to revising the for loop condition above (I'm trying to go through this step by step) since the first element of x (0) will not be getting used:
for i in range(1, len(x)):
That change will keep you from getting an index out of range error. Next for the staggered add:
for i in range(1, len(x)):
y.append(y[i-1] + x[i])
return y
So going back to the first append example. The for loop starts at index 1 where x = 1, and y has no value. To create a value for y[1] you append the sum of y at index 0 to x at index 1 giving you 4. The loop continues until you've exhausted the values in x, returning accumulated values in list y.

Resources