3

Here's a relatively simply one. Let's say I have the following vector ('V1'):

1
1
1
2
2
2
2
3
3
4
4
4
4
5
5
5

I want to create a second vector, V2, which starts at 1 and increases for each iteration of a value in V1, but then resets at a new value of V1. So for instance:

1
2
3
1
2
3
4
1
2
1
2
3
4
1
2
3

There may be a single iteration of a value in V1, or as many as 6.

The solution might use a for loop, but I imagine there is a simpler form without needing a loop ('repmat' comes to mind).

PyjamaNinja
  • 293
  • 2
  • 8

5 Answers5

6

Another suggestion without a loop.

First count the number of repeated values

a=histc(v1,unique(v1));

Construct counting array

b = ones(1,sum(a));

Now counter the cumulated sum the appropriate places:

a = a(1:end-1);
b(cumsum(a)+1) = b(cumsum(a)+1) - a;

finally take the cumulated sum

cumsum(b)

In total

v1 = [1,1,1,1,2,2,3,3,3,3,3,4];
a=histcounts(v1,[unique(v1),inf]);
b = ones(1,sum(a));
a = a(1:end-1);
b(cumsum(a)+1) = b(cumsum(a)+1) - a;
disp(cumsum(b))

TIMEITs:

Running on random sorted input V1 = sort(randi(100,1e6,1)); I obtain the following timings in Matlab 2017a.

  • Gnovic's first suggestion: 2.852872e-02
  • Gnovic's second suggestion: 2.909344e-02
  • AVK's suggestion: 3.935982e-01
  • RadioJava's suggestion: 2.441206e-02
  • Nicky's suggestion: 9.153147e-03

Code for reference:

function [] = SO()
V1 = sort(randi(100,1e6,1));

t1 = timeit(@() gnovice1(V1)); fprintf("* Gnovic's first suggestion: %d\n",t1);
t2 = timeit(@() gnovice2(V1)); fprintf("* Gnovic's second suggestion: %d\n",t2);
t3 = timeit(@() AVK(V1)); fprintf("* AVK's suggestion: %d\n",t3);
t4 = timeit(@() RadioJava(V1)); fprintf("* RadioJava's suggestion: %d\n",t4);
t5 = timeit(@() Nicky(V1)); fprintf("* Nicky's suggestion: %d\n",t5);


function []=gnovice1(V1)
V2 = accumarray(V1, 1, [], @(x) {1:numel(x)});
V2 = [V2{:}].';

function []=gnovice2(V1)
V2 = ones(size(V1));
V2([find(diff(V1))+1; end]) = 1-accumarray(V1, 1);
V2 = cumsum(V2(1:end-1));

function []=AVK(v)
a= v;
for i=unique(v)'
    a(v==i)= 1:length(a(v==i));
end

function []=RadioJava(vec)
vec = vec(:).';
zero_separated=[1,vec(1:end-1)==vec(2:end)];
c=cumsum(zero_separated);
zeros_ind = ~zero_separated;
d = diff([1 c(zeros_ind)]);
zero_separated(zeros_ind) = -d;
output=cumsum(zero_separated);

function []=Nicky(v1)
v1 = v1(:).';
a=histcounts(v1,[unique(v1),inf]);
b = ones(1,sum(a));
a = a(1:end-1);
b(cumsum(a)+1) = b(cumsum(a)+1) - a;
b = cumsum(b);
Nicky Mattsson
  • 3,052
  • 12
  • 28
5

Assuming V1 is sorted, here's a vectorized solution using accumarray:

V2 = accumarray(V1, 1, [], @(x) {1:numel(x)});
V2 = [V2{:}].';
gnovice
  • 125,304
  • 15
  • 256
  • 359
5

Based on the second approach in this answer:

t = diff([0; find([diff(V1)~=0; true])]);
V2 = ones(sum(t), 1);
V2(cumsum(t(1:end-1))+1) = 1-t(1:end-1);
V2 = cumsum(V2);
Luis Mendo
  • 110,752
  • 13
  • 76
  • 147
3

Solution without a loop

vec =[1 1 1 2 2 2 3 3 3];    
zero_separated=[1,vec(1:end-1)==vec(2:end)]; % 0 at every new set    
c=cumsum(zero_separated); % Temporary cumsum    
zeros_ind = ~zero_separated;    
d = diff([1 c(zeros_ind)]); % deltas in the temporary cumsum
zero_separated(zeros_ind) = -d; % Set zeros ind to delta    
output=cumsum(zero_separated); % Calculate cumsum now

Output

output = 1     2     3     1     2     3     1     2     3

Based on this

rayryeng
  • 102,964
  • 22
  • 184
  • 193
Prostagma
  • 1,756
  • 9
  • 21
  • So something like: >> zero_separated(isnan(zero_separated)) = 1-diff([0 find(isnan(zero_separated))]); >> cumsum(zero_separated)...which works, but ideally the smallest number would be 1 not 0 – PyjamaNinja Apr 11 '18 at 17:46
  • 1
    fixed and a better and simpler solution – Prostagma Apr 11 '18 at 18:08
2
v = [1;1;1;2;2;2;2;3;3;4;4;4;4;5;5;5];
a= v;
for i=unique(v)'
    a(v==i)= 1:length(a(v==i));
end
disp(a)
AVK
  • 2,130
  • 3
  • 15
  • 25