I'm trying to insert multiple values into an array using a 'values' array and a 'counter' array. For example, if:
a=[1,3,2,5]
b=[2,2,1,3]
I want the output of some function
c=somefunction(a,b)
to be
c=[1,1,3,3,2,5,5,5]
Where a(1) recurs b(1) number of times, a(2) recurs b(2) times, etc...
Is there a built-in function in MATLAB that does this? I'd like to avoid using a for loop if possible. I've tried variations of 'repmat()' and 'kron()' to no avail.
This is basically Run-length encoding
.
There is finally (as of R2015a) a built-in and documented function to do this,
repelem
. The following syntax, where the second argument is a vector, is relevant here:Or put another way, "Each element of
N
specifies the number of times to repeat the corresponding element ofV
."Example:
Problem Statement
We have an array of values,
vals
and runlengths,runlens
:We are needed to repeat each element in
vals
times each corresponding element inrunlens
. Thus, the final output would be:Prospective Approach
One of the fastest tools with MATLAB is
cumsum
and is very useful when dealing with vectorizing problems that work on irregular patterns. In the stated problem, the irregularity comes with the different elements inrunlens
.Now, to exploit
cumsum
, we need to do two things here: Initialize an array ofzeros
and place "appropriate" values at "key" positions over the zeros array, such that after "cumsum
" is applied, we would end up with a final array of repeatedvals
ofrunlens
times.Steps: Let's number the above mentioned steps to give the prospective approach an easier perspective:
1) Initialize zeros array: What must be the length? Since we are repeating
runlens
times, the length of the zeros array must be the summation of allrunlens
.2) Find key positions/indices: Now these key positions are places along the zeros array where each element from
vals
start to repeat. Thus, forrunlens = [2,2,1,3]
, the key positions mapped onto the zeros array would be:3) Find appropriate values: The final nail to be hammered before using
cumsum
would be to put "appropriate" values into those key positions. Now, since we would be doingcumsum
soon after, if you think closely, you would need adifferentiated
version ofvalues
withdiff
, so thatcumsum
on those would bring back ourvalues
. Since these differentiated values would be placed on a zeros array at places separated by therunlens
distances, after usingcumsum
we would have eachvals
element repeatedrunlens
times as the final output.Solution Code
Here's the implementation stitching up all the above mentioned steps -
Pre-allocation Hack
As could be seen that the above listed code uses pre-allocation with zeros. Now, according to this UNDOCUMENTED MATLAB blog on faster pre-allocation, one can achieve much faster pre-allocation with -
Wrapping up: Function Code
To wrap up everything, we would have a compact function code to achieve this run-length decoding like so -
Benchmarking
Benchmarking Code
Listed next is the benchmarking code to compare runtimes and speedups for the stated
cumsum+diff
approach in this post over the othercumsum-only
based approach onMATLAB 2014B
-Associated function code for
rld_cumsum.m
:Runtime and Speedup Plots
Conclusions
The proposed approach seems to be giving us a noticeable speedup over the
cumsum-only
approach, which is about 3x!Why is this new
cumsum+diff
based approach better than the previouscumsum-only
approach?Well, the essence of the reason lies at the final step of the
cumsum-only
approach that needs to map the "cumsumed" values intovals
. In the newcumsum+diff
based approach, we are doingdiff(vals)
instead for which MATLAB is processing onlyn
elements (where n is the number of runLengths) as compared to the mapping ofsum(runLengths)
number of elements for thecumsum-only
approach and this number must be many times more thann
and therefore the noticeable speedup with this new approach!There's no built-in function I know of, but here's one solution:
Explanation:
A vector of zeroes is first created of the same length as the output array (i.e. the sum of all the replications in
b
). Ones are then placed in the first element and each subsequent element representing where the start of a new sequence of values will be in the output. The cumulative sum of the vectorindex
can then be used to index intoa
, replicating each value the desired number of times.For the sake of clarity, this is what the various vectors look like for the values of
a
andb
given in the question:EDIT: For the sake of completeness, there is another alternative using ARRAYFUN, but this seems to take anywhere from 20-100 times longer to run than the above solution with vectors up to 10,000 elements long:
Benchmarks
Updated for R2015b:
repelem
now fastest for all data sizes.Tested functions:
repelem
function that was added in R2015acumsum
solution (rld_cumsum
)cumsum
+diff
solution (rld_cumsum_diff
)accumarray
solution (knedlsepp5cumsumaccumarray
) from this postnaive_jit_test.m
) to test the just-in-time compilerResults of
test_rld.m
on R2015b:Old timing plot using R2015a here.
Findings:
repelem
is always the fastest by roughly a factor of 2.rld_cumsum_diff
is consistently faster thanrld_cumsum
.repelem
is fastest for small data sizes (less than about 300-500 elements)rld_cumsum_diff
becomes significantly faster thanrepelem
around 5 000 elementsrepelem
becomes slower thanrld_cumsum
somewhere between 30 000 and 300 000 elementsrld_cumsum
has roughly the same performance asknedlsepp5cumsumaccumarray
naive_jit_test.m
has nearly constant speed and on par withrld_cumsum
andknedlsepp5cumsumaccumarray
for smaller sizes, a little faster for large sizesOld rate plot using R2015a here.
Conclusion
Use
repelem
below about 5 000 elements and the.cumsum
+diff
solution aboveThe performance problems in MATLAB's built-in
repelem
have been fixed as of R2015b. I have run thetest_rld.m
program from chappjc's post in R2015b, andrepelem
is now faster than other algorithms by about a factor 2: