在MATLAB中拆分矢量

时间:2015-04-25 01:34:01

标签: arrays matlab vector

我正在尝试优雅地分割矢量。例如,

vec = [1 2 3 4 5 6 7 8 9 10]

根据0和1的另一个相同长度的向量,其中1表示向量应该被分割的位置 - 或者更确切地说:

cut = [0 0 0 1 0 0 0 0 1 0]

给我们一个类似于以下内容的单元格输出:

[1 2 3] [5 6 7 8] [10]

6 个答案:

答案 0 :(得分:12)

解决方案代码

您可以使用cumsum& accumarray提供有效的解决方案 -

%// Create ID/labels for use with accumarray later on
id = cumsum(cut)+1   

%// Mask to get valid values from cut and vec corresponding to ones in cut
mask = cut==0        

%// Finally get the output with accumarray using masked IDs and vec values 
out = accumarray(id(mask).',vec(mask).',[],@(x) {x})

基准

以下是在列出的三种最常用方法上使用大量输入来解决此问题时的一些性能数字 -

N = 100000;  %// Input Datasize

vec = randi(100,1,N); %// Random inputs
cut = randi(2,1,N)-1;

disp('-------------------- With CUMSUM + ACCUMARRAY')
tic
id = cumsum(cut)+1;
mask = cut==0;
out = accumarray(id(mask).',vec(mask).',[],@(x) {x});
toc

disp('-------------------- With FIND + ARRAYFUN')
tic
N = numel(vec);
ind = find(cut);
ind_before = [ind-1 N]; ind_before(ind_before < 1) = 1;
ind_after = [1 ind+1]; ind_after(ind_after > N) = N;
out = arrayfun(@(x,y) vec(x:y), ind_after, ind_before, 'uni', 0);
toc

disp('-------------------- With CUMSUM + ARRAYFUN')
tic
cutsum = cumsum(cut);
cutsum(cut == 1) = NaN;  %Don't include the cut indices themselves
sumvals = unique(cutsum);      % Find the values to use in indexing vec for the output
sumvals(isnan(sumvals)) = [];  %Remove NaN values from sumvals
output = arrayfun(@(val) vec(cutsum == val), sumvals, 'UniformOutput', 0);
toc

<强>运行时

-------------------- With CUMSUM + ACCUMARRAY
Elapsed time is 0.068102 seconds.
-------------------- With FIND + ARRAYFUN
Elapsed time is 0.117953 seconds.
-------------------- With CUMSUM + ARRAYFUN
Elapsed time is 12.560973 seconds.

特殊情况:如果您可能运行 1 ,则需要修改下面列出的一些内容 -

%// Mask to get valid values from cut and vec corresponding to ones in cut
mask = cut==0  

%// Setup IDs differently this time. The idea is to have successive IDs.
id = cumsum(cut)+1
[~,~,id] = unique(id(mask))

%// Finally get the output with accumarray using masked IDs and vec values 
out = accumarray(id(:),vec(mask).',[],@(x) {x})

使用这种情况运行示例 -

>> vec
vec =
     1     2     3     4     5     6     7     8     9    10
>> cut
cut =
     1     0     0     1     1     0     0     0     1     0
>> celldisp(out)
out{1} =
     2
     3
out{2} =
     6
     7
     8
out{3} =
    10

答案 1 :(得分:7)

对于这个问题,一个方便的函数是cumsum,它可以创建剪切数组的累积和。生成输出单元阵列的代码如下:

vec = [1 2 3 4 5 6 7 8 9 10];
cut = [0 0 0 1 0 0 0 0 1 0];

cutsum = cumsum(cut);
cutsum(cut == 1) = NaN;  %Don't include the cut indices themselves
sumvals = unique(cutsum);      % Find the values to use in indexing vec for the output
sumvals(isnan(sumvals)) = [];  %Remove NaN values from sumvals
output = {};
for i=1:numel(sumvals)
    output{i} = vec(cutsum == sumvals(i)); %#ok<SAGROW>
end

如另一个答案所示,您可以使用arrayfun创建包含结果的单元格数组。要在此处应用,您可以使用以下行替换for循环(以及输出的初始化):

output = arrayfun(@(val) vec(cutsum == val), sumvals, 'UniformOutput', 0);

这很好,因为它不会最终增长输出单元阵列。

这个例程的关键特征是变量cutsum,最终看起来像这样:

cutsum =
     0     0     0   NaN     1     1     1     1   NaN     2

然后我们需要做的就是使用它创建索引以从原始vec数组中提取数据。我们从零循环到最大并拉出匹配值。请注意,此例程处理可能出现的某些情况。例如,它在cut数组的最开始和结尾处理1个值,并且它优雅地处理cut数组中的重复值,而不在输出中创建空数组。这是因为使用unique来创建要在cutsum中搜索的值集,以及我们在sumvals数组中抛出NaN值的事实。

您可以使用-1代替NaN作为不使用剪切位置的信号标记,但我喜欢NaN以提高可读性。 -1值可能更有效,因为你所要做的就是截断sumvals数组中的第一个元素。我只是偏爱使用NaN作为信号旗。

这是一个包含结果的单元格数组:

output{1} =
     1     2     3
output{2} =
     5     6     7     8
output{3} =
    10

我们需要处理一些奇怪的情况。考虑一下情况:

vec = [1 2 3 4 5 6 7 8 9 10 11 12 13 14];
cut = [1 0 0 1 1 0 0 0 0 1  0  0  0  1];

那里有重复的1,以及开头和结尾的1。这个例程正确处理所有这些,没有任何空集:

output{1} = 
     2     3
output{2} =
     6     7     8     9
output{3} = 
    11    12    13

答案 2 :(得分:6)

您可以结合使用findarrayfun

来执行此操作
vec = [1 2 3 4 5 6 7 8 9 10];
N = numel(vec);
cut = [0 0 0 1 0 0 0 0 1 0];
ind = find(cut);
ind_before = [ind-1 N]; ind_before(ind_before < 1) = 1;
ind_after = [1 ind+1]; ind_after(ind_after > N) = N;
out = arrayfun(@(x,y) vec(x:y), ind_after, ind_before, 'uni', 0);

我们得到:

>> celldisp(out)

out{1} =

     1     2     3         

out{2} =

     5     6     7     8    

out{3} =

    10

那么这是如何工作的?好吧,第一行定义了你的输入向量,第二行定义了这个向量中有多少个元素,第三行表示你的cut向量,它定义了我们需要在向量中切割的位置。接下来,我们使用find来确定cut中非零的位置,这些位置对应于向量中的分割点。如果您注意到,分割点确定了我们需要停止收集元素并开始收集元素的位置。

但是,我们需要考虑向量的开始以及结束。 ind_after告诉我们需要开始收集值的位置,ind_before告诉我们需要停止收集值的位置。要计算这些起始位置和结束位置,只需取find的结果并分别加1和减去1。

ind_afterind_before中的每个相应位置都告诉我们需要一起开始和停止收集值的位置。为了适应向量的开头,ind_after需要在开头插入索引1,因为索引1是我们应该从头开始收集值的位置。同样,N需要在ind_before的末尾插入,因为这是我们需要停止在数组末尾收集值的地方。

现在对于ind_afterind_before,存在一种退化情况,其中切割点可能位于向量的末尾或开头。如果是这种情况,则减1或加1将生成超出界限的开始和停止位置。我们在第4行和第5行代码中检查这一点,只需将它们设置为1或N,具体取决于我们是否在数组的开头或结尾。

最后一行代码使用arrayfun并遍历每对ind_afterind_before以切入我们的向量。每个结果都放在一个单元格数组中,我们的输出如下。

我们可以通过在cut的开头和结尾放置1以及其间的一些值来检查退化情况:

vec = [1 2 3 4 5 6 7 8 9 10];
cut = [1 0 0 1 0 0 0 1 0 1];

使用这个例子和上面的代码,我们得到:

>> celldisp(out)

out{1} =

     1

out{2} =

     2     3         

out{3} =

     5     6     7

out{4} =

     9         

out{5} =

    10

答案 3 :(得分:6)

又一种方式,但这次没有任何循环或累积......

lengths = diff(find([1 cut 1])) - 1;    % assuming a row vector
lengths = lengths(lengths > 0);
data = vec(~cut);
result = mat2cell(data, 1, lengths);    % also assuming a row vector

diff(find(...))构造给出了从每个标记到下一个标记的距离 - 我们用[1 cut 1]附加边界标记以捕获任何触及末端的零点。但是,每个长度都包含其标记,因此我们减去1来解释它,并删除任何仅覆盖连续标记的长度,这样我们就不会在输出中得到任何不需要的空单元格。

对于数据,我们屏蔽掉与标记相对应的任何元素,因此我们只有我们想要分区的有效部分。最后,随着准备拆分的数据和拆分它的长度,这恰恰是mat2cell的用途。

另外,使用@Divakar's benchmark code;

-------------------- With CUMSUM + ACCUMARRAY
Elapsed time is 0.272810 seconds.
-------------------- With FIND + ARRAYFUN
Elapsed time is 0.436276 seconds.
-------------------- With CUMSUM + ARRAYFUN
Elapsed time is 17.112259 seconds.
-------------------- With mat2cell
Elapsed time is 0.084207 seconds.

...只是说&#39; ;)

答案 4 :(得分:2)

这就是你需要的:

function spl  = Splitting(vec,cut)
n=1;
j=1;
for i=1:1:length(b)
    if cut(i)==0 
        spl{n}(j)=vec(i);
        j=j+1;
    else 
        n=n+1;
        j=1;
    end
end
end

尽管我的方法很简单,但它在性能方面排名第二:

-------------------- With CUMSUM + ACCUMARRAY
Elapsed time is 0.264428 seconds.
-------------------- With FIND + ARRAYFUN
Elapsed time is 0.407963 seconds.
-------------------- With CUMSUM + ARRAYFUN
Elapsed time is 18.337940 seconds.
-------------------- SIMPLE
Elapsed time is 0.271942 seconds.

答案 5 :(得分:1)

不幸的是,没有&#39;反向连接&#39;在MATLAB中。如果您想解决这样的问题,可以尝试以下代码。如果您有两个分割点,最终会生成三个向量,它会为您提供所需的内容。如果你想要更多分割,你需要在循环后修改代码。

结果为n矢量形式。要将它们放入单元格,请在结果上使用num2cell。

pos_of_one = 0;

% The loop finds the split points and puts their positions into a vector.
for kk = 1 : length(cut)
    if cut(1,kk) == 1
        pos_of_one = pos_of_one + 1;
        A(1,one_pos) = kk;
    end
end

F = vec(1 : A(1,1) - 1);
G = vec(A(1,1) + 1 : A(1,2) - 1);
H = vec(A(1,2) + 1 : end);