Стабильный аккумулятор в MATLAB

Встроенная функция MATLAB accumarray принимает функцию fun в качестве четвертого аргумента.

A = accumarray(subs,val,sz,fun);

Это применяется fun к каждому подмножеству элементов в val, которые имеют идентичные индексы в subs. Однако в документации говорится:

Если индексы в subs не отсортированы относительно их линейных индексов, fun не должен зависеть от порядка значений во входных данных.

Как мы можем реализовать стабильную версию accumarray, которая не имеет этого ограничения, но гарантирует, что подмножества будут принимать тот же порядок, что и val?

Пример:

subs = [1:10,1:10];
val = 1:20;
accumarray(subs(:), val(:), [], @(x)x(end)).'

Ожидаемый результат будет 11:20, если accumarray будет стабильным. Фактически результат:

ans =
    11    12    13    14     5     6     7    18    19    20

Наша реализация должна дать:

accumarrayStable(subs(:), val(:), [], @(x)x(end)).'`
ans =
    11    12    13    14    15    16    17    18    19    20

person knedlsepp    schedule 11.02.2015    source источник


Ответы (1)


Мы можем использовать sortrows в качестве шага предварительной обработки, чтобы сначала отсортировать индексы и соответствующие значения, как указано в документации:

SORTROWS использует стабильную версию быстрой сортировки.

Поскольку индексы в subs должны быть отсортированы относительно их линейных индексов, нам нужно отсортировать их в обратном лексикографическом порядке. Этого можно добиться, изменив порядок столбцов до и после использования sortrows.

Это дает нам следующий код для стабильной версии accumarray:

function A = accumarrayStable(subs, val, varargin)
[subs(:,end:-1:1), I] = sortrows(subs(:,end:-1:1));
A = accumarray(subs, val(I), varargin{:});

Альтернатива:

Как было предложено Луисом Мендо, вместо sortrows можно также сгенерировать линейные индексы из индексов и использовать вместо них sort.

function A = accumarrayStable(subs, val, varargin)
if numel(varargin)>0 && ~isempty(varargin{1})
    sz = varargin{1};
else
    sz = max(subs,[],1);
end
[~, I] = sort(subs*cumprod([1,sz(1:end-1)]).');
A = accumarray(subs(I,:), val(I), sz, varargin{:});

Обратите внимание, что мы должны использовать 1+(subs-1)*cumprod([1,sz(1:end-1)]).' для преобразования в линейные индексы. Мы опускаем +1 и -1, так как результат sort будет таким же; что экономит нам несколько циклов.

Какое из вышеперечисленных решений будет быстрее, будет зависеть от вашей машины и версии MATLAB. Вы можете протестировать, например, через:

A = randi(10, 1e4, 5); 
timeit(@()accumarrayStable(A(:,1:end-1), A(:,end), [], @(x)x(1))
person knedlsepp    schedule 11.02.2015
comment
Я собирался предложить sortrows относительно вашего комментария (он используется в тоже ответ @ Divakar). Хорошие вопросы и ответы! - person Luis Mendo; 11.02.2015
comment
@LuisMendo: Я подумал, что кому-то это когда-нибудь понадобится, так почему бы для разнообразия не ответить на мой собственный вопрос. :-) - person knedlsepp; 11.02.2015
comment
Реверсирование индексов перед подачей в sortrows необходимо, чтобы сортировка выполнялась в смысле линейной индексации, верно? Может быть, ты мог бы добавить это к своему ответу - person Luis Mendo; 11.02.2015
comment
Возможно, более быстрым подходом было бы явное вычисление линейных индексов (что можно очень эффективно сделать с умножением матриц) и использование sort вместо sortrows - person Luis Mendo; 11.02.2015
comment
@LuisMendo: Я думал то же самое, но тогда нам нужно было бы сначала узнать размеры матрицы, используя max, что, как я думал, добавит больше сложности, чем необходимо. Я попробую. - person knedlsepp; 11.02.2015
comment
@LuisMendo: Вы имели в виду следующее: ideone.com/83TSFx? Я еще не проводил обширного тестирования, но он кажется медленнее, чем текущий ответ. (Я мог бы избавиться от анонимной функции и вместо этого оставить комментарий.) У вас есть предложения? - person knedlsepp; 12.02.2015
comment
Да, анонимные функции работают медленно. Кроме того, вы можете избавиться от этих -1 и +1, чтобы немного выиграть. ideone.com/hUNzqV - person Luis Mendo; 12.02.2015
comment
К сожалению, я забыл cumprod и размер, указанный пользователем. Но ты получил идею. Кроме того, может случиться так, что reshape с линейными индексами (как вы) быстрее или медленнее, чем при использовании исходных субиндексов ... кто знает - person Luis Mendo; 12.02.2015
comment
@LuisMendo: Думаю, реализация sortrows неплохая. Я по-прежнему получаю лучшую производительность с исходным решением, чем с обновленным. - person knedlsepp; 12.02.2015
comment
Для меня вторая версия быстрее: ideone.com/RBFLe7 (R2014b), ideone.com/E3QruZ (R2010b). Так ... может выложить обе версии? - person Luis Mendo; 12.02.2015
comment
@LuisMendo: добавлю вторую версию. Кажется, у него есть что-то делать, что я тестировал с fun = @(x)x(end), который по какой-то причине замедляет версию 2 намного больше, чем версию 1 ... Мы, вероятно, должны использовать тестовый пример, где fun~=sum, иначе стабильный материал не имеет никакого смысла в любом случае. - person knedlsepp; 12.02.2015
comment
Я склонен согласиться с тем, что sum должен быть ссылкой (это функция по умолчанию, применяемая accumarray, и одна из самых распространенных). Но стабильность на самом деле имеет больше смысла для @(x) x(end), чем для sum, max и т. Д. (Где порядок не имеет значения). Для @(x) x(end) выигрыш меньше, но есть еще: ideone.com/njHPfV - person Luis Mendo; 12.02.2015
comment
@LuisMendo: Хорошо. Я просто оставлю обе версии и тест; как и на моем Macbook, я постоянно получаю противоположные результаты ans = 0.0523 и ans = 0.0583. Спасибо за ваш вклад! ;-) - person knedlsepp; 12.02.2015
comment
Теперь я вижу, что получил ваш комментарий о том, что не неправильно использует sum :-) Итак, мы согласны с тем, что стабильность имеет смысл для нестандартных функций. - person Luis Mendo; 12.02.2015
comment
sortrows также принимает аргумент, определяющий порядок столбцов, поэтому вместо [subs(:,end:-1:1), I] = sortrows(subs(:,end:-1:1)); вы можете использовать [subs, I] = sortrows(subs, size(subs,2):-1:1); - person KQS; 12.02.2016