Code covered by the BSD License  

Highlights from
combinations

from combinations by Gautam Vallabha
Returns combinations of a set of numeric or cell vectors (uses a memory-efficient algorithm)

combinations(varargin)
function out = combinations(varargin)
% COMBINATIONS  Returns all combinations of a set of vectors
% 
% C = COMBINATIONS(V1, V2, V3, ...Vn) returns the set of combinations
%   formed by taking the first element from  vector V1, the second
%   element from vector V2, the third from vector V3, and so on. 
%   Empty vectors are ignored in the calculation of the output 
%   (though a warning is issued). 
%
%    C is a KxN matrix, where K is the total number of combinations,
%    and N is the number of (non-empty) input vectors. 
%
%    V1, V2, ... Vn can be either numeric or cell arrays. If they are all 
%    numeric arrays, then C is a KxN numeric matrix. If any of the vectors
%    is a cell array, then C is a KxN cell matrix. 
%
% C = COMBINATIONS(V1, V2, V3, ...Vn, CLASSNAME) specifies that all
%    the  numeric vectors have numeric type CLASSNAME (e.g., 'int8',
%    'single'). If the output C is a numeric matrix, it will be of 
%    type CLASSNAME as well. By default, CLASSNAME is 'double'.
%
% This implementation is optimized for situations where the input 
% consists of many small vectors or a few very large vectors, for two
% reasons:
% 1) The algorithm used to generate the combinations does not use 
%    intermediate matrices, and 
% 2) When CLASSNAME is specified, there is no internal conversion from
%    DOUBLE -- the specified numeric datatype is used throughout.
%
% Examples:
%
% % Inputs are all numeric arrays
% c = combinations(1:5, 1:2)  
% c = combinations(1:5, 1:2, 'int8') % specify the numeric type
% c  = combinations(0:1,0:1,0:1,0:1,0:1,0:1,0:1,0:1,0:1,'int8');
%
% % Inputs are both numeric and cell arrays
% c = combinations({'aa','bb'}, 1:3)
% % specify the type of the numeric vectors
% c = combinations({'aa','bb'}, 1:3, 'int8')
%
% % Inputs are all cell arrays
% c  = combinations({'aa','bb'}, {[1 2] [4 5 6]}, {'zz','yy'})
% c  = combinations({'aa','bb'}, {[1 2] [4 5 6]}, [true false])
%
% % If inputs vectors have different numeric types, and you want to
% % preserve these types in the output, convert them to cell arrays:
% v1 = single(1:5); v2 = int8(1:2); 
% c = combinations( num2cell(v1), num2cell(v2) )
%
% % If you want combinations of character arrays, first convert them 
% % to explicit numeric arrays:
% c = combinations( double('abcd'), double('xyz') );
% % then convert back to character arrays
% char(c) 

% Gautam Vallabha (Gautam.Vallabha@mathworks.com)

if ischar(varargin{end})
    numericClass = varargin{end};
    try
        zeros(0,0,numericClass);
    catch  %#ok<CTCH>
        error('combinations:InvalidClass', ...
            ['String should be the name of a numeric data type ' ...
             '(<a href="matlab:doc datatypes">doc datatypes</a>)']);
    end
    varargin(end) = [];
else
    numericClass = 'double';
end


% Check the input arguments; empty lists are marked
% for later excision 
markForDelete = [];
for i=1:length(varargin)
    if isempty(varargin{i})
        markForDelete(end+1) = i; %#ok<AGROW>
        continue;
    end
    if ~(isnumeric(varargin{i}) || iscell(varargin{i}) || islogical(varargin{i}) || ischar(varargin{i}))
       error('combinations:InvalidArgument', ...
           'Parameters should be numeric vectors or cell arrays');
    end        
    if (numel(varargin{i}) ~= max(size(varargin{i})))
        error('combinations:InvalidArgument', ...
           'All arrays should be uni-dimensional');
     end
end

% Excise the empty lists
if numel(markForDelete) > 0
    for i=1:length(markForDelete)
        warning('combinations:EmptyArgument', 'Input #%d is an empty vector', markForDelete(i));
    end
    varargin(markForDelete) = [];
end


% Handle edge cases 
if numel(varargin) == 0
    out = []; 
    return;
elseif numel(varargin)  == 1
    % Need to handle this explicitly, since NDGRID(v) is 
    % implicitly treated as NDGRID(v,v)
    out = varargin{1}(:);
    return;
end

% Remember which ones are cell arrays
markAsCellArray = [];
celldata = {};
for i=1:length(varargin)
    if iscell(varargin{i})
        markAsCellArray(end+1) = i; %#ok<AGROW>
        celldata{end+1} = varargin{i}; %#ok<AGROW>
        varargin{i} = 1:numel(celldata{end});
    end
end

%% generate the combinations
numVars = numel(varargin);

% Method 1 -- using NDGRId
% v = cell(1,numVars);
% [v{:}] = ndgrid(varargin{:});
% numRows = numel(v{1});
% out2 = zeros(numRows, numVars, numericClass);
% for i=1:numVars
%     out2(:,i) = v{i}(:);
% end

% Method 2 -- using REPMAT
% This is asymptotically much faster
% and uses a lot less memory

sz = cellfun(@numel, varargin);
numRows = prod(sz);
out = zeros(numRows, numVars, numericClass);
for i=1:numVars
    % rotate the vector into the i'th dimension
    arr = permute(varargin{i}(:), [2:i 1 i+1:numVars]);
    % the repmat replicates the vector into all the other dimensions
    % and the reshape flattens it 
    temp_sz = sz;
    temp_sz(i) = 1;
    out(:,i) = reshape(repmat(arr, temp_sz), [numRows 1]);
end

%% Expand back into the cellarrays
if isempty(markAsCellArray)
    return;
end

cellout = num2cell(out);
for i=1:length(markAsCellArray)
    idx = markAsCellArray(i);
    cellout(:,idx) = celldata{i}(out(:,idx));
end
out = cellout;

Contact us at files@mathworks.com