10

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.

Nick Dandoulakis
  • 42,588
  • 16
  • 104
  • 136
Captain Cretaceous
  • 791
  • 3
  • 7
  • 14
  • If it helps anyone answering, here's a Haskell solution: `signChanges xs = sum $ zipWith (\x y -> if (x>0) == (y>0) then 0 else 1) (1:xs) xs` – Joey Adams May 29 '10 at 23:08
  • 1
    Here is a link for those looking for [**a solution where zero is handled as no change**](http://stackoverflow.com/a/40804510/2192488). – Serge Stroobandt Nov 25 '16 at 12:02

10 Answers10

19

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.

Mark Byers
  • 811,555
  • 193
  • 1,581
  • 1,452
7
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
Christian Neverdal
  • 5,655
  • 6
  • 38
  • 93
1

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
Jochen Ritzel
  • 104,512
  • 31
  • 200
  • 194
1

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 '-'.

ntg
  • 12,950
  • 7
  • 74
  • 95
1

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.

PaulMcG
  • 62,419
  • 16
  • 94
  • 130
1

I believe that a much simpler option is to use:

K = np.sum(np.abs(np.diff(x>0)))

which also gives the same result without complex computations.

Filipe Pinto
  • 311
  • 3
  • 17
0
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, …)

Georg Schölly
  • 124,188
  • 49
  • 220
  • 267
0

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
Peter Milley
  • 2,768
  • 19
  • 18
0
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
0

Using element-wise multiplication with the shifted array and signbit should be the fastest:

X = np.array([-3,2,7,-4,1,-1,1,6,-1,0,-2,1])
sign_changes = np.sum(np.signbit(X[1:]*X[:-1]))

Counting the first value as a change if it is negative:

if X[0] < 0: sign_changes += 1 

Because 0 is neither negative nor positive it can be replaced with a random negative number to be counted as a negative.

Spas
  • 840
  • 16
  • 13