-1

I am trying to solve a problem in which I have to remove the zeroes (both 0 and 0.0) from a list and add them to the end of the list (the zero that is appended can be a 0, doesn't have to be 0.0). But the catch is that I must not remove False. I thought I finally figured out my problem by using is instead of == but for some reason it's not working:

arr = [9,0.0,0,9,1,2,0,1,0,1,0.0,3,0,1,9,0,0,0,0,9]
def move_zeros(array):
    temp = array.copy()
    j = 0
    for p, i in enumerate(array):
        if i is 0 or i is 0.0:
            del temp[p - j]
            j += 1
            temp.append(0)
    return temp

print(move_zeros(arr))

I've tried putting parentheses at the if statement but the result is the same. It removes all the 0 but for some reason it won't remove 0.0. Result:

[9, 0.0, 9, 1, 2, 1, 1, 0.0, 3, 1, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0]

I rewrote the function but outside of the function and for some reason it works:

array = [1, 0, 2, False, 0.0, 4]
for p, i in enumerate(array):
    if i is 0 or i is 0.0:
        del temp[p - j]
        j += 1
        temp.append(0)

And here the result is how I would expect it:

[1, 2, False, 4, 0, 0]

Why is the floating point zero being removed when executed outside of the function, but when the function move_zeros is called, the floating points are not recognized in the if statement?

Siddharth Satpathy
  • 2,737
  • 4
  • 29
  • 52
lukaabra
  • 83
  • 1
  • 10

2 Answers2

2

You only handle floats and ints - so you can construct a new list from your input if the value is not 0:

arr = [9,0.0,0,9,1,2,0,1,0,1,0.0,3,0,1,9,0,0,0,0,9]

def move_zeros(array):
    len_arr = len(array)
    return ([ x for x in array if x != 0.0]+[0]*len_arr)[:len_arr]

# equivalent non-listcomprehension but simple for loop
def m_z(arr):
    k = []
    tmp = []
    for e in arr:
        if e/1.0 != 0.0:
            k.append(e)
        else:
            tmp.append(0)
    return k+tmp

    print(move_zeros(arr))

Output:

[9, 9, 1, 2, 1, 1, 3, 1, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

If x is an integer only 0 and -0 can lead to 0.0 - if x is a float, only 0.0 and -0.0 can lead to 0.0 - simply do not put them into the output. .copy() is not needed because the list comprehension copies already for you.

The check for intergers and is works because python caches integers from -5 to 256 or so - they all get the same id() and hence is "seems" to work.

Only use is for None checks or if you know what you do, never for numbers.

If you want to leave non (ìnt,float) untouched you can check for that too:

arr = [9,0.0,0,False,9,1,2,0,1,0,1,0.0,3,0,1,9,0,0,0,0,9]

def move_zeros(array):
    len_arr = len(array)
    return ([ x for x in array if type(x) not in {int,float} or 
             x != 0.0]+[0]*len_arr)[:len_arr]

# [9, False, 9, 1, 2, 1, 1, 3, 1, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

Read more:


Small sanity check:

k = 365
i = 365 // 5 * 5  # if you do i = 365 only, they are ==/is True, this way: they are not

for elem in [0,False,k]:
    for olem in [0,False,i]:     
        print(f"{str(elem):>8} is {str(olem):<10}:   {str(elem is olem):<10} ")
        print(f"{str(elem):>8} == {str(olem):<10}:   {str(elem == olem):<10} ")

Output:

       0 is 0         :   True       
       0 == 0         :   True       
       0 is False     :   False      
       0 == False     :   True       
       0 is 365       :   False      
       0 == 365       :   False      
   False is 0         :   False      
   False == 0         :   True       
   False is False     :   True       
   False == False     :   True       
   False is 365       :   False      
   False == 365       :   False      
     365 is 0         :   False      
     365 == 0         :   False      
     365 is False     :   False      
     365 == False     :   False      
     365 is 365       :   False      # k is i
     365 == 365       :   True       # k == i
Patrick Artner
  • 50,409
  • 9
  • 43
  • 69
  • Thank you for the solution! But could you point out in the question why is the `if` statement inside of the function not recognizing `0.0` and outside of it (in the second example) it is recognizing it. – lukaabra Dec 17 '18 at 16:56
  • 1
    What's the purpose of the division by `1.0`? – Mark Dickinson Dec 17 '18 at 19:09
  • 1
    Yes, of course. But why bother dividing by `1.0` at all? It makes no difference to the returned result, and just makes the code more confusing. Your solution would work just fine without the divisions. That is, `x / 1.0 != 0.0` could be replaced with `x != 0.0` with no change in behaviour. – Mark Dickinson Dec 17 '18 at 19:22
1

use ==, not is. is is for comparing identity (two "things" occupying the same space in memory, essentially), == is for equality (do two things have the same defining properties). Furthermore, with floats it is often useful to check whether things are "close enough", e.g. is_equal = a-b < 0.00001

dngrs
  • 269
  • 1
  • 4
  • Also, in CPython, at least, small ints are cached, floats are not, which explains why `0 is 0` can be (but isn't required to be) true. – chepner Dec 17 '18 at 16:45
  • OP states they used `is` to avoid catching `False` in the function, `False==0.0` evaluates to True – G. Anderson Dec 17 '18 at 16:47
  • I understand I can put a check before the `if` statement that checks if the element is `False` and do what you said with `==`. The thing I don't understand is the behavour inside and outside the function. – lukaabra Dec 17 '18 at 16:51