function X = ndhelper(fn, X, dim)
%NDHELPER run a function for columns of a matrix on any nd-array
%
% Usage:
% X = ndhelper(fn, X[, dim])
%
% fn should take a matrix and *always* perform its operation on the columns,
% that is run along dim=1.
%
% NDHELPER calls fn so that the operation is applied along dimension dim. If dim
% is unspecified, the first non-singleton dimension is used. This behavior is
% pervasive in Matlab-land. This routine lets your write simple code that can
% assume 2D arrays and only runs along the first dimension. You can then create
% a wrapper that passes the simple routine to NDHELPER, providing an interface
% in the style of Matlab's standard library routines.
%
% A common problem is making the behavior of a function sensible on empty
% arrays. In some circumstances it will be easier to make the wrapper deal with
% these cases separately rather than getting NDHELPER to do the right thing.
%
% Coding advice: if X is a matrix or other ND-array, always specify dim to avoid
% bugs. If you know that X is always a vector then the Matlab style means you
% don't have to specify dim, which makes supporting vectors that could be either
% way around easier.
% Iain Murray, November 2008, September 2010
if ~exist('dim', 'var')
dim = find(size(X)~=1, 1); % first non-singleton dimension (can be zero)
if isempty(dim)
dim = 2; % For histc Matlab sets to 2.
end
end
% Arrange into a matrix for fn()
nd = max(dim, ndims(X));
if (nd > 2) || (dim ~= 1)
perm = [dim:nd 1:(dim-1)];
% The Matlab help explicitly suggests using shiftdim, but I couldn't cover all
% cases correctly with that alone, so I went for permute/ipermute.
X = permute(X, perm);
sz = size(X);
X = reshape(X, sz(1), prod(sz(2:end)));
end
X = fn(X);
% Turn back into an ND-array.
if (nd > 2) || (dim ~= 1)
sz(1) = size(X, 1);
X = reshape(X, sz);
X = ipermute(X, perm);
end