10

I'd like to find a vectorized way to calculate the cumulative sums of a vector, but with upper and lower limits.

In my case, the input only contains 1's and -1's. You can use this assumption in your answer. Of course, a more general solution is also welcome.

For example:

x     = [1 1 1 1 -1 -1 -1 -1 -1 -1];
upper = 3;
lower = 0;
s     = cumsum(x)                    %// Ordinary cumsum.
s =
     1     2     3     4     3     2     1     0    -1    -2

y     = cumsumlim(x, upper, lower)   %// Cumsum with limits.
y =
     1     2     3     3     2     1     0     0     0     0
                 ^                       ^
                 |                       |
            upper limit             lower limit

When the cumulative sum reaches the upper limit (at the 3rd element), it won't increase anymore. Likewise, when the cumulative sum reaches the lower limit (at the 7th element), it won't decrease anymore. A for-loop version would be like this:

function y = cumsumlim(x, upper, lower)

y = zeros(size(x));
y(1) = x(1);

for i = 2 : numel(x)
    y(i) = y(i-1) + x(i);
    y(i) = min(y(i), upper);
    y(i) = max(y(i), lower);
end

end

Do you have any ideas?

Stewie Griffin
  • 14,889
  • 11
  • 39
  • 70
Ben.W
  • 199
  • 1
  • 9
  • I don't quite understand the example output you have showed. Can you be more verbose and explain how you arrived at your desired output? How exactly do the upper and lower limits come into play with your "`cumsum`" function? – rayryeng Aug 06 '15 at 06:43
  • 1
    Does `x` contain `1's` and `-1's` only? – Divakar Aug 06 '15 at 06:50
  • @BenW either specify limits on what `x` can contain or choose a more representative `x`. Firstly have it go more than `1` above and/or below the limits, have it breach the same limit more than once and most importantly as Divakar mentions, if `x` can contain other numbers please include some – Dan Aug 06 '15 at 06:54
  • @rayryeng , I've added more explanation. Hope that will be helpful. – Ben.W Aug 06 '15 at 06:54
  • 1
    @Divakar, in my case, the input only contains 1's and -1's. And also, I've added a for-loop version code of what I wanted to do. Hope that will help to explain. – Ben.W Aug 06 '15 at 07:02
  • Should this cumsum be able reach max more than once? – Ikaros Aug 06 '15 at 09:33
  • @HamtaroWarrior, yes, of course. – Ben.W Aug 06 '15 at 09:42
  • Just asking because it'd be quite quick otherwise ;) – Ikaros Aug 06 '15 at 09:43
  • @Ben.W, I suggest you accept Luis' answer (it's a nice gesture when someone answers one of your questions). It's the tick mark on the left side of the answer. (You could also accept mine if you for some reason didn't like his solution). – Stewie Griffin Sep 14 '15 at 06:54
  • @StewieGriffin, thanks for the reminder. I found both answers useful, so I up-voted both. However, I haven't found a solution to my problem yet, that's why I didn't accept any answer. Anyway, it seems like no one else is going to answer, so I'm happy to accept Luis' answer. – Ben.W Sep 17 '15 at 01:38

2 Answers2

5

This is a somewhat hackish solution, but perhaps worth mentioning.

You can do the sum using a signed integer data type, and exploit the inherent limits of that data type. For this to work, the input needs to be converted to that integer type and multiplied by the appropiate factor, and an initial offset needs to be applied. The factor and offset are chosen as a function of lower and upper. After cumsum, the multiplication and offset are undone to obtain the desired result.

In your example, data type int8 suffices; and the required factor and offset are 85 and -128 respectively:

x = [1 1 1 1 -1 -1 -1 -1 -1 -1];
result = cumsum([-128 int8(x)*85]); %// integer sum, with factor and initial offset
result = (double(result(2:end))+128)/85; %// undo factor and offset

which gives

result =
     1     2     3     3     2     1     0     0     0     0
Luis Mendo
  • 110,752
  • 13
  • 76
  • 147
4

I won't provide you with a magic vectorized way to do this, but I'll provide you with some data that probably will help you get on with your work.

Your cumsumlim function is very fast!

tic
for ii = 1:100
    y = cumsumlim(x,3,0);
end
t = toc;
disp(['Length of vector: ' num2str(numel(x))])
disp(['Total time for one execution: ' num2str(t*10), ' ms.'])
Length of vector: 65000
Total time for one execution: 1.7965 ms.

I really doubt this is your bottleneck. Have you tried profiling the code?

Stewie Griffin
  • 14,889
  • 11
  • 39
  • 70