Code covered by the BSD License  

Highlights from
CStrCatStr

image thumbnail

CStrCatStr

by

 

03 Jun 2009 (Updated )

Cat 2 or 3 strings/cell strings C-MEX: 10 times faster than STRCAT

TestCStrCatStr(doSpeed)
function TestCStrCatStr(doSpeed)
% Automatic test: CStrCatStr
% This is a routine for automatic testing. It is not needed for processing and
% can be deleted or moved to a folder, where it does not bother.
%
% TestCStrCatStr(doSpeed)
% INPUT:
%   doSpeed: Optional logical flag to trigger time consuming speed tests.
%            Default: TRUE. If no speed test is defined, this is ignored.
% OUTPUT:
%   On failure the test stops with an error.
%
% Tested: Matlab 6.5, 7.7, 7.8, WinXP
% Author: Jan Simon, Heidelberg, (C) 2009-2010 matlab.THISYEAR(a)nMINUSsimon.de

% $JRev: R0n V:030 Sum:wP60U9hy0/Ja Date:11-Feb-2010 01:28:24 $
% $File: Published\CStrCatStr\TestCStrCatStr.m $
% $History:
% 021: 07-May-2009 09:24, Check 3 inputs also.
% 027: 12-Sep-2009 16:20, Check handling uf NULL elements.
% 028: 25-Oct-2009 16:09, BUGFIX: Checks of rejected bad input failed.
%      Not rejected bad inputs called ERROR, but this was masked by a TRY-CATCH
%      block. The function CStrCatStr was not affected.

% Initialize: ==================================================================
% Global Interface: ------------------------------------------------------------
% Initial values: --------------------------------------------------------------
% Program Interface: -----------------------------------------------------------
if nargin == 0
   doSpeed = true;
end

% Reduce time to measure speed of STRCAT:
ExtrapolateSTRCAT = true;

% User Interface: --------------------------------------------------------------
% Do the work: =================================================================
whichFun = which('CStrCatStr');
disp(['==== Test CStrCatStr ', datestr(now, 0), char(10), ...
   '  Version: ', whichFun, char(10)]);
pause(0.001);
[dum1, dum2, typeFun] = fileparts(whichFun); %#ok<ASGLU>
typeFun(findstr(typeFun, '.')) = [];

% Set of test strings: ---------------------------------------------------------
S  = {'', 'a', 'ab', 'abc', 'abcdefghijklmnop'};
nS = length(S);
SR = S(nS:-1:1);
C  = {{}, {''}, {'', ''}, {'', '', ''}, ...
   {'a'}, {'ab'}, {'abc'}, ...
   {'a', ''}, {'', 'a'}, {'a', 'b'}, {'a', 'b', ''}, ...
   {'ab', ''}, {'', 'ab'}, {'ab', 'bb'}, {'ab', 'bb', 'b'}, ...
   S, SR};

% Two inputs: ------------------------------------------------------------------
% Compare combinations of test strings with result from STRCAT - consider the
% different replies for empty cells:
fprintf(['  2 inputs: Combinations of ', ShowStr(S), ': ']);
for i1 = 1:nS
   for i2 = 1:nS
      % String + scalar cell:
      CheckMex(S{i1}, S(i2));
      
      % Scalar cell + string:
      CheckMex(S(i1), S{i2});
      
      % Scalar cell + scalar cell:
      CheckMex(S(i1), S(i2));
   end
   
   % Cell + string:
   CheckMex(S, S{i1});
   
   % String + Cell:
   CheckMex(S{i1}, S);
   
   % String + Cell 2:
   for iC = 1:length(C)
      CheckMex(S{i1}, C{iC});
   end
end

% Cell + cell:
CheckMex(S,  S);
CheckMex(SR, S);
CheckMex(S,  SR);
CheckMex(SR, SR);

fprintf('ok\n');

% 3 inputs: --------------------------------------------------------------------
fprintf(['  3 inputs: Combinations of ', ShowStr(S), ': ']);

% Cell + cell:
for Bin = 0:7
   % Test strings and scalar cells:
   if Bin ~= 0  % At least one must be a cell
      for i1 = 1:nS
         for i2 = 1:nS
            for i3 = 1:nS
               v = [i1, i2, i3];
               for iBin = 1:3
                  if bitget(Bin, iBin)
                     SB{iBin} = S(v(iBin));  %#ok<AGROW>
                  else
                     SB{iBin} = S{v(iBin)};  %#ok<AGROW>
                  end
               end
               
               CheckMex(SB{1}, SB{2}, SB{3});
            end  % for i3
         end  % for i2
      end  % for i1
   end
   
   % Test all combinations of S and reverted S:
   for iBin = 1:3
      if bitget(Bin, iBin)
         SB{iBin} = S;   %#ok<AGROW>
      else
         SB{iBin} = SR;  %#ok<AGROW>
      end
   end
   
   CheckMex(SB{1}, SB{2}, SB{3});
end

fprintf('ok\n');

% Random tests and comparison with STRCAT: -------------------------------------
startTime = cputime;
count     = 0;
TestTime  = 1 + 4 * double(doSpeed);  % 1 or 5 seconds
fprintf('  Random inputs compared with STRCAT (%g sec): ', TestTime);
while cputime - startTime < TestTime
   count = count + 1;
   if rand < 2/7  % 2 inputs:
      switch fix(rand * 3)
         case 0
            A = RandStr('char');
            B = RandStr('cell', []);
         case 1
            A = RandStr('cell', []);
            B = RandStr('char');
         case 2
            A = RandStr('cell', []);
            B = RandStr('cell', size(A));
      end
      
      mexCat = CStrCatStr(A, B);
      matCat = strcat(A, B);
      
   else  % 3 inputs:
      switch fix(rand * 7)
         case 0
            A = RandStr('cell', []);
            B = RandStr('char');
            C = RandStr('char');
         case 1
            A = RandStr('char');
            B = RandStr('cell',[]);
            C = RandStr('char');
         case 2
            A = RandStr('char');
            B = RandStr('char');
            C = RandStr('cell', []);
         case 3
            A = RandStr('cell', []);
            B = RandStr('cell', size(A));
            C = RandStr('char');
         case 4
            A = RandStr('cell', []);
            B = RandStr('char');
            C = RandStr('cell', size(A));
         case 5
            A = RandStr('char');
            B = RandStr('cell', []);
            C = RandStr('cell', size(B));
         case 6
            A = RandStr('cell', []);
            B = RandStr('cell', size(A));
            C = RandStr('cell', size(A));
      end
      
      mexCat = CStrCatStr(A, B, C);
      matCat = strcat(A, B, C);
   end
   
   % STRCAT('', {''}) replies [1 x 0] char:
   matCat(cellfun('isempty', matCat)) = {''};
   
   if isequal(mexCat, matCat) == 0
      disp([char(10), 'CStrCatStr:']);
      disp(mexCat);
      disp('STRCAT:');
      disp(matCat);
      error(['*** ', mfilename, ': Failed for random tests']);
   end
end
fprintf('%d tests ok\n', count);

% Test bad inputs: -------------------------------------------------------------
disp([char(10), '  Provoke errors:']);
errBak  = lasterr;
tooLazy = false;
try
   V = CStrCatStr('string', 'string');  %#ok<*NASGU>
   tooLazy = true;
catch
   disp('  Ok: 2 strings refused.');
end
if tooLazy
   error(['*** ', mfilename, ': 2 strings accepted?!']);
end

try
   V = CStrCatStr({'1', '2'}, {'1'});
   tooLazy = true;
catch
   disp('  Ok: Cells of different size refused (1, 2).');
end
if tooLazy
   error(['*** ', mfilename, ': Cells of different size accepted?!']);
end

try
   V = CStrCatStr('string', 'string', 'string');
   tooLazy = true;
catch
   disp('  Ok: 3 strings refused.');
end
if tooLazy
   error(['*** ', mfilename, ': 2 strings accepted?!']);
end

try
   V = CStrCatStr({'1', '2'}, {'1'}, {'1'});
   tooLazy = true;
catch
   disp('  Ok: Cells of different size refused (2, 1, 1).');
end
if tooLazy
   error(['*** ', mfilename, ': Cells of different size accepted?!']);
end

try
   V = CStrCatStr({'1'}, {'1', '2'}, {'1'});
   tooLazy = true;
catch
   disp('  Ok: Cells of different size refused (1, 2, 1).');
end
if tooLazy
   error(['*** ', mfilename, ': Cells of different size accepted?!']);
end

try
   V = CStrCatStr({'1'}, {'1'}, {'1', '2'});
   tooLazy = true;
catch
   disp('  Ok: Cells of different size refused (1, 1, 2).');
end
if tooLazy
   error(['*** ', mfilename, ': Cells of different size accepted?!']);
end

try
   V = CStrCatStr({'1', '2'}, 'string', {'1'});
   tooLazy = true;
catch
   disp('  Ok: Cells of different size refused (cell, string, cell).');
end
if tooLazy
   error(['*** ', mfilename, ': Cells of different size accepted?!']);
end

try
   V = CStrCatStr({'1'}, {'1', '2'}, 'string');
   tooLazy = true;
catch
   disp('  Ok: Cells of different size refused (cell, cell, string).');
end
if tooLazy
   error(['*** ', mfilename, ': Cells of different size accepted?!']);
end

try
   V = CStrCatStr('string', {'1'}, {'1', '2'});
   tooLazy = true;
catch
   disp('  Ok: Cells of different size refused (string, cell, cell).');
end
if tooLazy
   error(['*** ', mfilename, ': Cells of different size accepted?!']);
end

% The mex version checks if elements are strings:
if strcmpi(typeFun, mexext)
   % 2 inputs:
   InputList1 = {'string', {'cell'}; {'cell'}, 'string'; {'cell'}, {'cell'}};
   for i1 = 1:size(InputList1, 1)
      aInput = InputList1(i1, :);
      for i2 = 1:2
         bInput = aInput;
         if ischar(bInput{i2})
            bInput{i2} = 0;
         else
            bInput{i2} = {0};
         end
         
         try
            V = CStrCatStr(bInput{:});
            tooLazy = true;
         catch  % do nothing
         end
         if tooLazy
            disp(bInput);
            error(['*** ', mfilename, ': Non-string input accepted?!']);
         end
      end
   end
   
   % Not initialized cell elements must be refused:
   Cell0      = cell(1, 1);
   Cell0x     = cell(1, 3);
   Cell0x{1}  = 'asd';
   Cell0x{3}  = 'bsd';
   InputList2 = {Cell0, Cell0x};
   
   for iType = 1:2
      aCell = InputList2{iType};
      try
         V = CStrCatStr('string', aCell);
         tooLazy = true;
      catch  % do nothing
      end
      if tooLazy
         error(['*** ', mfilename, ': NULL accepted?!']);
      end
      
      try
         V = CStrCatStr(aCell, 'string');
         tooLazy = true;
      catch  % do nothing
      end
      if tooLazy
         error(['*** ', mfilename, ': NULL accepted?!']);
      end
      
      try
         V = CStrCatStr(aCell, aCell);
         tooLazy = true;
      catch  % do nothing
      end
      if tooLazy
         error(['*** ', mfilename, ': NULL accepted?!']);
      end
   end
   
   % 3 inputs:
   InputList3 = {{'cell'}, 'string', 'string'; ...
      'string', {'cell'}, 'string'; ...
      'string', 'string', {'cell'}; ...
      {'cell'}, 'string', {'cell'}; ...
      {'cell'}, {'cell'}, 'string'; ...
      'string', {'cell'}, {'cell'}; ...
      {'cell'}, {'cell'}, {'cell'}};
   
   for i1 = 1:size(InputList3, 1)
      aInput = InputList3(i1, :);
      for i2 = 1:3
         bInput = aInput;
         if ischar(bInput{i2})
            bInput{i2} = 0;
         else
            bInput{i2} = {0};
         end
         
         try
            V = CStrCatStr(bInput{:});
            tooLazy = true;
         catch  % do nothing
         end
         if tooLazy
            disp(bInput);
            error(['*** ', mfilename, ': Non-string input accepted?!']);
         end
      end
   end
   
   InputList4 = {{'scalar_cell'}, {'cell1', 'cell2'}, 'string'};
   for iType = 1:length(InputList4)
      aCell = InputList4{iType};
      try
         V = CStrCatStr('A', 'B', aCell);
         if ~iscell(aCell)
            tooLazy = true;
         end
      catch  % do nothing
      end
      if tooLazy
         error(['*** ', mfilename, ': NULL accepted?!']);
      end
      
      try
         V = CStrCatStr('A', aCell, 'C');
         if ~iscell(aCell)
            tooLazy = true;
         end
      catch  % do nothing
      end
      if tooLazy
         error(['*** ', mfilename, ': NULL accepted?!']);
      end
      
      try
         V = CStrCatStr(aCell, 'B', 'C');
         if ~iscell(aCell)
            tooLazy = true;
         end
      catch  % do nothing
      end
      if tooLazy
         error(['*** ', mfilename, ': NULL accepted?!']);
      end
      
      try
         V = CStrCatStr('A', aCell, aCell);
         if ~iscell(aCell)
            tooLazy = true;
         end
      catch  % do nothing
      end
      if tooLazy
         error(['*** ', mfilename, ': NULL accepted?!']);
      end
      
      try
         V = CStrCatStr(aCell, 'B', aCell);
         if ~iscell(aCell)
            tooLazy = true;
         end
      catch  % do nothing
      end
      if tooLazy
         error(['*** ', mfilename, ': NULL accepted?!']);
      end
      
      try
         V = CStrCatStr(aCell, aCell, 'C');
         if ~iscell(aCell)
            tooLazy = true;
         end
      catch  % do nothing
      end
      if tooLazy
         error(['*** ', mfilename, ': NULL accepted?!']);
      end
      
      try
         V = CStrCatStr(aCell, aCell, aCell);
         if ~iscell(aCell)
            tooLazy = true;
         end
      catch  % do nothing
      end
      if tooLazy
         error(['*** ', mfilename, ': NULL accepted?!']);
      end
   end
   
   disp('  Ok: Non-string input rejected.');
end

lasterr(errBak);

% Speed: -----------------------------------------------------------------------
% Find a suiting number of loops:
disp([char(10), '== Test Speed:']);
minT = eps;
C = strread(path, '%s', 'delimiter', pathsep);
if doSpeed
   iLoop     = 0;
   startTime = cputime;
   while cputime - startTime < 1.0
      v     = CStrCatStr('asd', C);
      iLoop = iLoop + 1;
   end
   nLoops = 1000 * ceil(iLoop / ((cputime - startTime) * 1000));
   disp([sprintf('  %d', nLoops) ' loops on this machine.']);
else
   disp('  Use at least 2 loops (displayed times are random!)');
   nLoops = 2;
end

% ------------------------------------------------------------------------------
if strcmpi(typeFun, mexext) && ExtrapolateSTRCAT
   % Reduce number of loops for Matlab version if compared with a MEX script:
   Mdiv     = 10;
   extraStr = '  (extrapolated)';
else
   Mdiv     = 1;
   extraStr = '';
end

tic;
mLoops = ceil(nLoops / Mdiv);
for i = 1:mLoops
   v2 = strcat('asd', C);
   clear('v2');
end
etMat = toc * Mdiv + minT;
v2    = strcat('asd', C);

tic;
for i = 1:nLoops
   v1 = CStrCatStr('asd', C);
   clear('v1');
end
etMex = toc + minT;
v1    = CStrCatStr('asd', C);

if isequal(v1, v2)
   disp(['  Cat ''asd'' and {1 x ', sprintf('%d', numel(C)), '}:']);
   disp(['    STRCAT:     ', sprintf('%6.2f', etMat), extraStr, ...
      char(10), '    CStrCatStr: ', sprintf('%6.2f', etMex), ...
      '   ==> ', sprintf('%.1f', 100 * etMex / etMat), '%']);
else
   error(['*** ', mfilename, ...
      ': Failed during speed test: cat(string, cell string).']);
end

% ------------------------------------------------------------------------------
fprintf('\n');
tic;
for i = 1:mLoops
   v1 = strcat(C, C);
   clear('v1');
end
etMat = toc * Mdiv + minT;
v1    = strcat(C, C);

tic;
for i = 1:nLoops
   v2 = CStrCatStr(C, C);
   clear('v2');
end
etMex = toc + minT;
v2    = CStrCatStr(C, C);

if isequal(v1, v2)
   tmpStr = ['{1 x ', sprintf('%d', numel(C)), '}'];
   disp(['  Cat ', tmpStr, ' with ', tmpStr, ':', char(10), ...
      '    STRCAT:     ', sprintf('%6.2f', etMat), extraStr, ...
      char(10), '    CStrCatStr: ', sprintf('%6.2f', etMex), ...
      '   ==> ', sprintf('%.1f', 100 * etMex / etMat), '%']);
else
   error(['*** ', mfilename, ...
      ': Failed during speed test: cat(cell string, cell string).']);
end

% ------------------------------------------------------------------------------
fprintf('\n');
tic;
for i = 1:mLoops
   v1 = strcat(C, 'asd', C);
   clear('v1');
end
etMat = toc * Mdiv + minT;
v1    = strcat(C, 'asd', C);

tic;
for i = 1:nLoops
   v2 = CStrCatStr(C, 'asd', C);
   clear('v2');
end
etMex = toc + minT;
v2    = CStrCatStr(C, 'asd', C);

if isequal(v1, v2)
   tmpStr = ['{1 x ', sprintf('%d', numel(C)), '}'];
   disp(['  Cat ', tmpStr, ' + ''asd'' + ', tmpStr, ':', ...
      char(10), '    STRCAT:     ', sprintf('%6.2f', etMat), extraStr, ...
      char(10), '    CStrCatstr: ', sprintf('%6.2f', etMex), ...
      '   ==> ', sprintf('%.1f', 100 * etMex / etMat), '%']);
else
   error(['*** ', mfilename, ...
      ': Failed during speed test: cat(cell string, string, cell string).']);
end

% ------------------------------------------------------------------------------
fprintf('\n');
tic;
for i = 1:mLoops
   v1 = strcat('bsd', C, 'asd');
   clear('v1');
end
etMat = toc * Mdiv + minT;
v1    = strcat('bsd', C, 'asd');

tic;
for i = 1:nLoops
   v2 = CStrCatStr('bsd', C, 'asd');
   clear('v2');
end
etMex = toc + minT;
v2    = CStrCatStr('bsd', C, 'asd');

if isequal(v1, v2)
   tmpStr = ['{1 x ', sprintf('%d', numel(C)), '}'];
   disp(['  Cat ''bsd'' + ', tmpStr, ' + ''asd'':', ...
      char(10), '    STRCAT:     ', sprintf('%6.2f', etMat), extraStr, ...
      char(10), '    CStrCatstr: ', sprintf('%6.2f', etMex), ...
      '   ==> ', sprintf('%.1f', 100 * etMex / etMat), '%']);
else
   error(['*** ', mfilename, ...
      ': Failed during speed test: cat(string, cell string, string).']);
end

% ------------------------------------------------------------------------------
% Create longer string and add a char to avoid shared data copies:
D = strcat(cat(2, C, C, C, C, C, C, C, C, C, C), '*');
nLoops = round(nLoops / 10);
mLoops = round(mLoops / 10);

fprintf('\n');
tic;
for i = 1:mLoops
   v1 = strcat('bsd', D, 'asd');
   clear('v1');
end
etMat = toc * Mdiv + minT;
v1    = strcat('bsd', D, 'asd');

tic;
for i = 1:nLoops
   v2 = CStrCatStr('bsd', D, 'asd');
   clear('v2');
end
etMex = toc + minT;
v2    = CStrCatStr('bsd', D, 'asd');

if isequal(v1, v2)
   tmpStr = ['{1 x ', sprintf('%d', numel(D)), '}'];
   disp(['  Cat ''bsd'' + ', tmpStr, ' + ''asd'':   ', ...
      sprintf('(%d loops)', nLoops), ...
      char(10), '    STRCAT:     ', sprintf('%6.2f', etMat), extraStr, ...
      char(10), '    CStrCatstr: ', sprintf('%6.2f', etMex), ...
      '   ==> ', sprintf('%.1f', 100 * etMex / etMat), '%']);
else
   error(['*** ', mfilename, ...
      ': Failed during speed test: cat(bsd, long cell string, string).']);
end

% ready. -----------------------------------------------------------------------
disp([char(10), 'CStrCatStr works well.']);

return;

% ******************************************************************************
function CheckMex(S1, S2, S3)

switch nargin
   case 2
      OrigReply = strcat(S1, S2);
      try
         R = CStrCatStr(S1, S2);
      catch
         error(['*** ', mfilename, ': Error for:', char(10), ...
            '  ', ShowStr(S1), char(10), '  ', ShowStr(S2), char(10), ...
            lasterr]);
      end
      
      if isequal(S1, {}) || isequal(S2, {})
         if ~isequal(R, {})
            error(['*** ', mfilename, ': Bad reply for: ', char(10), ...
               '  ', ShowStr(S1), char(10), '  ', ShowStr(S2), char(10), ...
               'Reply: ', ShowStr(R), char(10), ...
               'Reply must be {}!']);
         end
      elseif ~isequal(R, OrigReply)
         fprintf('\n');
         error(['*** ', mfilename, ': Bad reply for: ', char(10), ...
            '  ', ShowStr(S1), char(10), '  ', ShowStr(S2), ...
            'Reply: ', ShowStr(R)]);
      end
      
   case 3
      OrigReply = strcat(S1, S2, S3);
      try
         R = CStrCatStr(S1, S2, S3);
      catch
         error(['*** ', mfilename, ': Error for:', char(10), ...
            '  ', ShowStr(S1), char(10), '  ', ShowStr(S2), char(10), ...
            '  ', ShowStr(S3), char(10), lasterr]);
      end
      
      if isequal(S1, {}) || isequal(S2, {}) || isequal(S3, {})
         if ~isequal(R, {})
            error(['*** ', mfilename, ': Bad reply for: ', char(10), ...
               '  ', ShowStr(S1), char(10), '  ', ShowStr(S2), char(10), ...
               '  ', ShowStr(S3), char(10), 'Reply: ', ShowStr(R), ...
               char(10), 'Reply must be {}!']);
         end
      elseif ~isequal(R, OrigReply)
         fprintf('\n');
         error(['*** ', mfilename, ': Bad reply for: ', char(10), ...
            '  ', ShowStr(S1), char(10), '  ', ShowStr(S2), char(10), ...
            '  ', ShowStr(S3), char(10), 'Reply: ', ShowStr(R)]);
      end
      
   otherwise
      error(['*** ', mfilename, '(CheckMex)', ...
         ': Bad number of switches - programming error!']);
end

return;

% ******************************************************************************
function S = ShowStr(C)

if ischar(C)
   S = [char(39), C, char(39)];
elseif isempty(C)
   S = '{}';
elseif length(C) == 1
   S = sprintf('{''%s''}', C{1});
else
   S = ['{', sprintf('''%s'', ', C{1:length(C) - 1}), ...
      sprintf('''%s''}', C{length(C)})];
end

return;

% ******************************************************************************
function S = RandStr(Kind, N)
% Create a random string or cell string.
charA = 'A';
charB = 'B';

switch Kind
   case 'char'
      S = charA(ones(1, fix(rand * 5)));
      if isempty(S), S = ''; end        % No [1 x 0] strings as input
      
   case 'cell'
      if isempty(N)
         S = cell(1 + fix(rand * 10), 1 + fix(rand * 10));
      else
         S = cell(N(1), N(2));
      end
      for iS = 1:numel(S)
         S{iS} = charB(ones(1, fix(rand * 5)));
      end
      S(cellfun('isempty', S)) = {''};
      
   otherwise
      error(['*** ', mfilename, '(RandStr): Bad [Kind] - programming error!']);
end

return;

Contact us