当未明确知道输入参数的数量时,使用可变数量的输入参数调用函数

Function call with variable number of input arguments when number of input arguments is not explicitly known

我有一个变量 pth,它是维度 1xn 的元胞数组,其中 n 是用户输入。 pth 中的每个元素本身就是一个元胞数组,length(pth{k}) 对于 k=1:n 是变量(另一个函数的结果)。每个元素 pth{k}{kk} 其中 k=1:nkk=1:length(pth{k}) 是 integers/node 数字的一维向量,其长度又是可变的。总而言之,我有可变数量的可变长度向量,它们组织在可变数量的元胞数组中。

当你从 pth{1}pth{2}{pth{3} 等中随机取一个向量时,我想尝试找到所有可能的交点...似乎这样做的文件交换,例如 this one or this one。我遇到的问题是你需要这样调用函数:

mintersect(v1,v2,v3,...)

而且我不能写出一般情况下的所有输入,因为我不知道具体有多少(这将是上面的 n)。理想情况下,我想做这样的事情;

mintersect(pth{1}{1},pth{2}{1},pth{3}{1},...,pth{n}{1})
mintersect(pth{1}{1},pth{2}{2},pth{3}{1},...,pth{n}{1})
mintersect(pth{1}{1},pth{2}{3},pth{3}{1},...,pth{n}{1})
etc...
mintersect(pth{1}{1},pth{2}{length(pth{2})},pth{3}{1},...,pth{n}{1})
mintersect(pth{1}{1},pth{2}{1},pth{3}{2},...,pth{n}{1})
etc...

继续尝试所有可能的组合,但我无法用代码编写。来自 File Exchange 的 function 看起来是找到所有可能组合的好方法,但我再次遇到具有可变输入数量的函数调用的相同问题:

allcomb(1:length(pth{1}),1:length(pth{2}),...,1:length(pth{n}))

当您无法物理指定所有输入参数(因为输入参数的数量是可变的)时,有人知道如何解决输入参数数量可变的函数调用问题吗?这同样适用于 MATLAB 和 Octave,因此有两个标签。关于从每个 pth{k} 随机抽取向量时如何找到所有可能 combinations/intersections 的任何其他建议欢迎!

编辑 2020 年 5 月 27 日

感谢疯狂物理学家的回答,我最终使用了以下有效的方法:

disp('Computing intersections for all possible paths...')
grids = cellfun(@(x) 1:numel(x), pth, 'UniformOutput', false);
idx = cell(1, numel(pth));
[idx{:}] = ndgrid(grids{:});
idx = cellfun(@(x) x(:), idx, 'UniformOutput', false);
idx = cat(2, idx{:});
valid_comb = [];
k = 1;

for ii = idx'
    indices = reshape(num2cell(ii), size(pth));
    selection = cellfun(@(p,k) p{k}, pth, indices, 'UniformOutput', false);
    if my_intersect(selection{:})
       valid_comb = [valid_comb k];
    endif
    k = k+1;
end

我自己的版本类似,但使用 for 循环而不是逗号分隔列表:

disp('Computing intersections for all possible paths...')
grids = cellfun(@(x) 1:numel(x), pth, 'UniformOutput', false);
idx = cell(1, numel(pth));
[idx{:}] = ndgrid(grids{:});
idx = cellfun(@(x) x(:), idx, 'UniformOutput', false);
idx = cat(2, idx{:});
[n_comb,~] = size(idx);
temp = cell(n_pipes,1);
valid_comb = [];
k = 1;

for k = 1:n_comb
  for kk = 1:n_pipes
    temp{kk} = pth{kk}{idx(k,kk)};
  end
  if my_intersect(temp{:})
    valid_comb = [valid_comb k];
  end
end

在这两种情况下,valid_comb 都有有效组合的索引,然后我可以使用类似的方法检索它们:

valid_idx = idx(valid_comb(1),:);
for k = 1:n_pipes
  pth{k}{valid_idx(k)} % do something with this
end

当我用一些样本数据对这两种方法进行基准测试时(pth4x1pth 的 4 个元素是 2x19x18x169x1),我得到了以下结果:

>> benchmark

Elapsed time is 51.9075 seconds.
valid_comb =  7112

Elapsed time is 66.6693 seconds.
valid_comb =  7112

所以疯狂物理学家的方法快了大约 15 秒。

我也误解了mintersect所做的,这不是我想要的。我想找到一个组合,其中两个或多个向量中不存在任何元素,所以我结束了编写 mintersect:

的版本
function valid_comb = my_intersect(varargin)

  % Returns true if a valid combination i.e. no combination of any 2 vectors 
  % have any elements in common

  comb_idx = combnk(1:nargin,2);
  [nr,nc] = size(comb_idx);
  valid_comb = true;
  k = 1;

  % Use a while loop so that as soon as an intersection is found, the execution stops
  while valid_comb && (k<=nr)
    temp = intersect(varargin{comb_idx(k,1)},varargin{comb_idx(k,2)});
    valid_comb = isempty(temp) && valid_comb;
    k = k+1;
  end

end

我相信这可以解决问题。为 k=1:nkk=1:length(pth{k}).

pth{k}{kk} 中所有可能的向量组合调用 mintersect

使用 eval 并稍微修改 sprintf/compose。请注意,通常使用 evalvery much discouraged。如果这是您的需要,可以添加更多评论。

% generate some data
n = 5;
pth = cell(1,n);

for k = 1:n
    pth{k} = cell(1,randi([1 10]));
    for kk = 1:numel(pth{k})
        pth{k}{kk} = randi([1 100], randi([1 10]), 1);
    end
end

% get all combs
str_to_eval = compose('1:length(pth{%i})', 1:numel(pth));
str_to_eval = strjoin(str_to_eval,',');
str_to_eval = sprintf('allcomb(%s)',str_to_eval);
% use eval to get all combinations for a given pth
all_combs = eval(str_to_eval);

% and make strings to eval in intersect
comp = num2cell(1:numel(pth));
comp = [comp ;repmat({'%i'}, 1, numel(pth))];
str_pattern = sprintf('pth{%i}{%s},', comp{:});
str_pattern = str_pattern(1:end-1); % get rid of last ,

strings_to_eval = cell(length(all_combs),1);
for k = 1:size(all_combs,1)
    strings_to_eval{k} = sprintf(str_pattern, all_combs(k,:));
end

% and run eval on all those strings 
result = cell(length(all_combs),1);
for k = 1:size(all_combs,1)
    result{k} = eval(['mintersect(' strings_to_eval{k} ')']);
    %fprintf(['mintersect(' strings_to_eval{k} ')\n']); % for debugging
end

对于随机生成的 pth,代码会生成以下要评估的字符串(其中一些 pth{k} 只有一个单元格用于说明):

mintersect(pth{1}{1},pth{2}{1},pth{3}{1},pth{4}{1},pth{5}{1})
mintersect(pth{1}{1},pth{2}{1},pth{3}{1},pth{4}{2},pth{5}{1})
mintersect(pth{1}{1},pth{2}{1},pth{3}{1},pth{4}{3},pth{5}{1})
mintersect(pth{1}{1},pth{2}{1},pth{3}{2},pth{4}{1},pth{5}{1})
mintersect(pth{1}{1},pth{2}{1},pth{3}{2},pth{4}{2},pth{5}{1})
mintersect(pth{1}{1},pth{2}{1},pth{3}{2},pth{4}{3},pth{5}{1})
mintersect(pth{1}{2},pth{2}{1},pth{3}{1},pth{4}{1},pth{5}{1})
mintersect(pth{1}{2},pth{2}{1},pth{3}{1},pth{4}{2},pth{5}{1})
mintersect(pth{1}{2},pth{2}{1},pth{3}{1},pth{4}{3},pth{5}{1})
mintersect(pth{1}{2},pth{2}{1},pth{3}{2},pth{4}{1},pth{5}{1})
mintersect(pth{1}{2},pth{2}{1},pth{3}{2},pth{4}{2},pth{5}{1})
mintersect(pth{1}{2},pth{2}{1},pth{3}{2},pth{4}{3},pth{5}{1})
mintersect(pth{1}{3},pth{2}{1},pth{3}{1},pth{4}{1},pth{5}{1})
mintersect(pth{1}{3},pth{2}{1},pth{3}{1},pth{4}{2},pth{5}{1})
mintersect(pth{1}{3},pth{2}{1},pth{3}{1},pth{4}{3},pth{5}{1})
mintersect(pth{1}{3},pth{2}{1},pth{3}{2},pth{4}{1},pth{5}{1})
mintersect(pth{1}{3},pth{2}{1},pth{3}{2},pth{4}{2},pth{5}{1})
mintersect(pth{1}{3},pth{2}{1},pth{3}{2},pth{4}{3},pth{5}{1})
mintersect(pth{1}{4},pth{2}{1},pth{3}{1},pth{4}{1},pth{5}{1})
mintersect(pth{1}{4},pth{2}{1},pth{3}{1},pth{4}{2},pth{5}{1})
mintersect(pth{1}{4},pth{2}{1},pth{3}{1},pth{4}{3},pth{5}{1})
mintersect(pth{1}{4},pth{2}{1},pth{3}{2},pth{4}{1},pth{5}{1})
mintersect(pth{1}{4},pth{2}{1},pth{3}{2},pth{4}{2},pth{5}{1})
mintersect(pth{1}{4},pth{2}{1},pth{3}{2},pth{4}{3},pth{5}{1})

几个有助于构建解决方案的要点:

  • This post shows you how to construct a Cartesian product between arbitrary arrays using ndgrid.
  • cellfun 同时接受多个元胞数组,您可以使用它们来索引特定元素。
  • 您可以使用元胞数组从函数中捕获可变数量的参数,如图所示here

所以让我们从最外层的数组中获取 ndgrid 的输入:

grids = cellfun(@(x) 1:numel(x), pth, 'UniformOutput', false);

现在您可以创建一个包含网格乘积的索引:

index = cell(1, numel(pth));
[index{:}] = ndgrid(grids{:});

你想把所有的网格都做成列向量,横向拼接起来。该矩阵的行将表示笛卡尔索引 select 每次迭代时 pth 的元素:

index = cellfun(@(x) x(:), index, 'UniformOutput', false);
index = cat(2, index{:});

如果将 index 的一行转换为元胞数组,则可以 运行 将其 pth 锁定为 select 正确的元素并调用 mintersect 在结果上。

for i = index'
    indices = num2cell(i');
    selection = cellfun(@(p, i) p{i}, pth, indices, 'UniformOutput', false);
    mintersect(selection{:});
end

这是在假设pth是一个行数组的情况下写的。如果不是这种情况,对于一般情况,您可以将循环的第一行更改为 indices = reshape(num2cell(i), size(pth));,对于列情况,只需将其更改为 indices = num2cell(i);。关键是 indices 的单元格必须与 pth 的形状相同才能同步迭代它。它已经生成为具有相同数量的元素。

正如 Madphysicist 所指出的,我误解了您的初始元胞数组的初始结构,但这一点是成立的。将未知数量的参数传递给函数的方法是通过 comma-separated-list generation, and your function needs to support it by being declared with varargin。更新了下面的示例。

创建一个辅助函数以从每个主单元格中收集随机子单元格:

% in getRandomVectors.m
function Out = getRandomVectors(C)   % C: a double-jagged array, as described
    N   = length(C);
    Out = cell(1, N);
    for i = 1 : length(C)
        Out{i} = C{i}{randi( length(C{i}) )};
    end
end

然后假设你已经有一个 mintersect 函数定义如下:

% in mintersect.m
function Intersections = mintersect( varargin )
    Vectors = varargin;
    N = length( Vectors );
    for i = 1 : N;    for j = 1 : N
        Intersections{i,j} = intersect( Vectors{i}, Vectors{j} );
    end; end
end

然后这样称呼:

C = { { 1:5, 2:4, 3:7 }, {1:8}, {2:4, 3:9, 2:8} }; % example double-jagged array

In  = getRandomVectors(C);   % In is a cell array of randomly selected vectors
Out = mintersect( In{:} );   % Note the csl-generator syntax

PS。我注意到您对 mintersect 的定义与链接的定义不同。可能只是你没有很好地描述你想要什么,在这种情况下我的 mintersect 函数不是你想要的。我所做的是为提供的向量生成所有可能的交集。您链接到的那个产生一个单一的交叉点,这是所有提供的向量所共有的。使用最适合您的那个。不过,使用它的基本原理是相同的。

PS。从您的描述中也不完全清楚您所追求的是每个 n 的随机向量 k,还是所有 n 和 k 上可能向量的整个 space。上述解决方案是前者。如果您想要后者,请参阅 MadPhysicist 关于如何创建所有可能索引的笛卡尔积的解决方案。