Настройка:
Этот пост посвящен тестированию решений решений по следующей проблеме:
Дается массив ячеек
S
, отформатированных как числа, разделенные символами подчеркивания, например:
list_of_words = repmat({'02_04_04_52_23_14_54_672_0'},10,1);
Гарантируется для всех строк, что:
- они содержат только десятичные цифры и символы подчеркивания;
- количество символов подчеркивания одинаковое;
- нет двух последовательных символов подчеркивания.
Напишите код MATLAB, который преобразует массив ячеек строк в числовую матрицу
S
& times;M
удваивает (значения на 1000 раз меньше), гдеS
- это число строки иM
- количество чисел в строке.
Очень похожая проблема была опубликована в StackOverflow с несколькими предложенными решениями. Первоначальной целью было повышение скорости работы.
Код:
Два решения, предназначенные для скорости, по-видимому, имеют совершенно разные характеристики от одной установки MATLAB к другой или даже от одного запуска к другому. Кроме того, у них действительно разные стили реализации.
В темном углу вы найдете решение, которое противоречит самым святым принципам MATLAB: eval
является злым, и циклы следует избегать любой ценой. Однако код пытается оптимизировать скорость, избегая повторных распределений памяти, используя быстрые способы преобразования строк в числа и выполняя операции на месте:
%'eval_and_loops_solution.m'
function array_of_numbers = eval_and_loops_solution(list_of_words)
n_numbers = 1 + sum(list_of_words{1}=='_');
n_words = numel(list_of_words);
array_of_numbers = zeros(n_numbers, n_words);
for k = 1:n_words
temp = ['[', list_of_words{k}, ']'];
temp(temp=='_') = ';';
array_of_numbers(:,k) = eval(temp);
end;
%'this is a waste of memory, but is kind of required'
array_of_numbers = transpose(array_of_numbers / 1000);
end
Примечание: Исходное решение вернуло результат как массив M
& times; S
double
. Код был адаптирован для S
& times; M
; однако эта адаптация потребляет в два раза больше памяти. И да, я написал это решение.
В ясном углу вы найдете решение, истинное для духа MATLAB, которое позволяет избежать использования циклов и eval
, в пользу единственного sscanf
для чтения всех строк (что является умным способ избежать накладных расходов при вызове функции S
раз):
%'single_sscanf_solution.m'
function array_of_numbers = single_sscanf_solution(list_of_words)
N = 1 + sum(list_of_words{1}=='_'); %'hard-coded in the original'
lens = cellfun(@numel,list_of_words);
tlens = sum(lens);
idx(1,tlens)=0;
idx(cumsum(lens(1:end-1))+1)=1;
idx2 = (1:tlens) + cumsum(idx);
one_str(1:max(idx2)+1)='_';
one_str(idx2) = [list_of_words{:}];
delim = repmat('%d_',1,N*numel(lens));
array_of_numbers = reshape(sscanf(one_str, delim),N,[])'./1000;
end
Примечание. Это решение относится к @Divakar.
Рефери состоит из двух функций: один, который генерирует тестовые примеры, и тот, который выполняет timig.
Группы генераторов тестового случая в массиве ячеек, выделенных символом подчеркивания, из 10 случайных чисел от 1 до 9999 (для простоты); однако реализация должна только предполагать, что числа положительны или равны нулю, а число должно соответствовать значению double
:
%'referee_test_case.m'
function list_of_words = referee_test_case(n_words)
NUM_PER_WORD = 10;
NUM_MAX = 9999;
word_format = [repmat('%d_', 1, NUM_PER_WORD-1), '%d'];
list_of_words = cell(n_words,1);
fprintf('Generating %d-words test case...\n', n_words);
for k = 1:n_words
list_of_words{k} = sprintf(word_format, randi(NUM_MAX, [1, NUM_PER_WORD]));
end;
end
Функция синхронизации принимает в качестве аргументов тестовый пример и произвольное количество обрабатываемых функций; он реализуется, например, ошибки в одной функции не должны беспокоить выполнение других. Он использует timeit
, как рекомендовано @Divakar и @LuisMendo; для тех, кто не имеет этой функции в своей установке MATLAB по умолчанию, ее можно загрузить из центра MATLAB Central/File Exchange:
%'referee_timing.m'
function referee_timing(test_case, varargin)
%' Specify the test case as a cell array of strings, then the function '
%' handles that will process the test case. '
%' '
%' The function uses timeit() to evaluate the performance of different '
%' processing handles; if your MATLAB installation does not have it '
%' natively, download the available version from File Exchange: '
%' '
%' http://www.mathworks.com/matlabcentral/fileexchange/18798-timeit-benchmarking-function '
fprintf('Timing %d-words test case...\n', numel(test_case));
for k = 1:numel(varargin)
try
t = timeit(@() varargin{k}(test_case));
fprintf(' %s: %f[s]\n', func2str(varargin{k}), t);
catch ME
fprintf(' %s: Error - %s\n', func2str(varargin{k}), ME.message);
end;
end;
end
Проблема:
Как было сказано ранее, результаты, как представляется, варьируются от одной установки MATLAB к другой и даже от одного запуска к другому. Задача, которую я предлагаю, трижды:
- На вашей конкретной установке MATLAB (версия + OS + HW), насколько хорошо эти два решения выполняются по сравнению с eachother?
- Можно ли улучшить одно или другое решение значительным образом?
- Существуют ли еще более быстрые решения MATLAB (например, без MEX или специальных наборов инструментов) на основе совершенно разных идей/функций?
Для точки 1. (сравнительный анализ) создайте в своем пути MATLAB четыре функциональных файла eval_and_loops_solution.m
, single_sscanf_solution.m
, referee_test_case.m
, referee_timig.m
и другие функции, которые вы можете протестировать (например, реализации, предложенные ответами). Используйте их для нескольких слов, например. например script:
%'test.m'
clc;
feature accel on; %'tune for speed'
%'low memory stress'
referee_timing( ...
referee_test_case(10^4), ...
@eval_and_loops_solution, ...
@single_sscanf_solution ... %'add here other functions'
);
%'medium memory stress'
referee_timing( ...
referee_test_case(10^5), ...
@eval_and_loops_solution, ...
@single_sscanf_solution ... %'add here other functions'
);
%'high memory stress'
referee_timing( ...
referee_test_case(10^6), ...
@eval_and_loops_solution, ...
@single_sscanf_solution ... %'add here other functions'
);
При публикации результатов укажите свою версию MATLAB, ОС, размер ОЗУ и модель ЦП. Помните, что эти тесты могут занять некоторое время для большого количества слов! Однако запуск этого script не изменит содержание вашей текущей рабочей области.
Для пунктов 2. или 3. вы можете публиковать код, который использует те же соглашения о входе/выводе как eval_and_loops_solution.m
и single_sscanf_solution.m
, а также сравнительный анализ, который поддерживает заявку.
Баунти:
Я буду голосовать за каждый сравнительный ответ, и я призываю всех сделать это. Это наименьшее, что можно сделать для людей, которые способствуют их навыкам, времени и вычислительной мощности.
Бонус за бонус +500 к бонусу отправится автору самого быстрого кода, будь то один из двух предложенных, или третий новый, который превосходит их. Я очень надеюсь, что это будет соответствовать общему соглашению. Бонус будет предоставлен в неделю month от первоначальной даты публикации.