Какой самый быстрый способ подсчета элементов в массиве?

в моих моделях одной из наиболее повторяющихся задач является подсчет количества каждого элемента в массиве. Подсчет идет из закрытого набора, поэтому я знаю, что есть 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, который представляет собой массив столбцов, который может отличаться по размеру. Наш вектор результатов будет spp1, таких, что spp(k) число ks в 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 в Примере) должно быть явно преобразовано в (беззнаковое) целое число тип.
  • быстрее вычислить дополнительный Бин, а затем отбросить его, чем" только обрабатывать " допустимые значения (см. ниже).

Though improvements are not large, it might be substantial when large datasets are involved.

эта функция занимает около 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