Python – counting sign changes
Question:
I have a list of numbers I am reading left to right. Anytime I encounter a sign change when reading the sequence I want to count it.
X = [-3,2,7,-4,1,-1,1,6,-1,0,-2,1]
X = [-, +, +, -, +, -, +, +, -, -,-,+]
So, in this list there are 8 sign changes.
When Item [0]
(in this case -3) is negative it is considered a sign change. Also, any 0 in the list is considered [-]
.
Any help would be greatly appreciated.
Answers:
You can use itertools.groupby
to count the groups of positive and non-positive numbers:
>>> x = [-3,2,7,-4,1,-1,1,6,-1,0,-2,1]
>>> import itertools
>>> len(list(itertools.groupby(x, lambda x: x > 0)))
Result:
8
In your question you state that you want:
- to count the changes, not the groups
- to count an extra change if the first element is not positive.
You can do this either by testing the first element directly and adjusting the result:
>>> len(list(itertools.groupby(x, lambda x: x > 0))) - (x[0] > 0)
or by prepending a positive number to the input before doing the grouping then subtracting 1 from the result:
>>> len(list(itertools.groupby(itertools.chain([1], x), lambda x: x > 0))) - 1
Watch out if your input list could by empty – the former solution will raise an exception.
X = [-3,2,7,-4,1,-1,1,6,-1,0,-2,1]
last_sign = 1
sign_changes = 0
for x in X:
if x == 0:
sign = -1
else:
sign = x / abs(x)
if sign == -last_sign:
sign_changes = sign_changes + 1
last_sign = sign
print sign_changes
numbers = [-3,2,7,-4,1,-1,1,6,-1,0,-2,1]
# could be replaced by signs = [x > 0 for x in numbers]
# but this methods gives us nice minus and plus signs
signs = map(lambda x: "+" if x > 0 else "-", numbers)
# zip(…) creates the pairs, each pair that has different signs
# adds one to "count"
count = sum(1 for x,y in zip(signs[:-1], signs[1:]) if x != y)
-> 7
For your additional requirement, that a negative number at the start of the list should be considered another change, just add a positive number to your list.
If you’re dealing with huge lists, consider using generators. (izip, tee, …)
Here is a solution using fold, have fun figuring it out:
def lolwut((x,c), y):
return (y, c+(x^y))
print reduce( lolwut ,(x > 0 for x in X), (True,0)) # 8
print reduce( lolwut ,(x > 0 for x in X), (False,0)) # 7
If you haven’t been convinced to read the itertools documentation yet:
def pairs(iterable):
'iter -> (iter0, iter1), (iter1, iter2), (iter3, iter4), ...'
from itertools import izip, tee
first, second = tee(iterable)
second.next()
return izip(first, second)
def sign_changes(l):
result = 0
if l and l[0]<=0: result += 1
result += sum(1 for a,b in pairs(l) if b*a<=0 and (a!=0 or b!=0))
return result
For integers, (a^b) < 0
if signs of a and b are different.
def countSignChanges(seq):
# make sure 0's are treated as negative
seq = [-1 if not x else x for x in seq]
# zip with leading 1, so that opening negative value is
# treated as sign change
return sum((a^b)<0 for a,b in zip([1]+seq, seq))
X = [-3,2,7,-4,1,-1,1,6,-1,0,-2,1]
print countSignChanges(X)
gives the desired answer, 8.
Here is a way to do it without loops… It should be much faster for large data 😉 (however, because of the numpy etc, will not be as efficient for small lists- and it will be much better for numpy array than lists for obvious reasons – you can even drop the conversion…)
x = np.array([-3,2,7,-4,1,-1,1,6,-1,0,-2,1])
positive= x>0
count = np.logical_xor(positive[1:],positive[:-1]).sum()
count += not(positive[0])
print count
returns 8
Xor returns true if the two booleans are different ( from + to – ) and is pretty fast. There is one problem with the code: if something goes to exactly 0 from the positive sign, it will count as crossing. This is exactly what you ask in your question though since you represented 0 as ‘-‘.
Input = [-1, 2, 3, -4, 5, -6, 7, 8, -9, 10, -11, 12]
for i in range(len(Input)):
if(Input[i]<1):
Input[i]=0
else:
Input[i]=1
print(Input) #[0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1]
count=0
check=Input[0]
for i in range(len(Input)-1):
if check != Input[i + 1]:
count+=1
check=Input[i+1]
print(count) #9
K = np.sum(np.abs(np.diff(x>0)))
Gives the result
I have a list of numbers I am reading left to right. Anytime I encounter a sign change when reading the sequence I want to count it.
X = [-3,2,7,-4,1,-1,1,6,-1,0,-2,1]
X = [-, +, +, -, +, -, +, +, -, -,-,+]
So, in this list there are 8 sign changes.
When Item [0]
(in this case -3) is negative it is considered a sign change. Also, any 0 in the list is considered [-]
.
Any help would be greatly appreciated.
You can use itertools.groupby
to count the groups of positive and non-positive numbers:
>>> x = [-3,2,7,-4,1,-1,1,6,-1,0,-2,1]
>>> import itertools
>>> len(list(itertools.groupby(x, lambda x: x > 0)))
Result:
8
In your question you state that you want:
- to count the changes, not the groups
- to count an extra change if the first element is not positive.
You can do this either by testing the first element directly and adjusting the result:
>>> len(list(itertools.groupby(x, lambda x: x > 0))) - (x[0] > 0)
or by prepending a positive number to the input before doing the grouping then subtracting 1 from the result:
>>> len(list(itertools.groupby(itertools.chain([1], x), lambda x: x > 0))) - 1
Watch out if your input list could by empty – the former solution will raise an exception.
X = [-3,2,7,-4,1,-1,1,6,-1,0,-2,1]
last_sign = 1
sign_changes = 0
for x in X:
if x == 0:
sign = -1
else:
sign = x / abs(x)
if sign == -last_sign:
sign_changes = sign_changes + 1
last_sign = sign
print sign_changes
numbers = [-3,2,7,-4,1,-1,1,6,-1,0,-2,1]
# could be replaced by signs = [x > 0 for x in numbers]
# but this methods gives us nice minus and plus signs
signs = map(lambda x: "+" if x > 0 else "-", numbers)
# zip(…) creates the pairs, each pair that has different signs
# adds one to "count"
count = sum(1 for x,y in zip(signs[:-1], signs[1:]) if x != y)
-> 7
For your additional requirement, that a negative number at the start of the list should be considered another change, just add a positive number to your list.
If you’re dealing with huge lists, consider using generators. (izip, tee, …)
Here is a solution using fold, have fun figuring it out:
def lolwut((x,c), y):
return (y, c+(x^y))
print reduce( lolwut ,(x > 0 for x in X), (True,0)) # 8
print reduce( lolwut ,(x > 0 for x in X), (False,0)) # 7
If you haven’t been convinced to read the itertools documentation yet:
def pairs(iterable):
'iter -> (iter0, iter1), (iter1, iter2), (iter3, iter4), ...'
from itertools import izip, tee
first, second = tee(iterable)
second.next()
return izip(first, second)
def sign_changes(l):
result = 0
if l and l[0]<=0: result += 1
result += sum(1 for a,b in pairs(l) if b*a<=0 and (a!=0 or b!=0))
return result
For integers, (a^b) < 0
if signs of a and b are different.
def countSignChanges(seq):
# make sure 0's are treated as negative
seq = [-1 if not x else x for x in seq]
# zip with leading 1, so that opening negative value is
# treated as sign change
return sum((a^b)<0 for a,b in zip([1]+seq, seq))
X = [-3,2,7,-4,1,-1,1,6,-1,0,-2,1]
print countSignChanges(X)
gives the desired answer, 8.
Here is a way to do it without loops… It should be much faster for large data 😉 (however, because of the numpy etc, will not be as efficient for small lists- and it will be much better for numpy array than lists for obvious reasons – you can even drop the conversion…)
x = np.array([-3,2,7,-4,1,-1,1,6,-1,0,-2,1])
positive= x>0
count = np.logical_xor(positive[1:],positive[:-1]).sum()
count += not(positive[0])
print count
returns 8
Xor returns true if the two booleans are different ( from + to – ) and is pretty fast. There is one problem with the code: if something goes to exactly 0 from the positive sign, it will count as crossing. This is exactly what you ask in your question though since you represented 0 as ‘-‘.
Input = [-1, 2, 3, -4, 5, -6, 7, 8, -9, 10, -11, 12]
for i in range(len(Input)):
if(Input[i]<1):
Input[i]=0
else:
Input[i]=1
print(Input) #[0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1]
count=0
check=Input[0]
for i in range(len(Input)-1):
if check != Input[i + 1]:
count+=1
check=Input[i+1]
print(count) #9
K = np.sum(np.abs(np.diff(x>0)))
Gives the result