Сплит-вектор в 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);