获取 class 类型元胞数组元素的最快方法

Fastest way to get class types of elements of a cell array

我有一个(大)元胞数组,具有各种数据类型。例如,

 myCell = { 1, 2, 3, 'test',  1 , 'abc';
            4, 5, 6, 'foob', 'a', 'def' };

这可以包括更模糊的类型,例如 java.awt.Color 对象。

我想确保每列中的数据属于同一类型,因为我想对其执行类似table的操作。然而,这个过程似乎很慢!

我目前的方法是用cellfun得到类,用strcmp检查

% Get class of every cell element
types = cellfun( @class, myCell, 'uni', false );
% Check that they are consistent for each column
typesOK = all( strcmp(repmat(types(1,:), size(types,1), 1), types), 1 );
% Output the types (mixed type columns can be handled using typesOK)
types = types(1, :);

% Output for the above example: 
% >> typesOK = [1 1 1 1 0 1]
% >> types = {'double', 'double', 'double', 'char', 'double', 'char'}

我曾想过使用 cell2table,因为它出于同样的原因进行类型检查。但是,它没有给我想要的结果(严格来说,哪些列是哪些类型)。

是否有更快的方法来检查元胞数组列中的类型一致性?


编辑:我刚刚做了一些分析...

看来 types = cellfun( @class, ...) 行占用了 90% 的处理时间。 如果您的方法与我的方法只是略有不同,那应该是那行发生了变化, strcmp 非常快。


编辑:有幸针对这个问题得到了很多建议,我都整理成了一个用于性能测试

您可以尝试 validateattributes,但您必须明确说明 class 并指定要检查的列。但是,例如,您可以轻松地从元胞数组的第一行获取它。

foo = { 'abc' 5 'def'; ...
        'foo' 6 'bar' };

cellfun(@(x) validateattributes(x, {'char'}, {}, foo(:, [1 3]))

要测试它是否可以更快地用于非常大的数组,但可能是这样的:

function [b] = IsTypeConsistentColumns(myCell)
%[
    b = true;
    try
        for ci = 1:size(myCell, 2)
           cell2mat(myCell(:, ci));
        end
    catch err
        if (strcmpi(err.identifier, 'MATLAB:cell2mat:MixedDataTypes'))
            b = false;
        else
            rethrow(err);
        end
    end
%]
end

这取决于 cell2mat 与您的字符串比较相比的速度有多快(即使此处未使用 cell2mat 的结果。

请注意,如果类型不一致(identifier: 'MATLAB:cell2mat:MixedDataTypes'message = 'All contents of the input cell array must be of the same data type.'

cell2mat 将抛出错误

编辑:限于 cellfun('isclass', c , cellclass) test

此处仅使用 cell2mat 例程内部执行的类型一致性检查:

function [consistences, types] = IsTypeConsistentColumns(myCell)
%[
    ncols = size(myCell, 2);
    consistences = false(1, ncols);
    types = cell(1, ncols);
    for ci = 1:ncols
        cellclass = class(myCell{1, ci});
        ciscellclass = cellfun('isclass', myCell(:, ci), cellclass);

        consistences(ci) = all(ciscellclass);
        types{ci} = cellclass; 
    end    
%]
end

有你测试用例myCell = repmat( { 1, 2, 3, 'test', 1 , 'abc'; 4, 5, 6, 'foob', 'a', 'def' }, 10000, 5 );,

在我的计算机上使用 R2015b 大约需要 0.0123 秒...如果您想在第一个不一致的列上失败(这里我正在测试它们),它甚至可以更快

您可以使用 unique:

myCell = { 1, 2, 3, 'test',  1 , 'abc';
            4, 5, 6, 'foob', 'a', 'def' };

types = cellfun( @class, myCell, 'uni', false );
[type,~,idx]=unique(types);
u = unique(reshape(idx,size(types)),'rows');
if size(u,1) == 1
    disp('consistent')
else
     disp('non-consistent')
end

这个怎么样:

>>  myCell = { 1, 2, 3, 'test',  1 , 'abc';
               4, 5, 6, 'foob', 'a', 'def' }
myCell =
  2×6 cell array
    [1]    [2]    [3]    'test'    [1]    'abc'
    [4]    [5]    [6]    'foob'    'a'    'def'

>> firstRowTypes = cellfun(@class, myCell(1,:), 'uni', false)
firstRowTypes =
  1×6 cell array
    'double'    'double'    'double'    'char'    'double'    'char'

>> for i = 1:numel(firstRowTypes)
       typesOK(i) = all(cellfun(@(x)isa(x,firstRowTypes{i}), myCell(:,i)));
   end

>> typesOK
typesOK =
  1×6 logical array
   1   1   1   1   0   1

我没有做过大量的计时,但我认为这应该会加快速度(至少对于大型单元阵列而言),因为

  1. 您只将第一行的类型转换为字符串
  2. 您是直接使用 isa 进行类型比较,而不是将所有类型转换为字符串然后比较字符串。

这是不同建议的集合,带有用于比较时间的基准测试脚本...

function benchie    
    % Create a large, mixed type cell array
    myCell = repmat( { 1, 2, 3, 'test',  1 , 'abc';
                       4, 5, 6, 'foob', 'a', 'def' }, 10000, 5 );

    % Create anonymous functions for TIMEIT               
    f1 = @() usingStrcmp(myCell);
    f2 = @() usingUnique(myCell);
    f3 = @() usingLoops(myCell);
    f4 = @() usingISA(myCell);
    f5 = @() usingIsClass(myCell);
    % Timing of different methods
    timeit(f1)
    timeit(f2)
    timeit(f3)    
    timeit(f4)
    timeit(f5)
end

function usingStrcmp(myCell)
    % The original method
    types = cellfun( @class, myCell, 'uni', false );
    typesOK = all( strcmp(repmat(types(1,:), size(types,1), 1), types), 1 );
    types = types(1, :);
end

function usingUnique(myCell)
    % Using UNIQUE instead of STRCMP, as suggested by rahnema1 
    types = cellfun( @class, myCell, 'uni', false );
    [type,~,idx]=unique(types);
    u = unique(reshape(idx,size(types)),'rows');
    if size(u,1) == 1
        % consistent
    else
        % not-consistent
    end
end

function usingLoops(myCell)
    % Using loops instead of CELLFUN. Move onto the next column if a type
    % difference is found, otherwise continue looping down the rows
    types = cellfun( @class, myCell(1,:), 'uni', false );
    typesOK = true(size(types));
    for c = 1:size(myCell,2)
        for r = 1:size(myCell,1)
            if ~strcmp( class(myCell{r,c}), types{c} )
                typesOK(c) = false;
                continue
            end
        end
    end
end

function usingISA(myCell)
    % Using ISA instead of converting all types to strings. Suggested by Sam
    types = cellfun( @class, myCell(1,:), 'uni', false );
    for ii = 1:numel(types)
       typesOK(ii) = all(cellfun(@(x)isa(x,types{ii}), myCell(:,ii)));
    end
end

function usingIsClass(myCell)
    % using the same method as found in CELL2MAT. Suggested by CitizenInsane 
    ncols = size(myCell, 2);
    typesOK = false(1, ncols);
    types = cell(1, ncols);
    for ci = 1:ncols
        cellclass = class(myCell{1, ci});
        ciscellclass = cellfun('isclass', myCell(:, ci), cellclass);
        typesOK(ci) = all(ciscellclass);
        types{ci} = cellclass; 
    end  
end

输出:

在 R2015b 上测试

usingStrcmp:  0.8523 secs
usingUnique:  1.2976 secs
usingLoops:   1.4796 secs
usingISA:    10.2670 secs 
usingIsClass: 0.0131 secs % RAPID!

在 R2017b 上测试

usingStrcmp:  0.8282 secs
usingUnique:  1.2128 secs
usingLoops:   0.4763 secs % ZOOOOM! (Relative to R2015b)
usingISA:     9.6516 secs
usingIsClass: 0.0093 secs % RAPID!

循环方法在很大程度上取决于类型差异发生的位置,因为它可以遍历每列的每一行或每列的两行。

虽然输入相同(如图所示),但循环已在较新版本的 MATLAB (2017b) 中进行了大规模优化,节省了 >65% 的时间,并且比原始版本快了 50%!


结论:

  • 对于持续快速的时间(无论输入如何),原始方法仍然获胜。
  • 对于较新 MATLAB 版本的最高速度,循环方法可能是最佳的。

  • 更新:与其他版本相比,方法 非常快,并且可能很难被击败,因为它使用了与 Matlab 自己的 cell2mat 相同的方法。

    建议:使用上面的usingIsClass函数。