2

I would like to sum up non-zero elements of an array (1-d) but do it separately for positive and negative integers (they can only be ones and two) and also display zeros where they are.

An example of an array:

array = np.array([0, 0, 0, -1, -1, 0, 1, 2, 1, 1, 0, -1, 0, 1, 1, -1, -2])

Output:

array([0, 0, 0, -2, 0, 5, 0, -1, 0, 2, -3])

I think my problem is that I have no idea how to separate the sequences of positive and negative values in array.

3
  • 4
    Could you do this on paper? If so, could you write out the steps for how to do it? If so, would you know how to translate that into code? Which part of that process are you stuck on? Or have you tried anything yet? Commented Sep 29, 2020 at 18:08
  • Do you open to other packages like Pandas? Commented Sep 29, 2020 at 18:11
  • @RandomDavis I love this comment :-). Actually, I did it on paper just like I do everything else I have problems with, but clearly I wasn't smart enough. Commented Sep 29, 2020 at 19:30

2 Answers 2

7

Here's one way -

def sum_by_signs(a):
    m1 = a<0
    m2 = a>0
    m0 = a==0 # or ~m1 & ~m2
    p = np.flatnonzero(m0[:-1] | np.diff(m1) | np.diff(m2))+1
    return np.add.reduceat(a, np.r_[0,p])

Or bring that np.r_[0 part into boolean construction part -

def sum_by_signs_v2(a):
    m1 = a<0
    m2 = a>0
    m0 = a==0 # or ~m1 & ~m2
    p = np.flatnonzero(np.r_[True, m0[:-1] | np.diff(m1) | np.diff(m2)])
    return np.add.reduceat(a, p)

Explanation

We are starting off the idea to split the array into "islands" based on sign changes or when we encounter a sequence of 0s, in which case we are looking to split each element as a separate one. By splitting, think of it as list of lists, if that makes it easier to understand. Now, the game is how do we get those splits. We need indices that signifies the start, stop indices of those islands. As said earlier, there are three cases, sign changes from + to - or vice versa or sequence of 0s.

Hence, that structure of boolean masks are used to give those indices with one-off slices to detect sign changes from + to - and vice versa with a combination of np.diff(m1) | np.diff(m2). Final one of m0[:-1] is for sequence of 0s. These indices are then fed to np.add.reduceat to get intervaled summations.

Sample runs -

In [208]: a
Out[208]: array([ 0,  0,  0, -1, -1,  0,  1,  2,  1,  1,  0, -1,  0,  1,  1, -1, -2])

In [209]: sum_by_signs(a)
Out[209]: array([ 0,  0,  0, -2,  0,  5,  0, -1,  0,  2, -3])

In [211]: a
Out[211]: array([ 1,  2,  0, -1, -1,  0,  1,  2,  1,  1,  0, -1,  0,  1,  1, -1, -2])

In [212]: sum_by_signs(a)
Out[212]: array([ 3,  0, -2,  0,  5,  0, -1,  0,  2, -3])

In [214]: a
Out[214]: 
array([ 1,  2,  0, -1, -1,  0,  1,  2,  1,  1,  0, -1,  0,  1,  1, -1, -2,
        0])

In [215]: sum_by_signs(a)
Out[215]: array([ 3,  0, -2,  0,  5,  0, -1,  0,  2, -3,  0])
Sign up to request clarification or add additional context in comments.

3 Comments

Can you explain to a beginner why and how this works? This is obviously a working solution but it seems pretty opaque to anyone except a numpy expert.
@RandomDavis See if the added explanation makes sense.
@Divakar this works great and it's pretty fast on large arrays too. Thank you very much for your help.
1

This solves the problem, but surely theres a smarter way to do it

array = [0, 0, 0, -1, -1, 0, 1, 2, 1, 1, 0, -1, 0, 1, 1, -1, -2]

switch = 0
while switch == 0:
    for i in range(len(array)):
        try:
            array[i+1]
            if array[i] > 0 and array[i+1] > 0: 
                array[i] += array[i + 1]
                array.pop(i + 1)
                break
            elif array[i] < 0 and array[i+1] < 0:   
                array[i] += array[i + 1]
                array.pop(i + 1)
                break
        except:
            switch = 1
            break

at the end, the value of array is [0, 0, 0, -2, 0, 5, 0, -1, 0, 2, -3]

1 Comment

Thank you. I did something similar but wasn't happy with the performance and complexity. Thanks for your help though.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.