Reputation: 408
I have some code that should find the position of any 0
items in a list and move it to the back whilst preserving the order of the other elements.
def test2(array):
try:
for n in range(len(array)):
array.append(array.pop(array.index(0)))
return array
except ValueError:
return array
This code works fine for any list apart from one with False
in. I think this is because the .index(0)
: will also return the position of any False in the list. Any way to get around this?
For example if array = [0,1,None,2,False,1,0]
then the output should be [1,None,2,False,1,0,0]
With that same input my code produces: [1, None, 2, 1, False, 0, 0]
Upvotes: 3
Views: 799
Reputation: 402423
This is a consequence of the fact that bool
is a subclass of int
in python, so searching for the first index of 0
will return the index of False
, if it is in the list before a 0
because False == 0
.
What you can do is check whether a list element is an instance of int
, and at the same time, not an instance of bool
. This way, you avoid matching other false values (like empty containers and None
).
def is_zero(v):
# return is instance(v, int) and v is not False and not v
# return isinstance(v, int) and not isinstance(v, bool) and not v
return type(v) in (int, float) and not v
You can then iterate over lst
in reverse and update in-place.
lst = [1, 0, None, False, 0, 3, True] # For example.
for i in reversed(range(len(lst))):
if is_zero(lst[i]):
lst.append(lst.pop(i))
print(lst)
# [1, None, False, 3, True, 0, 0]
This is amortised linear time complexity, if I'm not mistaken.
Upvotes: 3
Reputation: 11651
Python guarantees True
and False
are the only instances of bool
, so you can use is
to distinguish False
from 0
.
z = []
return [e for e in array if e is False or e != 0 or z.append(e)] + z
This will preserve the order of the various non-False
zeros (0
, 0.0
, 0j
, Decimal(0)
, Fraction(0, 1)
), that may be in the list.
Upvotes: 0
Reputation: 408
I think this is as concise as this can be. Thanks for the help everyone
l = [i for i in array if isinstance(i, bool) or i!=0]
return l+[0]*(len(array)-len(l))
Upvotes: 0
Reputation: 16056
You can use a wrapper with a custom __eq__
:
class Typed:
def __init__(self, val):
self.val = val
def __hash__(self):
return hash(self.val)
def __eq__(self, other):
if isinstance(other, Typed):
other = other.val
return type(self.val) is type(other) and self.val == other
Then replace array.index(0)
with array.index(Typed(0))
- you don't need to use this within the array itself.
Extending this to containers (so that (0, 1)
and (False, True)
won't be equal) is left as an exercise for the reader.
Upvotes: 1
Reputation: 57033
A variation of @coldspeed's solution:
array = [0, 1, None, 2, False, 1, 0]
nonzero = [x for x in array if x or x is None or isinstance(x,bool)]
nonzero + [0] * (len(array) - len(nonzero))
# [1, None, 2, False, 1, 0, 0]
Upvotes: 1
Reputation: 51643
If creating another list is of no concern, you can use list-comprehensions:
def test2(array):
la = len(array)
return ([x for x in array if not isinstance(x,int) or x]+[0]*la)[:la]
The first part filters out any 0 integerbut lets any non-int pass. The second part adds (too many) zeros and trims the result back down to the original lenght.
Caveat: This will create up to twice the original list-length in data - so not good for "big" lists or lists with few 0.
Usage:
k = [1,2,3,0,4,5,False,92,-3,0,1,0,0,True,[],None,True]
print(k)
print(test2(k))
Output:
[1, 2, 3, 0, 4, 5, False, 92, -3, 0, 1, 0, 0, True, [], None, True]
[1, 2, 3, 4, 5, 92, -3, 1, True, True, 0, 0, 0, 0, 0, 0, 0]
Doku:
Upvotes: 2