Matlab:将大于1(小于-1)的元素转换为1(-1)序列

4

更新:我做了一些测试,发现Jonas的解决方案在处理不同大小的输入向量时是最快的。尤其是像angainor所指出的那样,该解决方案可以很好地扩展到大规模问题上——这是一个重要的测试,因为通常是大规模问题促使我们在SO上提出这些问题。感谢Jonas和tmpearce提供的解决方案——基于对大规模问题的效率考虑,我将答案选为Jonas。

我的问题:我有这个列向量:

Vec = [0; 1; 2; -1; -3; 0; 0; 2; 1; -1];

我希望将每个大于1的元素转换为一个长度等于该元素值的1序列。同样地,我想将每个小于-1的元素转换为一系列负数。因此,我的输出向量应该如下所示:

VecLong = [0; 1; 1; 1; -1; -1; -1; -1; 0; 0; 1; 1; 1; -1];

请注意,每个数字2都被改成了两个数字1,而数字-3被改成了三个数字-1。目前,我是这样解决这个问题的:
VecTemp = Vec;
VecTemp(VecTemp == 0) = 1;
VecLong = NaN(sum(abs(VecTemp)), 1);
c = 1;
for n = 1:length(Vec)
    if abs(Vec(n)) <= 1
        VecLong(c) = Vec(n);
        c = c + 1;
    else
        VecLong(c:c + abs(Vec(n))) = sign(Vec(n));
        c = c + abs(Vec(n));
    end    
end

这种方法并不太优雅,有没有人能提出更好的方法?注意:你可以假设 Vec 只包含整数值。感谢所有建议。

2个回答

3

编辑:我想到了另一种(有点难懂)但比你所写的循环更快的方法。

for rep=1:100000
    #% original loop-based solution
end
toc
Elapsed time is 2.768822 seconds.

#% bsxfun-based indexing alternative
tic;
for rep=1:100000
TempVec=abs(Vec);TempVec(Vec==0)=1;
LongVec = sign(Vec(sum(bsxfun(@gt,1:sum(TempVec),cumsum(TempVec)))+1))
end
toc
Elapsed time is 1.798339 seconds.

相比原始版本,这个答案的可扩展性也很好,至少在某个点上有一个性能甜点。

Vec = repmat(OrigVec,10,1);
#% test with 100,000 loops
#% loop-based solution:
Elapsed time is 19.005226 seconds.
#% bsxfun-based solution:
Elapsed time is 4.411316 seconds.

Vec = repmat(OrigVer,1000,1);
#% test with 1,000 loops - 100,000 would be horribly slow
#% loop-based solution:
Elapsed time is 18.105728 seconds.
#% bsxfun-based solution:
Elapsed time is 98.699396 seconds.

bsxfun将向量扩展为矩阵,然后通过求和将其折叠。 对于非常大的向量,与循环相比,这样做需要更多的内存,因此它会失败。 但在此之前,它表现得非常好。


原始的慢速答案:

以下是一行代码:

out=cell2mat(arrayfun(@(x) repmat(((x>0)*2)-1+(x==0),max(1,abs(x)),1),Vec,'uni',0));
out' =

     0   1   1   1  -1  -1  -1  -1   0   0   1   1   1  -1

现在发生了什么:

((x>0)*2)-1 + (x==0) #% if an integer is >0, make it a 1, <0 becomes -1, 0 stays 0 

max(1,abs(x)) #% figure out how many times to replicate the value  

arrayfun(@(x) (the above stuff), Vec, 'uni', 0) #% apply the function  
 #% to each element in the array, generating a cell array output

cell2mat( (the above stuff) ) #% convert back to a matrix 

1
注意:如果您使用类似这样的东西,请在代码中编写一些注释,解释它的作用! - tmpearce
是的... arrayfun 也很慢。循环通常比 arrayfuncellfun 快得多,有时甚至与更好的矢量化解决方案相当竞争力,特别是如果预分配(就像你所做的那样)被正确地执行。它们只是因为声名狼藉而不受欢迎。不过一行代码也很有趣,特别是如果速度不是关键 :) - tmpearce
哈!有趣的是你这么说 - 如果你想了解arrayfun和循环之间的差异,可以查看angainor在我的另一个SO问题这里提供的绝佳答案。顺便说一下,我已经给你的回答点赞了,但是由于速度问题,我恐怕无法授予答案标记。 - Colin T Bowers
哦,你说得对。我刚刚修复了它,以备将来参考(因为听起来 Jonas 的答案效果很好,而且速度也很快)。 - tmpearce
1
没问题,感谢修复,也感谢提供解决方案。不过我已经选择了 Jonas 的答案,因为 Jonas 提供的解决方案在处理大型输入向量时非常有效。 - Colin T Bowers
显示剩余2条评论

3
你可以使用传统的cumsum方法来正确重复条目。注意,我正在分配一些临时变量,如果你想将所有内容放在一行中,可以将其清除。
%# create a list of values to repeat
signVec = sign(Vec);

%# create a list of corresponding indices that repeat
%# as often as the value in signVec has to be repeated

tmp = max(abs(Vec),1); %# max: zeros have to be repeated once
index = zeros(sum(tmp),1);
index([1;cumsum(tmp(1:end-1))+1])=1; %# assign ones a pivots for cumsum
index = cumsum(index); %# create repeating indices

%# repeat
out = signVec(index);
out'
out =

     0     1     1     1    -1    -1    -1    -1     0     0     1     1     1    -1

很抱歉,这在R2012b上对我不起作用。第一个问题是signabs函数的第二个参数。我是否漏掉了什么?我的理解是这两个函数只接受一个参数(而且在R2012b中使用两个输入参数会引发错误)。 - Colin T Bowers
@ColinTBowers:我的错 - 我复制了带有错误括号的那一行。现在一切都运行得很好。 - Jonas
啊,是的。抱歉,我可能应该自己能够发现这个问题。这个方法很好用,而且比循环运行得更快(+1)- 非常感谢 Jonas。我会在过夜后再选择一个答案,以防其他人想尝试一下。 - Colin T Bowers
这个看起来非常完美。@Colin,试试它处理更大的系统尺寸。 它比你的循环快了近10倍。虽然你可以稍微调整一下你的循环... - angainor
在我的机器上快了近20倍,绝对值得打勾!除非解决方案很简单,否则我喜欢等待24小时再打勾,这样所有时区都有机会尝试解决问题。 - Colin T Bowers

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接