MATLAB中的行程解码

时间:2015-02-13 14:07:21

标签: performance matlab run-length-encoding

为了巧妙地使用线性索引或accumarray,我有时觉得需要根据run-length encoding生成序列。由于没有内置函数,我要求最有效的方法来解码在RLE中编码的序列。

规格:

为了使这个公平比较,我想为该功能设置一些规范:

  • 如果指定了相同长度的可选第二个参数values,则输出应该根据这些值,否则只是值1:length(runLengths)
  • 优雅地处理:
    • runLengths
    • 中的零
    • values是一个单元格数组。
  • 输出向量应与runLengths
  • 具有相同的列/行格式

简而言之:该函数应该等同于以下代码:

function V = runLengthDecode(runLengths, values)
[~,V] = histc(1:sum(runLengths), cumsum([1,runLengths(:).']));
if nargin>1
    V = reshape(values(V), 1, []);
end
V = shiftdim(V, ~isrow(runLengths));
end

示例:

以下是一些测试用例

runLengthDecode([0,1,0,2])
runLengthDecode([0,1,0,4], [1,2,4,5].')
runLengthDecode([0,1,0,2].', [10,20,30,40])
runLengthDecode([0,3,1,0], {'a','b',1,2})

及其输出:

>> runLengthDecode([0,1,0,2])
ans =
     2     4     4

>> runLengthDecode([0,1,0,4], [1,2,4,5].')
ans =    
     2     5     5     5     5

>> runLengthDecode([0,1,0,2].', [10,20,30,40])
ans =
    20
    40
    40

>> runLengthDecode([0,3,1,0],{'a','b',1,2})
ans = 
    'b'    'b'    'b'    [1]

4 个答案:

答案 0 :(得分:6)

为了找出哪个是最有效的解决方案,我们提供了一个评估性能的测试脚本。第一个图描绘了矢量runLengths长度增长的运行时间,其中条目均匀分布,最大长度为200. modification of gnovice's solution是最快的, Divakar &#39 ; s解决方案位居第二。 Speed comparison

第二个图使用几乎相同的测试数据,但它包括长度2000的初始运行。这主要影响两个bsxfun解决方案,而其他解决方案的表现非常相似。

Speed comparison

测试表明modification gnovice original answer将是最高效的。


如果您想自己进行速度比较,请参阅上面用于生成上图的代码。

function theLastRunLengthDecodingComputationComparisonYoullEverNeed()
Funcs =  {@knedlsepp0, ...
          @LuisMendo1bsxfun, ...
          @LuisMendo2cumsum, ...
          @gnovice3cumsum, ...
          @Divakar4replicate_bsxfunmask, ...
          @knedlsepp5cumsumaccumarray
          };    
%% Growing number of runs, low maximum sizes in runLengths
ns = 2.^(1:25);
paramGenerators{1} = arrayfun(@(n) @(){randi(200,n,1)}, ns,'uni',0);
paramGenerators{2} = arrayfun(@(n) @(){[2000;randi(200,n,1)]}, ns,'uni',0);
for i = 1:2
    times = compareFunctions(Funcs, paramGenerators{i}, 0.5);
    finishedComputations = any(~isnan(times),2);
    h = figure('Visible', 'off');
    loglog(ns(finishedComputations), times(finishedComputations,:));
    legend(cellfun(@func2str,Funcs,'uni',0),'Location','NorthWest','Interpreter','none');
    title('Runtime comparison for run length decoding - Growing number of runs');
    xlabel('length(runLengths)'); ylabel('seconds');
    print(['-f',num2str(h)],'-dpng','-r100',['RunLengthComparsion',num2str(i)]);
end
end

function times = compareFunctions(Funcs, paramGenerators, timeLimitInSeconds)
if nargin<3
    timeLimitInSeconds = Inf;
end
times = zeros(numel(paramGenerators),numel(Funcs));
for i = 1:numel(paramGenerators)
    Params = feval(paramGenerators{i});
    for j = 1:numel(Funcs)
        if max(times(:,j))<timeLimitInSeconds
            times(i,j) = timeit(@()feval(Funcs{j},Params{:}));
        else
            times(i,j) = NaN;
        end
    end
end
end
%% // #################################
%% // HERE COME ALL THE FANCY FUNCTIONS
%% // #################################
function V = knedlsepp0(runLengths, values)
[~,V] = histc(1:sum(runLengths), cumsum([1,runLengths(:).']));%'
if nargin>1
    V = reshape(values(V), 1, []);
end
V = shiftdim(V, ~isrow(runLengths));
end

%% // #################################
function V = LuisMendo1bsxfun(runLengths, values)
nn = 1:numel(runLengths);
if nargin==1 %// handle one-input case
    values = nn;
end
V = values(nonzeros(bsxfun(@times, nn,...
    bsxfun(@le, (1:max(runLengths)).', runLengths(:).'))));
if size(runLengths,1)~=size(values,1) %// adjust orientation of output vector
    V = V.'; %'
end
end

%% // #################################
function V = LuisMendo2cumsum(runLengths, values)
if nargin==1 %// handle one-input case
    values = 1:numel(runLengths);
end
[ii, ~, jj] = find(runLengths(:));
V(cumsum(jj(end:-1:1))) = 1;
V = values(ii(cumsum(V(end:-1:1))));
if size(runLengths,1)~=size(values,1) %// adjust orientation of output vector
    V = V.'; %'
end
end

%% // #################################
function V = gnovice3cumsum(runLengths, values)
isColumnVector =  size(runLengths,1)>1;
if nargin==1 %// handle one-input case
    values = 1:numel(runLengths);
end
values = reshape(values(runLengths~=0),1,[]);
if isempty(values) %// If there are no runs
    V = []; return;
end
runLengths = nonzeros(runLengths(:));
index = zeros(1,sum(runLengths));
index(cumsum([1;runLengths(1:end-1)])) = 1;
V = values(cumsum(index));
if isColumnVector %// adjust orientation of output vector
    V = V.'; %'
end
end
%% // #################################
function V = Divakar4replicate_bsxfunmask(runLengths, values)
if nargin==1   %// Handle one-input case
    values = 1:numel(runLengths);
end

%// Do size checking to make sure that both values and runlengths are row vectors.
if size(values,1) > 1
    values = values.'; %//'
end
if size(runLengths,1) > 1
    yes_transpose_output = false;
    runLengths = runLengths.'; %//'
else
    yes_transpose_output = true;
end

maxlen = max(runLengths);

all_values = repmat(values,maxlen,1);
%// OR all_values = values(ones(1,maxlen),:);

V = all_values(bsxfun(@le,(1:maxlen)',runLengths)); %//'

%// Bring the shape of V back to the shape of runlengths
if yes_transpose_output
    V = V.'; %//'
end
end
%% // #################################
function V = knedlsepp5cumsumaccumarray(runLengths, values)
isRowVector = size(runLengths,2)>1;
%// Actual computation using column vectors
V = cumsum(accumarray(cumsum([1; runLengths(:)]), 1));
V = V(1:end-1);
%// In case of second argument
if nargin>1
    V = reshape(values(V),[],1);
end
%// If original was a row vector, transpose
if isRowVector
    V = V.'; %'
end
end

答案 1 :(得分:6)

从R2015a开始,函数repelem是执行此操作的最佳选择:

function V = runLengthDecode(runLengths, values)
if nargin<2
    values = 1:numel(runLengths);
end
V = repelem(values, runLengths);
end

对于R2015a之前的版本,这是一个快速的选择:

替代repelem

我感觉 gnovice 的方法可以通过使用比预处理掩码更好的zero-runLength修复来改进。 所以我给了accumarray一个镜头。这似乎为解决方案带来了又一次提升:

function V = runLengthDecode(runLengths, values)
%// Actual computation using column vectors
V = cumsum(accumarray(cumsum([1; runLengths(:)]), 1));
V = V(1:end-1);
%// In case of second argument
if nargin>1
    V = reshape(values(V),[],1);
end
%// If original was a row vector, transpose
if size(runLengths,2)>1
    V = V.'; %'
end
end

答案 2 :(得分:5)

方法1

这应该相当快。它用 bsxfun要创建大小为numel(runLengths) x numel(runLengths)的矩阵,因此它可能不适合大量输入。

function V = runLengthDecode(runLengths, values)
nn = 1:numel(runLengths);
if nargin==1 %// handle one-input case
    values = nn;
end
V = values(nonzeros(bsxfun(@times, nn,...
    bsxfun(@le, (1:max(runLengths)).', runLengths(:).'))));
if size(runLengths,1)~=size(values,1) %// adjust orientation of output vector
    V = V.';
end

方法2

这种方法基于cumsum,是对this other answer中使用的方法的改编。它比方法1使用更少的内存。

function V = runLengthDecode2(runLengths, values)
if nargin==1 %// handle one-input case
    values = 1:numel(runLengths);
end
[ii, ~, jj] = find(runLengths(:));
V(cumsum(jj(end:-1:1))) = 1;
V = values(ii(cumsum(V(end:-1:1))));
if size(runLengths,1)~=size(values,1) %// adjust orientation of output vector
    V = V.';
end

答案 3 :(得分:4)

此处介绍的解决方案基本上分两步执行run-length decoding -

  1. 将所有values复制到最大runLengths
  2. 使用bsxfun的屏蔽功能从每列中选择相应的runlengths
  3. 功能代码中的其他内容是处理输入和输出大小以满足问题中设置的要求。

    下面列出的功能代码是one of my previous answers to a similar problem的“已清理”版本。这是代码 -

    function V = replicate_bsxfunmask(runLengths, values)
    
    if nargin==1   %// Handle one-input case
        values = 1:numel(runLengths);
    end
    
    %// Do size checking to make sure that both values and runlengths are row vectors.
    if size(values,1) > 1
        values = values.'; %//'
    end
    if size(runLengths,1) > 1
        yes_transpose_output = false;
        runLengths = runLengths.'; %//'
    else
        yes_transpose_output = true;
    end
    
    maxlen = max(runLengths);
    
    all_values = repmat(values,maxlen,1);
    %// OR all_values = values(ones(1,maxlen),:);
    
    V = all_values(bsxfun(@le,(1:maxlen)',runLengths)); %//'
    
    %// Bring the shape of V back to the shape of runlengths
    if yes_transpose_output
        V = V.'; %//'
    end
    
    return;
    

    下面列出的代码将是混合代码(cumsum + replicate_bsxfunmask),并且当您拥有大量异常值或非常大的异常值时,它们将非常适合。另外,为了简单起见,目前这只适用于数字数组。这是实施 -

    function out = replicate_bsxfunmask_v2(runLengths, values)
    
    if nargin==1                       %// Handle one-input case
        values = 1:numel(runLengths);
    end
    
    if size(values,1) > 1
        values = values.';  %//'
    end
    
    if size(runLengths,1) > 1
        yes_transpose_output = true;
        runLengths = runLengths.';  %//'
    else
        yes_transpose_output = false;
    end
    
    %// Regularize inputs
    values = values(runLengths>0);
    runLengths = runLengths(runLengths>0);
    
    %// Main portion of code
    thresh = 200; %// runlengths threshold that are to be processed with cumsum
    
    crunLengths = cumsum(runLengths); %%// cumsums of runlengths
    mask = runLengths >= thresh; %// mask of runlengths above threshold
    starts = [1 crunLengths(1:end-1)+1]; %// starts of each group of runlengths
    
    mask_ind = find(mask); %// indices of mask
    
    post_mark = starts(mask);
    negt_mark = crunLengths(mask)+1;
    
    if  ~isempty(negt_mark) && negt_mark(end) > crunLengths(end)
        negt_mark(end) = [];
    end
    
    %// Create array & set starts markers for starts of runlengths above thresh
    marked_out = zeros(1,crunLengths(end));
    marked_out(post_mark) = mask_ind;
    marked_out(negt_mark) = marked_out(negt_mark) -1*mask_ind(1:numel(negt_mark));
    
    %// Setup output array with the cumsumed version of marked array
    out = cumsum(marked_out);
    
    %// Mask for final ouput to decide between large and small runlengths
    thresh_mask = out~=0;
    
    %// Fill output array with cumsum and then rep-bsxfun based approaches
    out(thresh_mask) = values(out(thresh_mask));
    
    values = values(~mask);
    runLengths = runLengths(~mask);
    
    maxlen = max(runLengths);
    all_values = repmat(values,maxlen,1);
    out(~thresh_mask) = all_values(bsxfun(@le,(1:maxlen)',runLengths)); %//'
    
    if yes_transpose_output
        out = out.';  %//'
    end
    
    return;