Какой самый быстрый способ подсчета элементов в массиве?
в моих моделях одной из наиболее повторяющихся задач является подсчет количества каждого элемента в массиве. Подсчет идет из закрытого набора, поэтому я знаю, что есть X
типы элементов, и все или некоторые из них заполняют массив вместе с нулями, представляющими "пустые" ячейки. Массив никак не сортируется и может быть довольно длинным (около 1M элементов), и эта задача выполняется тысячи раз во время одного моделирования (которое также является частью сотен симуляций). Этот результатом должен быть вектор r
в размере X
, Так что r(k)
сумма k
в массиве.
пример:
на X = 9
, если у меня есть следующий входной вектор:
v = [0 7 8 3 0 4 4 5 3 4 4 8 3 0 6 8 5 5 0 3]
Я хотел бы получить этот результат:
r = [0 0 4 4 3 1 1 3 0]
обратите внимание, что мне не нужно количество нулей и что элементы ,которые не отображаются в массиве (например,2
) у 0
в соответствующей позиции результирующего вектора (r(2) == 0
).
что будет быстрый способ достижения этой цели?
2 ответов
tl; dr: самый быстрый метод зависит от размера массива. Для массива меньше 214 метод 3 ниже (accumarray
) быстрее. Для массивов больше, чем этот метод 2 ниже (histcounts
) лучше.
UPDATE: я тестировал это также с неявное вещания, который был введен в 2016b, и результаты почти равны bsxfun
подход, без существенной разницы (относительно других методов) в этом метод.
давайте посмотрим, каковы доступные методы для выполнения этой задачи. Для следующих примеров мы предположим X
и n
элементы, от 1 до n
, и наш массив интереса M
, который представляет собой массив столбцов, который может отличаться по размеру. Наш вектор результатов будет spp
1, таких, что spp(k)
число k
s в M
. Хотя я пишу здесь о X
, в коде нет явной реализации этого ниже я просто определяю n = 500
и X
неявно 1:500
.
наивный for
цикл
Самый простой и простой способ справиться с этой задачей-это for
цикл, который повторяет элементы в X
и подсчитайте количество элементов в M
это равно:
function spp = loop(M,n)
spp = zeros(n,1);
for k = 1:size(spp,1);
spp(k) = sum(M==k);
end
end
это, конечно, не так умно, особенно если только небольшая группа элементов из X
заполнение M
, поэтому нам лучше сначала поискать те, что уже в M
:
function spp = uloop(M,n)
u = unique(M); % finds which elements to count
spp = zeros(n,1);
for k = u(u>0).';
spp(k) = sum(M==k);
end
end
обычно в MATLAB рекомендуется максимально использовать встроенные функции, так как в большинстве случаев они намного быстрее. Я подумал о 5 вариантах сделать это:
1. Функция tabulate
Функция tabulate
возвращает очень удобную таблицу частот, которая на первый взгляд кажется идеальным решением для этого задача:
function tab = tabi(M)
tab = tabulate(M);
if tab(1)==0
tab(1,:) = [];
end
end
единственное исправление, которое нужно сделать, это удалить первую строку таблицы, если она подсчитывает 0
элемент (возможно, в M
).
2. Функция histcounts
Еще один вариант, который можно настроить довольно легко для нашей потребности в нем histcounts
:
function spp = histci(M,n)
spp = histcounts(M,1:n+1);
end
здесь, чтобы подсчитать все различные элементы от 1 до n
отдельно, мы определяем края 1:n+1
, так что каждый элемент в X
имеет свой собственный ящик. Мы могли бы написать также histcounts(M(M>0),'BinMethod','integers')
, но я уже тестировал его, и это занимает больше времени (хотя это делает функцию независимой от n
).
3. Функция accumarray
Следующий вариант, который я приведу здесь, - это использование функции accumarray
:
function spp = accumi(M)
spp = accumarray(M(M>0),1);
end
здесь мы даем функцию M(M>0)
в качестве ввода, чтобы пропустить нули и использовать 1
как vals
вход для подсчета всех уникальных элементы.
4. Функция bsxfun
Мы даже можем использовать двоичную операцию @eq
(т. е. ==
) искать все элементы из каждого типа:
function spp = bsxi(M,n)
spp = bsxfun(@eq,M,1:n);
spp = sum(spp,1);
end
если мы сохраним первый вход M
и второй 1:n
в разных измерениях, поэтому один столбец вектора в другой вектор-строку, то функция сравнивает каждый элемент M
С каждым элементом в 1:n
, и создать length(M)
- by -n
логические матрица, чем мы можем суммировать, чтобы получить желаемый результат.
5. Функция ndgrid
Другой вариант, похожий на bsxfun
, чтобы явно создать две матрицы всех возможностей, используямы знаем, что входной вектор всегда целые числа, так почему бы не использовать это, чтобы "выжать" немного больше производительности алгоритма?
я экспериментировал с некоторыми оптимизациями двух лучших методов binning предложенный OP и вот что я придумал:
- количество уникальных значений (
X
в вопросе, илиn
в Примере) должно быть явно преобразовано в (беззнаковое) целое число тип. - быстрее вычислить дополнительный Бин, а затем отбросить его, чем" только обрабатывать " допустимые значения (см. ниже).
эта функция занимает около 30 секунд, чтобы запустить на моей машине. Я использую MATLAB R2016a.
function q38941694
datestr(now)
N = 25;
func_times = zeros(N,4);
for n = 1:N
func_times(n,:) = timing_hist(2^n,500);
end
% Plotting:
figure('Position',[572 362 758 608]);
hP = plot(1:n,log10(func_times.*1000),'-o','MarkerEdgeColor','k','LineWidth',2);
xlabel('Log_2(Array size)'); ylabel('Log_{10}(Execution time) (ms)')
legend({'histcounts (double)','histcounts (uint)','accumarray (old)',...
'accumarray (new)'},'FontSize',12,'Location','NorthWest')
grid on; grid minor;
set(hP([2,4]),'Marker','s'); set(gca,'Fontsize',16);
datestr(now)
end
function out = timing_hist(N,n)
% Convert n into an appropriate integer class:
if n < intmax('uint8')
classname = 'uint8';
n = uint8(n);
elseif n < intmax('uint16')
classname = 'uint16';
n = uint16(n);
elseif n < intmax('uint32')
classname = 'uint32';
n = uint32(n);
else % n < intmax('uint64')
classname = 'uint64';
n = uint64(n);
end
% Generate an input:
M = randi([0 n],N,1,classname);
% Time different options:
warning off 'MATLAB:timeit:HighOverhead'
func_times = {'histcounts (double)','histcounts (uint)','accumarray (old)',...
'accumarray (new)';
timeit(@() histci(double(M),double(n))),...
timeit(@() histci(M,n)),...
timeit(@() accumi(M)),...
timeit(@() accumi_new(M))
};
out = cell2mat(func_times(2,:));
end
function spp = histci(M,n)
spp = histcounts(M,1:n+1);
end
function spp = accumi(M)
spp = accumarray(M(M>0),1);
end
function spp = accumi_new(M)
spp = accumarray(M+1,1);
spp = spp(2:end);
end