Сплит-вектор в MATLAB
Я пытаюсь элегантно разделить вектор. Например,
vec = [1 2 3 4 5 6 7 8 9 10]
В соответствии с другим вектором 0 и 1 той же длины, где 1 указывает, где вектор должен быть разделен - или, скорее, разрезан:
cut = [0 0 0 1 0 0 0 0 1 0]
Дайте нам вывод ячейки, подобный следующему:
[1 2 3] [5 6 7 8] [10]
Ответы
Ответ 1
Код решения
Вы можете использовать cumsum
и accumarray
для эффективного решения -
%// Create ID/labels for use with accumarray later on
id = cumsum(cut)+1
%// Mask to get valid values from cut and vec corresponding to ones in cut
mask = cut==0
%// Finally get the output with accumarray using masked IDs and vec values
out = accumarray(id(mask).',vec(mask).',[],@(x) {x})
Бенчмаркинг
Ниже приведены некоторые показатели производительности при использовании большого ввода для трех наиболее популярных подходов, перечисленных для решения этой проблемы -
N = 100000; %// Input Datasize
vec = randi(100,1,N); %// Random inputs
cut = randi(2,1,N)-1;
disp('-------------------- With CUMSUM + ACCUMARRAY')
tic
id = cumsum(cut)+1;
mask = cut==0;
out = accumarray(id(mask).',vec(mask).',[],@(x) {x});
toc
disp('-------------------- With FIND + ARRAYFUN')
tic
N = numel(vec);
ind = find(cut);
ind_before = [ind-1 N]; ind_before(ind_before < 1) = 1;
ind_after = [1 ind+1]; ind_after(ind_after > N) = N;
out = arrayfun(@(x,y) vec(x:y), ind_after, ind_before, 'uni', 0);
toc
disp('-------------------- With CUMSUM + ARRAYFUN')
tic
cutsum = cumsum(cut);
cutsum(cut == 1) = NaN; %Don't include the cut indices themselves
sumvals = unique(cutsum); % Find the values to use in indexing vec for the output
sumvals(isnan(sumvals)) = []; %Remove NaN values from sumvals
output = arrayfun(@(val) vec(cutsum == val), sumvals, 'UniformOutput', 0);
toc
Runtimes
-------------------- With CUMSUM + ACCUMARRAY
Elapsed time is 0.068102 seconds.
-------------------- With FIND + ARRAYFUN
Elapsed time is 0.117953 seconds.
-------------------- With CUMSUM + ARRAYFUN
Elapsed time is 12.560973 seconds.
Специальный сценарий: В случаях, когда у вас могут быть пробеги 1
, вам нужно изменить несколько вещей, как указано ниже -
%// Mask to get valid values from cut and vec corresponding to ones in cut
mask = cut==0
%// Setup IDs differently this time. The idea is to have successive IDs.
id = cumsum(cut)+1
[~,~,id] = unique(id(mask))
%// Finally get the output with accumarray using masked IDs and vec values
out = accumarray(id(:),vec(mask).',[],@(x) {x})
Пример прогона с таким случаем -
>> vec
vec =
1 2 3 4 5 6 7 8 9 10
>> cut
cut =
1 0 0 1 1 0 0 0 1 0
>> celldisp(out)
out{1} =
2
3
out{2} =
6
7
8
out{3} =
10
Ответ 2
Для этой задачи удобная функция cumsum
, которая может создать кумулятивную сумму массива разреза. Код, который создает массив выходных ячеек, выглядит следующим образом:
vec = [1 2 3 4 5 6 7 8 9 10];
cut = [0 0 0 1 0 0 0 0 1 0];
cutsum = cumsum(cut);
cutsum(cut == 1) = NaN; %Don't include the cut indices themselves
sumvals = unique(cutsum); % Find the values to use in indexing vec for the output
sumvals(isnan(sumvals)) = []; %Remove NaN values from sumvals
output = {};
for i=1:numel(sumvals)
output{i} = vec(cutsum == sumvals(i)); %#ok<SAGROW>
end
Как показывает другой ответ, вы можете использовать arrayfun
для создания массива ячеек с результатами. Чтобы применить это здесь, вы замените цикл for
(и инициализацию вывода) следующей строкой:
output = arrayfun(@(val) vec(cutsum == val), sumvals, 'UniformOutput', 0);
Это хорошо, потому что это не приводит к увеличению массива выходных ячеек.
Ключевой особенностью этой подпрограммы является переменная cutsum
, которая выглядит следующим образом:
cutsum =
0 0 0 NaN 1 1 1 1 NaN 2
Тогда нам нужно всего лишь использовать его для создания индексов, чтобы вытащить данные из исходного массива vec
. Мы переходим от нуля к максимальному и вытягиваем соответствующие значения. Обратите внимание, что эта процедура обрабатывает некоторые ситуации, которые могут возникнуть. Например, он обрабатывает 1 значение в самом начале и в самом конце массива cut
, и он изящно обрабатывает повторяющиеся в массиве cut
без создания пустых массивов на выходе. Это связано с использованием unique
для создания набора значений для поиска в cutsum
и того факта, что мы выбрасываем значения NaN в массиве sumvals
.
Вместо NaN
вы можете использовать -1
, поскольку флаг сигнала для мест вырезания не используется, но мне нравится NaN для удобочитаемости. Значение -1, вероятно, будет более эффективным, так как все, что вам нужно сделать, - обрезать первый элемент из массива sumvals. Я просто предпочитаю использовать NaN в качестве сигнального флага.
Результатом этого является массив ячеек с результатами:
output{1} =
1 2 3
output{2} =
5 6 7 8
output{3} =
10
Есть некоторые нечетные условия, которые нам нужно обрабатывать. Рассмотрим ситуацию:
vec = [1 2 3 4 5 6 7 8 9 10 11 12 13 14];
cut = [1 0 0 1 1 0 0 0 0 1 0 0 0 1];
Там повторяется 1, а также 1 в начале и конце. Эта процедура должным образом обрабатывает все это без каких-либо пустых наборов:
output{1} =
2 3
output{2} =
6 7 8 9
output{3} =
11 12 13
Ответ 3
Вы можете сделать это с помощью комбинации find
и arrayfun
:
vec = [1 2 3 4 5 6 7 8 9 10];
N = numel(vec);
cut = [0 0 0 1 0 0 0 0 1 0];
ind = find(cut);
ind_before = [ind-1 N]; ind_before(ind_before < 1) = 1;
ind_after = [1 ind+1]; ind_after(ind_after > N) = N;
out = arrayfun(@(x,y) vec(x:y), ind_after, ind_before, 'uni', 0);
Таким образом, получаем:
>> celldisp(out)
out{1} =
1 2 3
out{2} =
5 6 7 8
out{3} =
10
Итак, как это работает? Ну, первая строка определяет ваш входной вектор, вторая строка определяет, сколько элементов находится в этом векторе, а третья строка обозначает ваш вектор cut
, который определяет, где нам нужно вырезать наш вектор. Затем мы используем find
для определения местоположений, отличных от нуля в cut
, которые соответствуют точкам разделения в векторе. Если вы заметили, точки разделения определяют, где нам нужно прекратить сбор элементов и начать собирать элементы.
Однако нам нужно учитывать начало вектора, а также конец. ind_after
указывает нам места, где нам нужно начинать собирать значения, а ind_before
указывает места, где нам нужно прекратить сбор значений. Чтобы вычислить эти начальные и конечные позиции, вы просто берете результат find
и добавляете и вычитаете 1 соответственно.
Каждая соответствующая позиция в ind_after
и ind_before
сообщает нам, где нам нужно начинать и останавливать сбор значений вместе. Чтобы разместить начало вектора, ind_after
должен иметь индекс 1, вставленный в начале, потому что индекс 1 - это то, где мы должны начать собирать значения в начале. Аналогично, N
необходимо вставить в конце ind_before
, потому что здесь нам нужно прекратить собирать значения в конце массива.
Теперь для ind_after
и ind_before
существует вырожденный случай, когда точка разреза может быть в конце или в начале вектора. Если это так, то вычитание или добавление по 1 будет генерировать начальную и конечную позицию, выходящие за пределы. Мы проверяем это на 4-й и 5-й строках кода и просто устанавливаем их в 1 или N
в зависимости от того, находимся ли мы в начале или конце массива.
Последняя строка кода использует arrayfun
и выполняет итерацию через каждую пару ind_after
и ind_before
, чтобы нарезать наш вектор. Каждый результат помещается в массив ячеек, и наш результат следует.
Мы можем проверить вырожденный случай, поставив 1 в начале и конце cut
и некоторые значения между ними:
vec = [1 2 3 4 5 6 7 8 9 10];
cut = [1 0 0 1 0 0 0 1 0 1];
Используя этот пример и приведенный выше код, получаем:
>> celldisp(out)
out{1} =
1
out{2} =
2 3
out{3} =
5 6 7
out{4} =
9
out{5} =
10
Ответ 4
Еще один способ, но на этот раз без каких-либо циклов или накопления вообще...
lengths = diff(find([1 cut 1])) - 1; % assuming a row vector
lengths = lengths(lengths > 0);
data = vec(~cut);
result = mat2cell(data, 1, lengths); % also assuming a row vector
Конструкция diff(find(...))
дает нам расстояние от каждого маркера до следующего - мы добавляем граничные маркеры с [1 cut 1]
, чтобы поймать любые прогоны нулей, которые касаются концов. Каждая длина включает в себя его маркер, однако, поэтому мы вычитаем 1 для учета этого и удалим все, которые просто покрывают последовательные маркеры, так что мы не получим никаких нежелательных пустых ячеек в выходе.
Для данных мы маскируем любые элементы, соответствующие маркерам, поэтому у нас есть только те части, которые мы хотим разбить. Наконец, с данными, готовыми к разбиению и длинами, в которые нужно разбить его, это то, для чего mat2cell
.
Кроме того, используя @Divakar benchmark code;
-------------------- With CUMSUM + ACCUMARRAY
Elapsed time is 0.272810 seconds.
-------------------- With FIND + ARRAYFUN
Elapsed time is 0.436276 seconds.
-------------------- With CUMSUM + ARRAYFUN
Elapsed time is 17.112259 seconds.
-------------------- With mat2cell
Elapsed time is 0.084207 seconds.
... просто sayin ';)
Ответ 5
Вот что вам нужно:
function spl = Splitting(vec,cut)
n=1;
j=1;
for i=1:1:length(b)
if cut(i)==0
spl{n}(j)=vec(i);
j=j+1;
else
n=n+1;
j=1;
end
end
end
Несмотря на то, насколько прост мой метод, он на 2-м месте для производительности:
-------------------- With CUMSUM + ACCUMARRAY
Elapsed time is 0.264428 seconds.
-------------------- With FIND + ARRAYFUN
Elapsed time is 0.407963 seconds.
-------------------- With CUMSUM + ARRAYFUN
Elapsed time is 18.337940 seconds.
-------------------- SIMPLE
Elapsed time is 0.271942 seconds.
Ответ 6
К сожалению, в MATLAB нет "обратной конкатенации". Если вы хотите решить такой вопрос, вы можете попробовать приведенный ниже код. Он даст вам то, что вы ищете в случае, когда у вас есть две точки разделения, чтобы создать три вектора в конце. Если вам нужно больше расщеплений, вам нужно будет изменить код после цикла.
Результаты представлены в n векторной форме. Чтобы сделать их в ячейки, используйте num2cell для результатов.
pos_of_one = 0;
% The loop finds the split points and puts their positions into a vector.
for kk = 1 : length(cut)
if cut(1,kk) == 1
pos_of_one = pos_of_one + 1;
A(1,one_pos) = kk;
end
end
F = vec(1 : A(1,1) - 1);
G = vec(A(1,1) + 1 : A(1,2) - 1);
H = vec(A(1,2) + 1 : end);