Вызов функции с переменным количеством входных аргументов, когда количество входных аргументов явно не известно

У меня есть переменная pth, которая представляет собой массив ячеек размера 1xn, где n - пользовательский ввод. Каждый из элементов в pth сам по себе является массивом ячеек, а length(pth{k}) для k=1:n является переменным (результатом другой функции). Каждый элемент pth{k}{kk}, где k=1:n и kk=1:length(pth{k}) - это одномерный вектор целых чисел / номеров узлов снова переменной длины. Итак, чтобы подвести итог, у меня есть переменное количество векторов переменной длины, организованных в доступное количество массивов ячеек.

Я хотел бы попытаться найти все возможные пересечения, когда вы берете вектор наугад из pth{1}, pth{2}, {pth{3} и т. Д. В обмене файлами есть различные функции, которые, кажется, делают это, например этот или этот. У меня проблема в том, что вам нужно вызвать функцию следующим образом:

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 выглядит как хороший способ найти все возможные комбинации, но опять же у меня такая же проблема с вызовом функции с переменным количеством входов:

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

Кто-нибудь знает, как обойти эту проблему вызовов функций с переменным количеством входных аргументов, когда вы не можете физически указать все входные аргументы, потому что их количество является переменным? Это в равной степени относится к MATLAB и Octave, следовательно, к двум тегам. Любые другие предложения о том, как найти все возможные комбинации / пересечения при случайном взятии вектора из каждого pth{k} приветствуются!

ИЗМЕНИТЬ 27.05.20

Благодаря ответу Безумного физика я в конечном итоге использовал следующее, которое работает:

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

Когда я сравнил два подхода с некоторыми образцами данных (pth = 4x1, а 4 элемента pth 2x1, 9x1, 8x1 и 69x1), я получил следующие результаты:

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

person am304    schedule 22.05.2020    source источник
comment
Не уверен, почему ни один из ответов не упоминает об этом, но канонический способ создания функции, которая принимает переменное количество аргументов, - это varargin   -  person Tasos Papastylianou    schedule 23.05.2020
comment
@TasosPapastylianou. Потому что это буквально не имеет отношения к вопросу. Вопрос в том, как обрабатывать переменное количество аргументов, не делая того, чего делать не следует.   -  person Mad Physicist    schedule 24.05.2020
comment
@MadPhysicist Я не согласен, но это помогло мне понять, что я неправильно понял структуру исходного массива ячеек по одному измерению, поэтому спасибо за комментарий :) Я обновил свой ответ. Что касается того, почему я не согласен, для меня varargin явно является частью решения, как показано в ссылках OP функций. На мой взгляд, передача переменных аргументов состоит из двух частей: a. возможность создавать их программно, и b. имея функцию, которая может с ними справиться. Списки, разделенные запятыми, работают с первым, а varargin - со вторым. 'b' имеет значение, поскольку связанные функции не выполняют то, что запрашивает OP.   -  person Tasos Papastylianou    schedule 24.05.2020


Ответы (3)


Пара полезных моментов для построения решения:

  • Этот пост показывает, как построить декартово произведение между произвольными массивами с помощью ndgrid < / а>.
  • cellfun одновременно принимает несколько массивов ячеек, которые можно использовать для индексировать определенные элементы.
  • Вы можете захватить переменное количество аргументов из функции, используя массивы ячеек, как показано здесь.

Итак, давайте получим входные данные для ndgrid из вашего внешнего массива:

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

Теперь вы можете создать индекс, содержащий произведение сеток:

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

Вы хотите превратить все сетки в векторы-столбцы и соединить их боками. Строки этой матрицы будут представлять декартовы индексы для выбора элементов pth на каждой итерации:

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

Если вы превратите строку index в массив ячеек, вы можете запустить ее синхронно с pth, чтобы выбрать правильные элементы и вызвать 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, чтобы проходить по ней синхронно. Он уже создан с таким же количеством элементов.

person Mad Physicist    schedule 23.05.2020
comment
@CrisLuengo. Что-то вроде этого? - person Mad Physicist; 23.05.2020
comment
Да вот так. - person Cris Luengo; 23.05.2020
comment
@CrisLuengo. Знаете ли вы, есть ли способ оптимизировать материал, окружающий index = cat(2, index{:});. Я написал это на мобильном телефоне, поэтому мне негде что-то надежно протестировать. - person Mad Physicist; 23.05.2020
comment
Я полагаю, вы могли бы добавить дополнительное одноэлементное измерение впереди во время построения ndgrid, затем vertcat и проиндексировать все это через (:, :). - person Tasos Papastylianou; 24.05.2020
comment
Привет, спасибо за ответ. Это дало мне долгий путь к решению, и я, конечно, не смог бы разобраться в этом самостоятельно. Но он падает в строке selection = cellfun(@(p, i) p(i), pth, indices, 'UniformOutput', false) с сообщением об ошибке error: cellfun: dimensions mismatch (я пробовал и в Octave, и в MATLAB). Аналогично, pth(indices) или даже pth{indices} приводит к сообщению об ошибке error: pth(<cell>): subscripts must be either integers 1 to (2^63)-1 or logicals. Может быть, потому, что pth - это двумерный массив ячеек, поэтому вам нужно что-то вроде pth{k}{k}? - person am304; 26.05.2020
comment
Для справки, в некоторых примерах данных size(pth) - это 3x1, size (pth{1}) - это 75x1, size(pth{2}) - это 216x1, а size(pth{3}) - это 60x1, каждый из pth{k} является массивом ячеек. - person am304; 26.05.2020
comment
@ am304 подожди немного. Я написал это на мобильном телефоне, так что это полностью не проверено. Я исправлю это, как только доберусь до своего матлаба. - person Mad Physicist; 26.05.2020
comment
@MadPhysicist Спасибо, я тоже над этим работаю и близок к тому, чтобы найти решение, используя первую часть того, что вы предложили. - person am304; 26.05.2020
comment
@ am304. Ответ был написан с предположением, что pth является массивом строк, поэтому вы можете сделать indices = reshape(num2cell(i'), size(pth)); - person Mad Physicist; 26.05.2020
comment
@ am304, я обновил свой ответ, добавив небольшую аннотацию внизу. - person Mad Physicist; 26.05.2020
comment
Спасибо, я обнаружил, что для работы mintersect мне пришлось изменить p(i) в selection = cellfun(@(p, i) p{i}, pth, indices, 'UniformOutput', false); на p{i}. Мой собственный метод, который я разработал параллельно, был немного медленнее по сравнению с вашим, когда я сравнивал его с некоторыми образцами данных. Я также неправильно понял, что сделал mintersect, а это не то, что я хотел, поэтому в итоге я сделал свою собственную версию. В любом случае, я бы не смог решить эту проблему без вашей помощи, поэтому приму ваш ответ и обновлю свой вопрос тем, что я в конечном итоге использовал. - person am304; 27.05.2020

Я считаю, что это помогает. Вызывает mintersect для всех возможных комбинаций векторов в pth{k}{kk} для k=1:n и kk=1:length(pth{k}).

Использую eval и немного поигрался с _6 _ / _ 7_. Обратите внимание, что обычно использование eval очень не рекомендуется . Могу добавить еще комментарии, если это то, что вам нужно.

% 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})
person rinkert    schedule 22.05.2020
comment
Использование eval - очень плохая идея. - person Sardar Usama; 23.05.2020
comment
@SardarUsama Я знаю, но разве нет на это некоторых? В этом случае невозможно ввести все комбинации вручную. Так что, возможно, теряется некоторая эффективность, но на это у меня уходит меньше времени. Альтернативой для eval в моем ответе было бы записать все строки в файл m, а затем запустить его, но если вы создадите этот файл m динамически, не приведет ли это к тому же самому. Что было бы лучше в этом случае? - person rinkert; 23.05.2020
comment
Нет необходимости ни в eval, ни в преобразовании данных в строку. Вместо этого вы можете собрать входные данные в массив ячеек, а затем выполнить mintersect(params{:});. Я не собираюсь отрицать это, но не думаю, что это хорошее решение. - person Cris Luengo; 23.05.2020
comment
Спасибо за Ваш ответ. Я хорошо осведомлен о зле eval, и если бы не было другого варианта, я бы рассмотрел и, вероятно, принял ваш ответ, но я считаю, что ответ MadPhysicist лучше именно потому, что он не полагается на eval, поэтому я принял его ответ вместо этого . Еще раз спасибо. - person am304; 27.05.2020

Как указал Мадфизик, я неправильно понял исходную структуру вашего исходного массива ячеек, однако суть в этом остается. Способ передать неизвестное количество аргументов функции: создание списка, разделенного запятыми, и ваша функция должна поддерживать его, объявляясь с помощью 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. Из вашего описания также не совсем ясно, является ли то, что вам нужно, случайным вектором k для каждого n или всем пространством возможных векторов по всем n и k. Вышеупомянутое решение делает первое. Если вы хотите последнее, посмотрите решение MadPhysicist о том, как вместо этого создать декартово произведение всех возможных индексов.

person Tasos Papastylianou    schedule 23.05.2020
comment
OP не ищет определение mintersect. Они ищут ту часть, которая предшествует этому. Они ищут не случайную выборку, а декартово произведение всех выборок. Кроме того, mintersect назван так именно потому, что это минимальное пересечение: один выход для всех входных массивов. - person Mad Physicist; 24.05.2020
comment
@MadPhysicist вполне возможно. В этом случае фраза «найти все возможные комбинации / пересечения при случайном взятии вектора из каждого p-го {k}» является просто плохой формулировкой. В исходной формулировке вопроса казалось, что единственной причиной для декартова произведения было сохранить его, а затем взять из него случайный компонент, представляющий конкретную комбинацию наугад. В этом случае случайный компонент может быть получен без необходимости в полном декартовом произведении. (т.е. проблема XY). - person Tasos Papastylianou; 24.05.2020
comment
Я согласен с тем, что могут быть разные толкования вопроса. - person Mad Physicist; 25.05.2020
comment
Спасибо за ваш ответ и извинения, если мой вопрос был недостаточно ясен. Однако дело не в том, как определить функцию с множественными входными переменными (я хорошо знаю varargin и знаю, как его использовать), а скорее в том, как вызвать функцию с помощью has был определен с varargin, когда количество входных аргументов явно не известно. Я проверил список, разделенный запятыми, но я не могу заставить его работать без серьезной реструктуризации данных. Например, если я попытаюсь pth{:}{1} взять первый k по всем n, я получаю сообщение об ошибке: error: a cs-list cannot be further indexed. - person am304; 26.05.2020
comment
Чтобы прояснить вопрос, я пытаюсь найти единственное пересечение (которое вполне может быть пустым) на всем n для каждой возможной комбинации k. - person am304; 26.05.2020
comment
@ am304 привет и спасибо за разъяснения! Да, в этом случае ответ MadP - это то, что вам нужно. Что касается cs-списков, да, это правда, к сожалению, они не могут транслироваться так, как вы надеялись. Думайте о них больше как о удобном способе выгрузки списка аргументов, разделенных запятыми, в функции (или в скобки). Он сопоставим с синтаксисом fun(*list) python, синтаксисом R (специфичный для dplyr) fun(!!!list), или с так называемым синтаксисом «splat» julia fun(list...) и т.д. . - person Tasos Papastylianou; 26.05.2020