cosmo align skl

function [map_x2y, map_y2x] = cosmo_align(x, y)
    % find permutation so that values in two inputs are matched
    %
    % [map_x2y, map_y2x]=cosmo_align(x,y)
    %
    % Inputs:
    %   x               } both x and y must be a cell with K elements, each
    %   y               } being a vector or a cellstring with the same number
    %                     of elements (say N). Alternatively it can
    %                     be a single vector  or cellstrings v, which is
    %                     interpreted as {v}.
    %
    % Outputs:
    %   map_x2y           a vector with N elements, so that for all values I in
    %                     1:K it holds that map_x2y(x{I}) is equal to y{I}.
    %   map_y2x           a vector with N elements, so that for all values I in
    %                     1:K it holds that map_y2x(y{I}) is equal to x{I}.
    %
    % #   For CoSMoMVPA's copyright information and license terms,   #
    % #   see the COPYING file distributed with CoSMoMVPA.           #

    [x_cell, x_label] = as_cell(x, 1);
    [y_cell, y_label] = as_cell(y, 2);

    ensure_matching_labels(x_label, y_label);

    nx = numel(x_cell);
    ny = numel(y_cell);

    if nx ~= ny
        error(['number of elements in each cell is not equal; first '...
               'input has %d elements, second input has %d'], ...
              nx, ny);
    end

    [xi, xv] = cosmo_index_unique(x_cell);
    [yi, yv] = cosmo_index_unique(y_cell);

    if ~cosmo_isequaln(xv, yv)
        error(['the two inputs do not have the same unique '...
               'combination of elements']);
    end

    nxi = cellfun(@numel, xi);
    nyi = cellfun(@numel, yi);

    if ~isequal(nxi, nyi)
        error(['the two inputs do have matching elements '...
               'but their number of occurrences is not matched']);
    elseif any(cellfun(@numel, xi) > 1)
        error('combinations of elements are non-unique');
    end

    xv = cat(1, xi{:});
    yv = cat(1, yi{:});

    n = numel(xi);
    map_x2y = zeros(1, n);
    map_x2y(yv) = xv;

    map_y2x = zeros(1, n);
    map_y2x(xv) = yv;

function [v_cell, labels] = as_cell(v, pos)
    if iscell(v) && ~iscellstr(v)
        n = numel(v);

        for j = 1:n
            ensure_vector(v{j}, pos, j);
        end
        v_cell = v;
        labels = [];
    elseif isstruct(v)
        labels = sort(fieldnames(v));
        n = numel(labels);

        v_cell = cell(n, 1);
        for k = 1:n
            label = labels{k};
            v_value = v.(label);
            ensure_vector(v_value, pos, label);
            v_cell{k} = v_value;
        end
    else
        ensure_vector(v, pos, 1);
        v_cell = {v(:)};
        labels = [];
    end

function ensure_vector(v, pos, i)
    if ~isvector(v)
        msg = 'only input with vectors is supported';
    elseif ~(isnumeric(v) || iscellstr(v))
        msg = ['only inputs with numeric vectors or cellstrings '...
               'are supported'];
    else
        % all fine
        return
    end

    if ischar(i)
        elem_str = ['field ' i];
    else
        elem_str = sprintf('element %d', i);
    end

    % throw error
    error('input %d, %s: %s', pos, elem_str, msg);

function ensure_matching_labels(x_label, y_label)
    if ~isequal(x_label, y_label)
        if iscellstr(x_label) && iscellstr(y_label)
            error(['field name mismatch between two inputs: '...
                   '[%s] ~= [%s]'], ...
                  cosmo_strjoin(x_label), cosmo_strjoin(y_label));
        else
            error(['either both inputs must be cells or vectors, ', ...
                   'or both must be structs']);
        end
    end