function out = matrix2tdigest(x,iscat,vnames,hfunc)
% ============== use aggregatefun to create TDigests and other info
% Create a TDigest from each column of x.

%   Copyright 2017-2022 The MathWorks, Inc

% Use map/reduce to create TDigest objects on each chunk, and then combine
% them. 
out = hfunc(@(x)tdmap(x,iscat,vnames),@tdreduce,x);
end

function c = tdmap(x,iscat,vnames)
% Fill a cell array with TDigest objects for each column of x.
iscat = regularizeIscat(iscat,size(x,2),vnames);
n = size(x,2);
c = cell(1,n);
for j=1:n
    xj = getColumn(x,j);
    if iscat(j) ...
            || (iscategorical(x) && ~isordinal(x)) ...
            || (~iscategorical(x) && ~isnumeric(xj))
        if ischar(xj)
            xj = cellstr(xj);
        end
        if iscellstr(xj) || isstring(xj)
            xj = strtrim(xj);
        end
        U = unique(xj);
        U = U(~ismissing(U));
        c{j} = U(:);
    else
        xj = xj(isfinite(xj));
        c{j} = {matlab.internal.math.TDigest(xj),class(xj)};
    end
end
end

function out = tdreduce(in)
% Merge the TDigest values from each column.
out = in(1,:);
if ~isempty(in) && size(in,1)>1
    for j=1:size(in,2)
        if hasTDigest(out{1,j})
            % Merge TDigests across chunks
            for k=2:size(in,1)
                merge(out{1,j}{1},in{k,j}{1});
            end
        else
            % Take the union of categories across all chunks
            for k=1:size(in,1)
                out{1,j} = union(out{1,j}(:),in{k,j}(:));
            end
        end
    end
end
end

function tf = hasTDigest(c)
tf = iscell(c) && numel(c)==2 && isa(c{1},'matlab.internal.math.TDigest');
end

function iscat = regularizeIscat(iscat,p,vnames)
% Convert any iscat type (numeric, logical, names) to logical
if isempty(iscat)
    iscat = false(1,p);
elseif isequal(iscat,'all')
    iscat = true(1,p);
elseif islogical(iscat)
    if ~isvector(iscat) || numel(iscat)~=p
         error(message('stats:classreg:learning:FullClassificationRegressionModel:prepareDataCR:BadCatPredLogicalIndex', p));
    end
elseif isnumeric(iscat)
    if ~isvector(iscat)
        error(message('stats:classreg:learning:FullClassificationRegressionModel:prepareDataCR:BadCatPredIntegerIndex', p));
    end
    iscat = sort(iscat);
    if any(diff(iscat)==0) || any(iscat<1 | iscat>p | iscat~=floor(iscat))
        error(message('stats:classreg:learning:FullClassificationRegressionModel:prepareDataCR:BadCatPredIntegerIndex', p));
    end
    temp = false(1,p);
    temp(iscat) = true;
    iscat = temp;
elseif iscellstr(iscat) || isstring(iscat) || ischar(iscat)
    if ~isempty(vnames) && (iscellstr(vnames) || isstring(vnames) || ischar(iscat))
        if ~all(ismember(iscat,vnames))
            error(message('stats:classreg:learning:internal:utils:CategoricalNotPredictor'))
        end
        iscat = ismember(vnames,iscat);
    else
        error(message('stats:classreg:learning:FullClassificationRegressionModel:prepareDataCR:CharCatVarWithoutVarNames'));
    end
else
    error(message('stats:classreg:learning:FullClassificationRegressionModel:prepareDataCR:BadCatVarType'));
end
end

function x = getSubset(T,rows,cols)
% Get a subset of either a table or a matrix; if table get contents
if istable(T)
    x = T{rows,cols};
else
    x = T(rows,cols);
end
end

function x = getColumn(T,col)
% Get a column from either a table or a matrix; if table get contents
x = getSubset(T,':',col);
end