I haven't coded in Python for a long time and sometimes I'm quite confused.
When I have an array, like eg.: arr = [0, 1, 0, 3, 12]
and say:
for i in arr:
if i == 0:
arr.remove(i)
it is removing the two zeroes the way I want.
But if the array is something like: arr = [0, 0, 1], with the above method, one 0 will remain.
So could someone explain me why the behavior is like that? I don't find an explanation for this.
Better try this:
arr = [n for n in arr if n != 0]
This uses a list comprehension, it's a lot safer than what you're doing: removing the elements at the same time you're iterating is a bad idea, and you'll experience problems such as elements not being removed.
This is because the list size is reduced and the iterator traversing it will find less elements than it was expecting when the iteration began.
I think I found why your method doesn't work. The problem comes from the way you iterate.
In your example, your function seems to work for arr = [0,1,0,3,12] but not on your second array arr2 = [0,0,2] and returns [0,2]. One interesting thing to investigate then, is the fact that in your second example, you have two consecutive zeros.
Take a look at this code and try to execute it :
for i in arr:
print('i = '+str(i))
if(i == 0):
arr.remove(i)
With your first array, you noticed that your output is the one you expected but that was lucky. As a matter of fact, if you run the code above, you would see that it prints in your console :
> i = 0
> i = 0
> i = 12
So, actually, this means that your remove statement changes the array you iterate on. After a deletion, you skip an element in your array.
This means you should prefer another way, like the ones suggested in comments.
Hope this helps
you can filter out your zeros with the built-in function filter:
arr = list(filter(None, arr))
you have to pay attention if you use filter function with None as first parameter, this will apply bool over your items if you have elements like None, 0 or the empty string '' the result will be the same, False and all these elements will be filtered out, for safety reasons you may use:
arr = list(filter(lambda x: x != 0 , arr))
Related
Have a good day everyone, pardon my lack of understanding, but I can't seem to figure out why python built in function does not work when being called with another function variable and it just doesn't do what I want at all. Here is the code
def ignoreten(h):
ignoring = False
for i in range (1,len(h)-2):
if ignoring == True and h[i]==10:
h.remove(10)
if ignoring == False and h[i] ==10:
ignoring = True
The basic idea of this is just to decided the first 10 in a list, keep it, continue iterating until you faced another 10, then just remove that 10 to avoid replication, I had searched around but can't seem to find any solution and that's why I have to bring it up here. Thank you
The code you listed
def ignoreten(h):
ignoring = False
for i in range (1,len(h)-2):
if ignoring == True and h[i]==10:
h.remove(10)
if ignoring == False and h[i] ==10:
ignoring = True
Will actually do almost the exact opposite of what you want. It'll iterate over h (sort of, see [1]), and if it finds 10 twice, it'll remove the first occurrence from the list. (And, if it finds 10 three times, it'll remove the first two occurrences from the list.)
Note that list.remove will:
Remove the first item from the list whose value is equal to x. It
raises a ValueError if there is no such item.
Also note that you're mutating the list you're iterating over, so there's some additional weirdness here which may be confusing you, depending on your input.
From your follow-up comment to my question, it looks like you want to remove only the second occurrence of 10, not the first and not any subsequent occurrences.
Here are a few ways:
Iterate, store index, use del
def ignoreten(h):
index = None
found_first = False
for i,v in enumerate(h):
if v == 10:
if not found_first:
found_first = True
else:
index = i
break
if index is not None:
del h[index]
A little more verbose than necessary, but explicit, safe, and modifiable without much fear.
Alternatively, you could delete inside the loop but you want to make sure you immediately break:
def ignoreten(h):
found_first = False
for i,v in enumerate(h):
if v == 10:
if not found_first:
found_first = True
else:
del h[i]
break
Collect indices of 10s, remove second
def ignoreten(h):
indices = [i for (i,v) in enumerate(h) if v == 10]
if len(indices) > 1:
del h[indices[1]] # The second index of 10 is at indices[1]
Clean, but will unnecessarily iterate past the second 10 and collect as many indices of 10s are there are. Not likely a huge issue, but worth pointing out.
Collect indices of 10s, remove second (v2, from comments)
def ignoreten(h):
indices = [i for (i,v) in enumerate(h) if v == 10]
for i in reversed(indices[1:]):
del h[i]
From your comment asking about removing all non-initial occurrences of 10, if you're looking for in-place modification of h, then you almost definitely want something like this.
The first line collects all the indices of 10 into a list.
The second line is a bit tricky, but working inside-out it:
[1:] "throws out" the first element of that list (since you want to keep the first occurrence of 10)
reversed iterates over that list backwards
del h[i] removes the values at those indices.
The reason we iterate backwards is because doing so won't invalidate the rest of our indices that we've yet to delete.
In other words, if the list h was [1, 10, 2, 10, 3, 10], our indices list would be [1, 3, 5].
In both cases we skip 1, fine.
But if we iterate forwards, once we delete 3, and our list shrinks to 5 elements, when we go to delete 5 we get an IndexError.
Even if we didn't go out of bounds to cause an IndexError, our elements would shift and we'd be deleting incorrect values.
So instead, we iterate backwards over our indices, delete 5, the list shrinks to 5 elements, and index 3 is still valid (and still 10).
With list.index
def ignoreten(h):
try:
second_ten = h.index(10, h.index(10)+1)
del h[second_ten]
except ValueError:
pass
The inner .index call finds the first occurrence, the second uses the optional begin parameter to start searching after that. Wrapped in try/except in case there are less than two occurrences.
⇒ Personally, I'd prefer these in the opposite order of how they're listed.
[1] You're iterating over a weird subset of the list with your arguments to range. You're skipping (not applying your "is 10" logic to) the first and last two elements this way.
Bonus: Walrus abuse
(don't do this)
def ignoreten(h):
x = 0
return [v for v in h if v != 10 or (x := x + 1) != 1]
(unlike the previous versions that operated on h in-place, this creates a new list without the second occurrence of 10)
But the walrus operator is contentious enough already, please don't let this code out in the wild. Really.
Problem to solve for: Write an algorithm that takes an array and moves all of the zeros to the end, preserving the order of the other elements.
Solution tested:
array = ["a",0,0,"b","c","d",0,1,0,1,0,3,0,1,9,0,0,0,0,9]
newlist=[]
number_of_zero=0
for i in array:
if i==0:
number_of_zero+=1
if i!=0:
newlist.append(i)
print(newlist)
for i in range(number_of_zero):
newlist.append(0)
print(newlist)
The solution worked great but however when I migrate all this over to a function, my tests fail and I'm not sure why since the solution tested 100% using jupyter notebook
Solution implemented:
def move_zeros(array):
newlist=[]
count = 0
for i in array:
if i==0: #Everytime a zero is encountered, the count of zero is increased
count+=1
if i!=0:
newlist.append(i) #if the value encountered while looping is not zero, append it to the new array
#print(newlist)
for i in range(count):
newlist.append(0) #This loop will append zero times it got counted
return newlist
Can someone suggest me why is my code failing when wrapped into a function? Do not see anything wrong with my logic.
def move_zeros(array):
newlist=[]
zerolist = []
for i in array:
# False == 0 will evaluate to True (but False should stay in place), ignore boolean types
# not isinstance(i, bool) means anything other than True or False
# True and False (like everything else) are objects in python
if not isinstance(i, bool):
# at this point, we can use ==, which will only evaluate to True if an int or float is zero
if i == 0:
# add the zero, but maintain the type (int or float)
zerolist.append(i)
# go to the next iteration
continue
# if the continue statement wasn't executed, everything else will be added here
newlist.append(i)
# append the zero list (with the same types) to the non-zero list
return newlist + zerolist
This looks like a codewars problem I remember solving some time ago. The trick to this problem is accounting for the "truthiness" of 1's and 0's or the "numberness" of True and False.
My solution is you append all the values that are not equal to 0 or are bool values (to capture the False values) using the isinstance() function
then you can calculate the difference in length of your original list against your "non zero" list. Then take that difference and append zeros to it.
def move_zeros(array):
no_zeroes = [x for x in array if x != 0 or isinstance(x, bool)]
solution = no_zeroes + (len(array) - len(no_zeroes))*[0]
return solution
Strip the zeroes and add them all in one go afterwards by counting the difference in length
arr = ["a",0,0,"b","c","d",0,1,0,1,0,3,0,1,9,0,0,0,0,9]
def move_zeros(array):
nz = [e for e in arr if e is not 0]
return nz+[0]*(len(arr)-len(nz))
print(move_zeros(arr))
produces
['a', 'b', 'c', 'd', 1, 1, 3, 1, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Short and simple
newlist = sorted(array, key=(lambda x: x is 0))
If you don't mind mutating the original list you can do
array.sort(key=(lambda x: x is 0))
Note that normally we would test equality with == rather than is, but using is allows us to differentiate 0 from False. This counts on the fact that the CPython implementation preallocates integers from -5 to 256. Unfortunately, that means the behavior of 0 is 0 is implementation defined. You can still count on it though, because there is not a major Python implementation that does not act like CPython in this regard (Cython, PyPy, IronPython, Jython).
I have to write a function that will be executed as long as the while loop is true. The condition for this while loop is that there may be no zeros in my list. But if my list contains an element that is not zero and all the other element are zero, this is not a problem.
I'll give an example: [0, 0, 0] -> stop execution;
[0, 1, 0, 0, 0] -> continue execution
This is my code:
while all(char != 0 for char in list):
list = next(list)
finallist.append(list)
(next is the name of my function)
Thank you
Your wording is confusing because it seems like you're asking for contradictory conditions, but based off of your example, I believe what you want is to have at least one non-zero in your list. In which case the answer is simply to replace all with any in your code.
Also, I would avoid naming your function next, as that conflicts with the next function that is built into Python.
Filter the list from zeroes:
[x for x in your_list if x != 0]
Then, check if the length of the filtered list is greater than 0 (or just put it in a condition, cause empty lists are False and non-empty lists are True:
while [x for x in your_list if x != 0]:
# Do stuff
Edit: I re-read your question, and found where you were wrong,
Where using all(), you check that the list contains only elements that are not 0, while you actually have to check if that list has any elements that are not 0. For that, use any() and not all().
I have two arrays: array and least_common (filter array)
The following code iterates through array, checks for elements that match least_common, and if finds it, modifies it and appends it to a new array.
for i in range (len(array)):
for j in range(len(least_common)):
if array[i] is least_common[j][0]:
new_array.append ((array[i]) + (array[i] * (mod[1]/100)))
However, if the element in array does not match any of the elements in least_common I wan't to append it to new_array, then iterate to the next element in array to begin the checking process again.
This code is a bit wonky to me -- I think you want to start with something more like:
lookup = set([x[0] for x in least_common])
new_array = []
for elem in array:
if elem in lookup:
new_array.append(elem + (elem * (mod[1]/100)))
else:
new_array.append(elem)
In Python, what you are trying to do is done using lists. There is another separate data type called arrays, but that is for totally different purpose. Please don't confuse and use the proper terminology, list.
Lists can be iterated through. You need to not index the elements out of the list and then access them using the index. That is C or C++ way of doing things and not python.
You use a list or a dictionary called mod in your original code. It is a bad idea to override builtin names. I tried to understand what you are trying, came up with the following code. Take it further, but before that, I think some beginner tutorials might help you as well.
new_array = []
somevalue = 0.001
for elem in array:
for anotherelem in least_common:
if elem == anotherelem[0]:
new_array.append(elem + (elem * somevalue))
Keep track of whether you found a match using a boolean, which you set to False before each inner loop and set to True within your if. After each iteration, if it's still False it means you found no matches and should then do your appending.
You should also follow what #Andrew says and iterate over lists using for a in array:. If you need the index, use for i, a in enumerate(array):. And be aware that is is not the same as ==.
new_array = []
for array_item in array:
found = False
for least_common_item in least_common:
if array_item is least_common_item:
found = True
if not found:
new_array.append (array_item * (1 + mod[1]/100))
You can also greatly shorten this code using in if you meant to use == instead of is:
for array_item in array:
if array_item not in least_common:
new_array.append (array_item * (1 + mod[1]/100))
Why not this:
least_common_set = frozenset(x[0] for x in least_common)
for e in array:
if e is not in least_common_set:
new_array.append(e + (e * (mod[1]/100)))
If I understand correctly your problem, here is a possible solution:
for e in array:
for lc in least_common:
if e is lc[0]:
new_array.append(e + e * (md[1] / 100))
break
else:
new_array.append(e)
The else clause in the for loop is executed when the loop terminates through exhaustion of the list, but not when the loop is terminated by a break statement.
Note that there is no need to use range or len, in Python you can just iterate on the elements of a sequence without involving indexes - you may use enumerate for that, but in this case you don't need to. Also, please don't use built-in names such as mod for your variables: here, I have renamed your dictionary md.
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.