Element-wise array replication according to a count [duplicate]
Asked Answered
L

4

8

My question is similar to this one, but I would like to replicate each element according to a count specified in a second array of the same size.

An example of this, say I had an array v = [3 1 9 4], I want to use rep = [2 3 1 5] to replicate the first element 2 times, the second three times, and so on to get [3 3 1 1 1 9 4 4 4 4 4].

So far I'm using a simple loop to get the job done. This is what I started with:

vv = [];
for i=1:numel(v)
    vv = [vv repmat(v(i),1,rep(i))];
end

I managed to improve by preallocating space:

vv = zeros(1,sum(rep));
c = cumsum([1 rep]);
for i=1:numel(v)
    vv(c(i):c(i)+rep(i)-1) = repmat(v(i),1,rep(i));
end

However I still feel there has to be a more clever way to do this... Thanks

Lucy answered 4/3, 2010 at 20:0 Comment(3)
see https://mcmap.net/q/661825/-repeat-copies-of-array-elements-run-length-decoding-in-matlabDibucaine
@Doresoom: I thought I had answered a question like this before, but couldn't find it. I finally hunted it down at the same time as you. The title and tags were quite different, which was what made it a little tough to find.Haarlem
if anyone comes here looking for a numpy solution, check docs.scipy.org/doc/numpy/reference/generated/numpy.repeat.htmlTruancy
H
16

Here's one way I like to accomplish this:

>> index = zeros(1,sum(rep));
>> index(cumsum([1 rep(1:end-1)])) = 1;

index =

     1     0     1     0     0     1     1     0     0     0     0

>> index = cumsum(index)

index =

     1     1     2     2     2     3     4     4     4     4     4

>> vv = v(index)

vv =

     3     3     1     1     1     9     4     4     4     4     4

This works by first creating an index vector of zeroes the same length as the final count of all the values. By performing a cumulative sum of the rep vector with the last element removed and a 1 placed at the start, I get a vector of indices into index showing where the groups of replicated values will begin. These points are marked with ones. When a cumulative sum is performed on index, I get a final index vector that I can use to index into v to create the vector of heterogeneously-replicated values.

Haarlem answered 4/3, 2010 at 20:10 Comment(4)
could you add some comments of how this works?Crotty
@Nathan: Already ahead of ya. =)Haarlem
definitely a clever way of using cumsum.. Thanks!Lucy
Be careful, this solution works only if all elements of rep are positive. If you don't want to repeat some elements by setting some elements of rep to zeros, it will fail. v = [3 1 9 4] and rep = [2 3 1 0] result into [3 3 1 1 1 9 4], giving an extra element.Petulah
O
2

To add to the list of possible solutions, consider this one:

vv = cellfun(@(a,b)repmat(a,1,b), num2cell(v), num2cell(rep), 'UniformOutput',0);
vv = [vv{:}];

This is much slower than the one by gnovice..

Ordure answered 4/3, 2010 at 20:46 Comment(1)
You could actually use ARRAYFUN and avoid the calls to NUM2CELL, but it would still be much slower: https://mcmap.net/q/661825/-repeat-copies-of-array-elements-run-length-decoding-in-matlab/….Haarlem
P
0

What you are trying to do is to run-length decode. A high level reliable/vectorized utility is the FEX submission rude():

% example inputs
counts = [2, 3, 1];
values = [24,3,30];

the result

rude(counts, values)
ans =
    24    24     3     3     3    30

Note that this function performs the opposite operation as well, i.e. run-length encodes a vector or in other words returns values and the corresponding counts.

Placido answered 21/8, 2013 at 13:23 Comment(0)
P
0

accumarray function can be used to make the code work if zeros exit in rep array

function vv = repeatElements(v, rep)
index = accumarray(cumsum(rep)'+1, 1);
vv = v(cumsum(index(1:end-1))+1);
end

This works similar to solution of gnovice, except that indices are accumulated instead being assigned to 1. This allows to skip some indices (3 and 6 in the example below) and remove corresponding elements from the output.

>> v = [3 1 42 9 4 42];
>> rep = [2 3 0 1 5 0];
>> index = accumarray(cumsum(rep)'+1, 1)'

index =

     0     0     1     0     0     2     1     0     0     0     0     2

>> cumsum(index(1:end-1))+1

ans =

     1     1     2     2     2     4     5     5     5     5     5

>> vv = v(cumsum(index(1:end-1))+1)

vv =

     3     3     1     1     1     9     4     4     4     4     4
Petulah answered 17/10, 2014 at 12:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.