cosmo align

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 occurences 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