将mat2str推广到单元数组

11

我有时候想要一个函数来产生一个(可能是嵌套的)单元数组的字符串表示。这将是对 mat2str 的泛化,因为它仅适用于非单元数组(数值、字符或逻辑类型)。

给定一个数组 x,如何获取其字符串表示 y,并且评估该字符串会生成 x

例如,输入

x = {[10 20], {'abc'; false; true;}};
应该生成类似于输出字符串的结果。
y = '{[10 20], {''abc''; false; true}}';

(或关于间距和分隔符的某些变化),以便:

isequal(x, eval(y))

true

2个回答

8
这个过程将数据结构转化为字符串,以后可以进行eval操作,称之为序列化
Octave有一个序列化函数,可以用于此目的,支持任何核心数据类型(不仅限于单元数组),以及任意维度的数据(不仅限于2D)。
例子:
## Works for normal 2d numeric arrays
octave> a = magic (4);
octave> serialize (a)
ans = double([16 2 3 13;5 11 10 8;9 7 6 12;4 14 15 1])
octave> assert (eval (serialize (a)), a)

## Works for normal 3d numeric arrays with all precision
octave> a = rand (3, 3, 3);
octave> serialize (a)
ans = cat(3,double([0.53837757395682650507 0.41720691649633284692 0.66860079620859769189;0.018390655109800025518 0.56538265981533797344 0.20709955358395887304;0.86811365238275806089 0.18398187533949311723 0.20280927116918162634]),double([0.40869259684132724919 0.96877003954154328191 0.32138458265911834522;0.37357584261201565168 0.69925333907961184643 0.10937000120952171389;0.3804633375950405294 0.32942660641033155722 0.79302478034566603604]),double([0.44879474273802461015 0.78659287316710135851 0.49078191654039543534;0.66470978375890155121 0.87740365914996953922 0.77817214018098579409;0.51361398808500036139 0.75508941052835898411 0.70283088935085502591]))
octave> assert (eval (serialize (a)), a)

## Works for 3 dimensional cell arrays of strings
octave> a = reshape ({'foo', 'bar' 'qux', 'lol', 'baz', 'hello', 'there', 'octave'}, [2 2 2])
a = {2x2x2 Cell Array}
octave> serialize (a)
ans = cat(3,{["foo"],["qux"];["bar"],["lol"]},{["baz"],["there"];["hello"],["octave"]})
octave> assert (eval (serialize (a)), a)

然而,更好的问题是为什么你首先想要这样做?如果您这样做的原因是在多个Octave实例之间发送变量,请考虑使用parallelmpi包,它们具有专门设计用于此目的的函数。

谢谢!我不知道Octave里有这个功能。而且它支持多维数组! 我只是想用它来更好地控制如何显示单元格数组,主要是为了代码高尔夫 - Luis Mendo
1
@LuisMendo 我刚刚在 #octave 上与 serialize 函数的作者 Andy 讨论了一些可能的改进方案。你可以创建一个 1D 向量,然后使用 reshape 而不是嵌套调用 cat。原因是 reshape 是一种非常便宜的操作,而多个 cat 将导致大量的复制开销。 - carandraug

5
以下函数适用于任意数组,具有任何嵌套结构和任何形状的数组,只要它们都是二维数组。多维数组不受支持(与mat2str相同)。
该函数还允许为单元格数组指定任意行和列分隔符(例如,选择逗号和空格之间的差异),并可选地强制非单元格数组使用这些分隔符(从而覆盖mat2str的行为)。单元格数组中的默认分隔符为列使用' ',行使用'; '
function y = array2str(x, col_sep, row_sep, sep_noncell)
% Converts a (possibly cell, nested) array to string representation
%
% Optional inputs col_sep and row_sep specify separators for the cell arrays.
% They can be arbitrary strings (but they should be chosen as per Matlab rules
% so that the output string evaluates to the input). Optional flag sep_noncell
% can be used to force those separators with non-cell arrays too, instead of
% the separators produced by mat2str (space and semicolon)

% Default values
if nargin<4
    sep_noncell = false;
end
if nargin<3
    row_sep = '; ';
end
if nargin<2
    col_sep = ' ';
end

x = {x}; % this is to initiallize processing
y = {[]}; % [] indicates content unknown yet: we need to go on
done = false;
while ~done
    done = true; % tentatively
    for n = 1:numel(y);
        if isempty(y{n}) % we need to go deeper
            done = false;
            if ~iscell(x{1}) % we've reached ground
                s = mat2str(x{1}); % final content
                if sep_noncell % replace mat2str's separators if required
                    s = regexprep(s,'(?<=^[^'']*(''[^'']*'')*[^'']*) ', col_sep);
                    s = regexprep(s,'(?<=^[^'']*(''[^'']*'')*[^'']*);', row_sep);
                end
                y{n} = s; % put final content...
                x(1) = []; % ...and remove from x
            else % advance one level
                str = ['{' repmat([{[]}, col_sep], 1, numel(x{1})) '}'];
                ind_sep = find(cellfun(@(t) isequal(t, col_sep), str));
                if ~isempty(ind_sep)
                    str(ind_sep(end)) = []; % remove last column separator
                    ind_sep(end) = [];
                end
                step_sep = size(x{1}, 2);
                str(ind_sep(step_sep:step_sep:end)) = {row_sep};
                y = [y(1:n-1) str y(n+1:end)]; % mark for further processing...
                x = [reshape(x{1}.', 1, []) x(2:end)]; % ...and unbox x{1},
                    % transposed and linearized
            end
        end
    end
end
y = [y{:}]; % concatenate all strings

上述函数使用正则表达式来强制非单元数组中指定的分隔符。这在Matlab中有效,但在Octave中无效,因为支持的反向查找模式受限。以下修改后的版本避免了正则表达式,因此在Matlab和Octave中均有效。与第一个版本相比,只有if sep_noncell和匹配的end之间的部分发生了变化。

function y = array2str(x, col_sep, row_sep, sep_noncell)
% Converts a (possibly cell, nested) array to string representation.
% Octave-friendly version
%
% Optional inputs col_sep and row_sep specify separators for the cell arrays.
% They can be arbitrary strings (but they should be chosen as per Matlab rules
% so that the output string evaluates to the input). Optional flag sep_noncell
% can be used to force those separators with non-cell arrays too, instead of
% the separators produced by mat2str (space and semicolon)

% Default values
if nargin<4
    sep_noncell = false;
end
if nargin<3
    row_sep = '; ';
end
if nargin<2
    col_sep = ' ';
end

x = {x}; % this is to initiallize processing
y = {[]}; % [] indicates content unknown yet: we need to go on
done = false;
while ~done
    done = true; % tentatively
    for n = 1:numel(y);
        if isempty(y{n}) % we need to go deeper
            done = false;
            if ~iscell(x{1}) % we've reached ground
                s = mat2str(x{1}); % final content
                if sep_noncell % replace mat2str's separators if required
                    for k = flip(find(~mod(cumsum(s==''''),2) & s==' ')) % process
                        % backwards, because indices to the right will become invalid
                        s = [s(1:k-1) col_sep s(k+1:end)];
                    end
                    for k = flip(find(~mod(cumsum(s==''''),2) & s==';'))
                        s = [s(1:k-1) row_sep s(k+1:end)];
                    end
                end
                y{n} = s; % put final content...
                x(1) = []; % ...and remove from x
            else % advance one level
                str = ['{' repmat([{[]}, col_sep], 1, numel(x{1})) '}'];
                ind_sep = find(cellfun(@(t) isequal(t, col_sep), str));
                if ~isempty(ind_sep)
                    str(ind_sep(end)) = []; % remove last column separator
                    ind_sep(end) = [];
                end
                step_sep = size(x{1}, 2);
                str(ind_sep(step_sep:step_sep:end)) = {row_sep};
                y = [y(1:n-1) str y(n+1:end)]; % mark for further processing...
                x = [reshape(x{1}.', 1, []) x(2:end)]; % ...and unbox x{1},
                    % transposed and linearized
            end
        end
    end
end
y = [y{:}]; % concatenate all strings

它是如何工作的

我选择了一种非递归方法,因为我通常比递归更喜欢迭代。

输出是通过在单元数组(y)中保留子字符串或空数组([])来逐步构建的。单元格数组y中的空数组表示“需要进一步处理”。子字符串定义了“结构”,或最终嵌套单元格的最深层次中的数字、字符或逻辑内容。

在每次迭代中,y中找到的第一个空数组被实际内容或子字符串和其他空数组替换以供稍后处理。当y不包含任何空数组时,该过程结束,并将y的所有子字符串连接起来以获得最终字符串输出。

例如,给定输入x={[10 20], {'abc'; false; true;}};并调用y=array2str(x),则每个步骤中的数组y都是一个包含以下内容的单元格数组:

'{'   []   ', '   []   '}'

'{'   '[10 20]'   ', '   []   '}'

'{'   '[10 20]'   ', '   '{'   []   '; '   []   '; '   []   '}'   '}'

'{'   '[10 20]'   ', '   '{'   ''abc''   '; '   []   '; '   []   '}'   '}'

'{'   '[10 20]'   ', '   '{'   ''abc''   '; '   'false'   '; '   []   '}'   '}'

'{'   '[10 20]'   ', '   '{'   ''abc''   '; '   'false'   '; '   'true'   '}'   '}'

而后者最终被连接成字符串。
'{[10 20] {''abc''; false; true}}'

作为具有自定义分隔符的示例,array2str(x, ', ', '; ', true) 将返回:
'{[10, 20], {''abc''; false; true}}'

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接