Increment values in an array with constraints using Matlab? - arrays

Scenario :
If I have an array with 4 loads (a1 a2 a3 a4)
a=[a1 a2 a3 a4] (locations of these loads must be fixed)
a=[1 2 3 3]
I would like to try and increase all values in the array to 3.
Note : the array a is not fixed, and can have any value from 0:3
Constraints :
There is a priority array that cannot be violated
Number of total increments is limited to 3
Given :
Priority array v=[1 3 2 1] -- (1 is highest priority, and 3 is lowest priority).
Note : the array v is not fixed, and can have any value from 0:3
Using this priority array :
a(1,1)=highest priority
a(1,4)=2nd highest priority
a(1,3)=3rd priority
a(1,2)=lowest priority
Implementation, my trial in pseudo code :
a=[1 2 3 3]
v=[1 3 2 1]
count=3
Check highest priority : a(1,1)
increment by 1
decrement count by 1
count = 2
still less than 3 ? if yes, then increment again until a(1,1)<= 3 AND count >=0
Change highest priority to 5 (so that min(v) will not pick it up)
ans : a=[3 2 3 3] ; v=[5 2 3 3] ; count = 1
Check highest priority : a(1,3)
value >= 3
Change highest priority to 5 (so that min(v) will not pick it up)
skip
ans : a=[3 2 3 3] ; v=[5 2 5 3] ; count = 1
Check highest priority : a(1,4)
value >=3
Change highest priority to 5 (so that min(v) will not pick it up)
skip
ans : a=[3 2 3 3] ; v=[5 2 5 5] ; count = 1
Check highest priority : a(1,2)
increment by 1
decrement count by 1
count = 0
still less than 3 ? if yes, then increment again until a(1,1)<= 3 AND count >=0
Change highest priority to 5 (so that min(v) will not pick it up)
ans = [a1 a2 a3 a4] = [3 3 3 3]
Note : if a priority value = [1 1 1 1] is reached, then a is prioritised from left to right (I haven't found a better way to do this)
I hope this makes sense, and that my pseudo code shows what I'm trying to implement. Ask me if something is not clear.

You could do something like this
a = [1 2 3 3];
v = [1 3 2 1];
% Sort a in the order of priority v
[vSrt, indSrt] = sort(v);
a = a(indSrt);
nIncsRemaining = 3; % Total no. of increments allowed
target = 3; % Target value for each value in a
for curInd = 1:length(a)
% Difference from target
aDiff = target - a(curInd);
% Do we need to increment this value of a?
if aDiff > 0
% Increment by a maximum of nIncsRemaining
aDelta = min(aDiff, nIncsRemaining);
% Increment a and decrement no. of increments remaining by the
% same amount
a(curInd) = a(curInd) + aDelta;
nIncsRemaining = nIncsRemaining - aDelta;
end
% Have we done as much as we're allowed?
if nIncsRemaining == 0, break; end
end
The key step is the sorting of the priority array, and the sorting of a by the same indices. Then you can just loop through a, being confident that you're beginning with the highest priority.
If you require the same order as the input at output, then you can invert the sorting operation by doing
[~, indReSrt] = sort(indSrt);
a = a(indReSrt);
The array v was not modified in the first place, so you don't need to invert the sort on that array.

Here's what I came up with:
a = [1 2 3 3];
v = [1 3 2 1];
% Get priority vector - this converts v into the indices of a that are most important in descending order. This can also be preallocated for speed or stored in place if v is not important;
priority_vec = [];
for i = 0:3
% Get indices
priority_vec = horzcat(priority_vec,find(v == i));
end
% Loop over priority_vec
count = 3; % count is the number of additions you can make
for i = 1:4 % Loop over the indices of priority vec
priority_ind = priority_vec(i); % This is the current index of most importance
while(a(priority_ind) < 3 && count ~= 0) % Continue to add one while count is greater than 0 and the value at the priority index is less than three
a(priority_ind) = a(priority_ind) + 1;
count = count - 1;
end
end

Another version:
a = [1 2 3 3];
v = [1 3 2 1];
count = 3;
target = 3;
Sort a and v in priority order
[vSorted, order] = sort(v);
aSorted = a(order);
Find position that will cause count to equal 0
pos = find(cumsum(target - aSorted) >= count);
Update all values up until but not including pos, decrement count accordingly
count = count - sum(3 - aSorted(1:pos - 1));
vSorted(1:pos - 1) = 5;
aSorted(1:pos - 1) = target;
Update the value s at pos
aSorted(pos) = aSorted(pos) + count;
count = 0;
if aSorted(pos) == target
vSorted(pos) = 5;
end
Restore sort order
[~, invOrder] = sort(order);
a = aSorted(invOrder);
v = vSorted(invOrder);
If v is only used to determine the priority there is no need to update it.
If count may still be non-zero after all values of a have reached target some extra handling of that case is required as that will cause pos = find(...); to return an empty array.

Related

Get the first 2 non-zero elements from every row of matrix

I have a matrix A like this:
A = [ 1 0 2 4; 2 3 1 0; 0 0 3 4 ]
A has only unique row elements except zero, and each row has at least 2 non-zero elements.
I want to create a new matrix B from A,where each row in B contains the first two non-zero elements of the corresponding row in A.
B = [ 1 2 ; 2 3 ; 3 4 ]
It is easy with loops but I need vectorized solution.
Here's a vectorized approach:
A = [1 0 2 4; 2 3 1 0; 0 0 3 4]; % example input
N = 2; % number of wanted nonzeros per row
[~, ind] = sort(~A, 2); % sort each row of A by the logical negation of its values.
% Get the indices of the sorting
ind = ind(:, 1:N); % keep first N columns
B = A((1:size(A,1)).' + (ind-1)*size(A,1)); % generate linear index and use into A
Here is another vectorised approach.
A_bool = A > 0; A_size = size(A); A_rows = A_size(1);
A_boolsum = cumsum( A_bool, 2 ) .* A_bool; % for each row, and at each column,
% count how many nonzero instances
% have occurred up to that column
% (inclusive), and then 'zero' back
% all original zero locations.
[~, ColumnsOfFirsts ] = max( A_boolsum == 1, [], 2 );
[~, ColumnsOfSeconds ] = max( A_boolsum == 2, [], 2 );
LinearIndicesOfFirsts = sub2ind( A_size, [1 : A_rows].', ColumnsOfFirsts );
LinearIndicesOfSeconds = sub2ind( A_size, [1 : A_rows].', ColumnsOfSeconds );
Firsts = A(LinearIndicesOfFirsts );
Seconds = A(LinearIndicesOfSeconds);
Result = horzcat( Firsts, Seconds )
% Result =
% 1 2
% 2 3
% 3 4
PS. Matlab / Octave common subset compatible code.

Finding number(s) that is(are) repeated consecutively most often

Given this array for example:
a = [1 2 2 2 1 3 2 1 4 4 4 5 1]
I want to find a way to check which numbers are repeated consecutively most often. In this example, the output should be [2 4] since both 2 and 4 are repeated three times consecutively.
Another example:
a = [1 1 2 3 1 1 5]
This should return [1 1] because there are separate instances of 1 being repeated twice.
This is my simple code. I know there is a better way to do this:
function val=longrun(a)
b = a(:)';
b = [b, max(b)+1];
val = [];
sum = 1;
max_occ = 0;
for i = 1:max(size(b))
q = b(i);
for j = i:size(b,2)
if (q == b(j))
sum = sum + 1;
else
if (sum > max_occ)
max_occ = sum;
val = [];
val = [val, q];
elseif (max_occ == sum)
val = [val, q];
end
sum = 1;
break;
end
end
end
if (size(a,2) == 1)
val = val'
end
end
Here's a vectorized way:
a = [1 2 2 2 1 3 2 1 4 4 4 5 1]; % input data
t = cumsum([true logical(diff(a))]); % assign a label to each run of equal values
[~, n, z] = mode(t); % maximum run length and corresponding labels
result = a(ismember(t,z{1})); % build result with repeated values
result = result(1:n:end); % remove repetitions
One solution could be:
%Dummy data
a = [1 2 2 2 1 3 2 1 4 4 4 5 5]
%Preallocation
x = ones(1,numel(a));
%Loop
for ii = 2:numel(a)
if a(ii-1) == a(ii)
x(ii) = x(ii-1)+1;
end
end
%Get the result
a(find(x==max(x)))
With a simple for loop.
The goal here is to increase the value of x if the previous value in the vector a is identical.
Or you could also vectorized the process:
x = a(find(a-circshift(a,1,2)==0)); %compare a with a + a shift of 1 and get only the repeated element.
u = unique(x); %get the unique value of x
h = histc(x,u);
res = u(h==max(h)) %get the result

Sum up vector values till threshold, then start again

I have a vector a = [1 3 4 2 1 5 6 3 2]. Now I want to create a new vector 'b' with the cumsum of a, but after reaching a threshold, let's say 5, cumsum should reset and start again till it reaches the threshold again, so the new vector should look like this:
b = [1 4 4 2 3 5 6 3 5]
Any ideas?
You could build a sparse matrix that, when multiplied by the original vector, returns the cumulative sums. I haven't timed this solution versus others, but I strongly suspect this will be the fastest for large arrays of a.
% Original data
a = [1 3 4 2 1 5 6 3 2];
% Threshold
th = 5;
% Cumulative sum corrected by threshold
b = cumsum(a)/th;
% Group indices to be summed by checking for equality,
% rounded down, between each cumsum value and its next value. We add one to
% prevent NaNs from occuring in the next step.
c = cumsum(floor(b) ~= floor([0,b(1:end-1)]))+1;
% Build the sparse matrix, remove all values that are in the upper
% triangle.
S = tril(sparse(c.'./c == 1));
% In case you use matlab 2016a or older:
% S = tril(sparse(bsxfun(#rdivide,c.',c) == 1));
% Matrix multiplication to create o.
o = S*a.';
By normalizing the arguments of cumsum with the threshold and flooring you can get grouping indizes for accumarray, which then can do the cumsumming groupwise:
t = 5;
a = [1 3 4 2 1 5 6 3 2];
%// cumulative sum of normalized vector a
n = cumsum(a/t);
%// subs for accumarray
subs = floor( n ) + 1;
%// cumsum of every group
aout = accumarray( subs(:), (1:numel(subs)).', [], #(x) {cumsum(a(x))});
%// gather results;
b = [aout{:}]
One way is to use a loop. You create the first cumulative sum cs, and then as long as elements in cs are larger than your threshold th, you replace them with elements from the cumulative sum on the rest of the elements in a.
Because some elements in a might be larger than th, this loop will be infinite unless we also eliminate these elements too.
Here is a simple solution with a while loop:
a = [1 3 4 2 1 5 6 3 2];
th = 5;
cs = cumsum(a);
while any(cs>th & cs~=a) % if 'cs' has values larger that 'th',
% and there are any values smaller than th left in 'a'
% sum all the values in 'a' that are after 'cs' reached 'th',
% excluding values that are larger then 'th'
cs(cs>th & cs~=a) = cumsum(a(cs>th & cs~=a));
end
Calculate the cumulative sum and replace the indices value obeying your condition.
a = [1 3 4 2 1 5 6 3 2] ;
b = [1 4 4 2 3 5 6 3 5] ;
iwant = a ;
a_sum = cumsum(a) ;
iwant(a_sum<5) = a_sum(a_sum<5) ;

Add values in array and compare with threshold within loop in Matlab

I am stuck trying to figure this out. I have an array:
a = [ 1 1 1 2 1 1 1 3 2 1 1 2 1 1 1]
I want to add the values in the array so that it equal to 10. Once the added value reaches 10, I want the array to start adding the value again until it reaches 10. There is two problem that I face here,
1) How can I add the array so that the sum = 10 everytime. Notice that in the array, there is 3. If I add all the value before 3, I get 8 and I only need 2 from 3. I need to make sure that the remainder, which is 1 is added to the next array to get the sum 10.
2) How do I break the loop once it reaches 10 and ask it continue the summation to next value to get another 10?
I created a loop but it only works for the first part of the array. I have no idea how to make it continue. The code is as follow:
a = [ 1 1 1 2 1 1 1 3 2 1 1 2 1 1 1];
c = 0;
for i = 1:length(a)
while c < 10
c = c + a(i);
break
end
end
Please help. Thank you
This can be done using cumsum, mod, diff and find as follows:
temp = cumsum(a);
required = find([0 diff(mod(temp,10))] <0)
cumsum returns the cumulative sum which then is rescaled using mod. diff determines where the sum gets greater than or equal to 10 and finally find determines those indexes.
Edit: Above solution works if a doesn't have negative elements. If a can have negative elements then:
temp1=cumsum(a); %Commulative Sum
temp2=[0 diff(mod(temp1,10))];%Indexes where sum >=10 (indicated by negative values)
temp2(temp1<0)=0; %Removing false indexes which may come if `a` has -ve values
required = find(temp2 <0) %Required indexes
This should do what you are trying. It displays the index at which each time the sum equals 10. Check this with your testcases. rem stores the residual sum in each iteration which is carried forward in the next iteration. The rest of the code is similar to what you were doing.
a = [ 1 1 1 2 1 1 1 3 2 1 1 2 1 1 1];
c = 0;
rem = 0;
i = 1;
length(a);
while(i <= length(a))
c = rem;
while (c < 10 && i <= length(a))
c = c + a(i);
i = i + 1;
if(c >= 10)
rem = c - 10;
break
end
end
if(c >= 10)
disp(i-1)
end
use cumsum instead of your while loop:
a = [ 1 1 1 2 1 1 1 3 2 1 1 2 1 1 1];
a_ = a;
endidxlist = false(size(a));
startidxlist = false(size(a));
startidxlist(1) = true;
while any(a_) && (sum(a_) >= 10)
b = cumsum(a_);
idx = find(b >= 10,1);
endidxlist(idx) = true;
% move residual to the next sequence
a_(idx) = b(idx) - 10;
if a_(idx) > 0
startidxlist(idx) = idx;
elseif (idx+1) <= numel(a)
startidxlist(idx+1) = true;
end
a_(1:idx-1) = 0;
end
if (idx+1) <= numel(a)
startidxlist(idx+1) = false;
end
endidxlist gives you the end-indexes of each sequence and startidxlist the start-indexes

Get elements of a matrix that are greater than sum of their two indices in row major order

I'm Writing a function called large_elements that takes input an array named X that is a matrix or a vector. The function identifies those elements of X that are greater than the sum of their two indexes.
For example, if the element X(2,3) is 6, then that element would be identified because 6 > (2 + 3). The output of the function gives the indexes(row and column sub) of such elements found in row-major order. It is a matrix with exactly two columns. The first column contains the row indexes, while the second column contains the corresponding column indexes.
Here is an example, the statement
indexes = large_elements([1 4; 5 2; 6 0])
should give the output like this:
[1 2; 2 1; 3 1]
If no such element exists,
the function returns an
empty array.
I have came up with the following code
function indexes = large_elements(A)
[r c] = size(A);
ind = 1;
for ii = 1:r
for jj = 1:c
if A(ii,jj) > ii + jj
indexes(ind,:) = [ii jj];
ind = ind + 1;
else
indexes = [];
end
end
end
end
But the results are not as expected. Any help would be appreciated.
One vectorised approch using bsxfun, find and ind2sub
A = randi(8,5); %// Your matrix
%// finding sum of the indexes for all elements
indSum = bsxfun(#plus, (1:size(A,1)).', 1:size(A,2));
%// generating a mask of which elements satisfies the given condition (i.e A > indSum)
%// Transposing the mask and finding corresponding indexes
[c,r] = find(bsxfun(#gt, A, indSum).') ;
%// getting the matrix by appending row subs and col subs
out = [r,c]
Results:
Input A:
>> A
A =
4 4 7 2 2
1 3 4 8 3
8 8 2 8 7
8 3 4 5 1
4 1 1 1 1
Output in row-major order:
out =
1 1
1 2
1 3
2 4
3 1
3 2
3 4
4 1
Note: Getting subs in row-major order is tricky here
Also here is your correct loopy approach
[r, c] = size(A);
ind = 0;
indexes = [];
for ii = 1:r
for jj = 1:c
if A(ii,jj) > ii + jj
ind = ind + 1;
indexes(ind,:) = [ii jj];
end
end
end
That is because whenever you encounter an element which is smaller than the sum of its indices you are reinitializing the array to null. So the output is coming out to be null. You should not initialize it to null on the else condition.

Resources