How can I simplify this MatLab code? - matlab

I would like to know if there is a way to get rid of the inner for loop
for i = 1:size(VALUES)
for k = 2:bins+1
if VALUES(i) < Arr(k)
answer_list(i) = find(Arr == Arr(k)) - 1;
break
end
end
end
VALUES is a file with 100 doubles from 2 to 4
Arr is an array with 4 values, starting at VALUES min a step of 1 and ends at VALUES max
bins is Arr's length - 1
and answer_list is a column of numbers VALUES long that hold the discrete value depending on the size of the bins variable.

I think this is what you look for (in comments are the references to the original lines in your code):
out = bsxfun(#lt,VALUES(:).',Arr(:)) % if VALUES(i) < Arr(k):
out2 = size(out,1)-cumsum(out,1); % find(Arr == Arr(k)) - 1;
answer_list = out2(end,any(out,1)).';
This replaces the whole code, not only the inner loop.

Related

MATLAB For Loop to count #s in one column based off of entries in another

I have 50 spreadsheets with multiple scored columns:
One column (AG) has numbers coded 1:13, the other, (SEC) has numbers coded 1:6.
Ex:
AG SEC
1 1
2 1
4 1
13 1
3 2
12 2
I want to write a for loop that counts all the 1s in .SEC that correspond to #s 1:5 in .AG. (output would be 3 - it wouldn't count the 1 corresponding to 13). I need this to happen for all #s in .SEC (1:6). The final output would have the spreadsheet name in the first column, and counts for .SEC=1,2,3,4,5,6 in each of the proceeding columns.
My current code creates a variable for total .AG counts in .SEC, but is nondiscriminatory (counts the amount of times any number is given in .AG instead of counting for specific values)
scoringfiles is a 50-item path list (when I do readtable(scoringfiles) it iterates through the list and reads through excel files. filelist is a 50-item list with just filenames.
for i=1:length(scoringfiles)
if contains(filelist(i,:),"sheet")
disp(i)
sheetnum=[sheetnum extractBetween(filelist{i},1,4)]
s1=[s1 length(find(readtable(scoringfiles(i,:)).SEC==1))]
s2=[s2 length(find(readtable(scoringfiles(i,:)).SEC==2))]
s3=[s3 length(find(readtable(scoringfiles(i,:)).SEC==3))]
s4=[s4 length(find(readtable(scoringfiles(i,:)).SEC==4))]
s5=[s5 length(find(readtable(scoringfiles(i,:)).SEC==5))]
s6=[s6 length(find(readtable(scoringfiles(i,:)).SEC==6))]
elseif contains(filelist(i,:),"graph")
disp("not sheet")
end
end
In MATLAB, i and j are the imaginary unit. To avoid redefining it, you should make a habit of using ii and jj as your loop variable instead of i and j.
Now back to the main question:
Let's assume you've read the file contents into the data variable. This is going to be a Nx2 array.
You only care about AG when it is in the range 1:5. Let's create a filter array with true where AG is in the range and false elsewhere.
filter = data(:, 1) >= 1 & data(:, 1) <= 5;
Let's first split the columns into two variables for legibility. Use the filter to select just the rows that match our criteria.
ag = data(filter, 1);
sec = data(filter, 2);
Now you want to go through each unique value in sec, and count the number of ag entries.
unique_sec = unique(sec);
counts = zeros(size(unique_sec)); % Preallocate a zeros array to save our answer in
for ii = 1:length(unique_sec)
sec_value = unique_sec(ii); % Get the value of SEC
matches = sec == sec_value; % Make a filter for rows that have this value
% matches is a logical array. true = 1, false = 0. sum gives number of trues.
counts(ii) = sum(matches);
end
Alternatively, you could perform the filter for 1 <= AG <= 5 inside the loop if you don't want to filter before:
ag = data(:, 1);
sec = data(:, 2);
unique_sec = unique(sec);
counts = zeros(size(unique_sec));
for ii = 1:length(unique_sec)
sec_value = unique_sec(ii);
matches = sec == sec_value & ag >= 1 & ag <= 5; % Add more conditions to the filter
counts(ii) = sum(matches);
end
If you want to do this for multiple files, iterate over them and read the files into the data variable.
I figured out how to apply a filter thanks to the help of Pranav. It is as simple as adding the filter to each line of the for loop as it iterates through reading my spreadsheets. See below:
THIS EXAMPLE ONLY LOOKS AT S1 and S2. Realistically, I have this for 6 different #s creating 6 tables with counts per spreadsheet.
for i=1:length(scoringfiles)
filter1 = readtable(scoringfiles(i,:)).AG >= 1;
filter2 = readtable(scoringfiles(i,:)).AG <= 5;
if contains(filelist(i,:),"sheet")
disp(i)
sheetnum=[sheetnum extractBetween(filelist{i},1,4)]
s1=[s1 length(find(readtable(scoringfiles(i,:)).SEC==1 & filter1 & filter2))]
s2=[s2 length(find(readtable(scoringfiles(i,:)).SEC==2 & filter1 & filter2))]
elseif contains(filelist(i,:),"graph")
disp("not sheet")
end
end

save matrix value which create in each loop save in common matrix ,each loop matrix has same row size but column is ow

a=18;
b=22;
for i=1:10
r1 = randi([18 22],1,1)
name= (b-a).*rand(r1,2) + a
end
now save this all name value matrix in result matrix of all genrated value
in each loop row size not fix but coiumn is 2
preallocate the maximum sized matrix and remove redundant rows at the end:
a = 18;
b = 22;
% number of iterations
n = 10;
% maximum number of rows
maxRows = b*n;
% preallocating matrix
nameMat = zeros(maxRows,2);
currentRow = 0;
for i = 1:n
r1 = randi([18 22],1,1);
name = (b-a).*rand(r1,2) + a;
nameRows = size(name,1);
nameMat((1:nameRows) + currentRow,:) = name;
currentRow = currentRow + nameRows;
end
% remove redundant rows
nameMat(currentRow+1:end,:) = [];
You can achieve it with one line code; actually in each iteration of the loop
you:
generate an integer random number r1 (e. g. 20)
it is then used to generate r1 double random number that are multiplied by (b-a)
and then added to a
these random numbers are not affected by the ones generated in a previos iteration
a and b are constant so do not change in the loop
At the end of your loop you the number of row will be the sum of the
integer random number, so you can directly generate the 10 integer random numbers in the evaluation of name and sum them to create the desired set name values:
a=18;
b=22;
n_iter=10;
name=(b-a).*rand(sum(randi([18 22],n_iter,1)),2) + a
Hope this helps,
Qapla'

Can operations on submatrices (and subvectors) be vectorized?

I'm currently working on an edge detector in octave. Coming from other programming languages like Java and Python, I'm used to iterating in for loops, rather than performing operations on entire matrices. Now in octave, this causes a serious performance hit, and I'm having a bit of difficulty figuring out how to vectorize my code. I have the following two pieces of code:
1)
function zc = ZeroCrossings(img, T=0.9257)
zc = zeros(size(img));
# Iterate over central positions of all 3x3 submatrices
for y = 2:rows(img) - 1
for x = 2:columns(img) - 1
ndiff = 0;
# Check all necessary pairs of elements of the submatrix (W/E, N/S, NW/SE, NE/SW)
for d = [1, 0; 0, 1; 1, 1; 1, -1]'
p1 = img(y-d(2), x-d(1));
p2 = img(y+d(2), x+d(1));
if sign(p1) != sign(p2) && abs(p1 - p2) >= T
ndiff++;
end
end
# If at least two pairs fit the requirements, these coordinates are a zero crossing
if ndiff >= 2
zc(y, x) = 1;
end
end
end
end
2)
function g = LinkGaps(img, k=5)
g = zeros(size(img));
for i = 1:rows(img)
g(i, :) = link(img(i, :), k);
end
end
function row = link(row, k)
# Find first 1
i = 1;
while i <= length(row) && row(i) == 0
i++;
end
# Iterate over gaps
while true
# Determine gap start
while i <= length(row) && row(i) == 1
i++;
end
start = i;
# Determine gap stop
while i <= length(row) && row(i) == 0
i++;
end
# If stop wasn't reached, exit loop
if i > length(row)
break
end
# If gap is short enough, fill it with 1s
if i - start <= k
row(start:i-1) = 1;
end
end
end
Both of these functions iterate over submatrices (or rows and subrows in the second case), and particularly the first one seems to be slowing down my program quite a bit.
This function takes a matrix of pixels (img) and returns a binary (0/1) matrix, with 1s where zero crossings (pixels whose corresponding 3x3 neighbourhoods fit certain requirements) were found.
The outer 2 for loops seem like they should be possible to vectorize somehow. I can put the body into its own function (taking as an argument the necessary submatrix) but I can't figure out how to then call this function on all submatrices, setting their corresponding (central) positions to the returned value.
Bonus points if the inner for loop can also be vectorized.
This function takes in the binary matrix from the previous one's output, and fills in gaps in its rows (i.e. sets them to 1). A gap is defined as a series of 0s of length <= k, bounded on both sides by 1s.
Now I'm sure at least the outer loop (the one in LinkGaps) is vectorizable. However, the while loop in link again operates on subvectors, rather than single elements so I'm not sure how I'd go about vectorizing it.
Not a full solution, but here is an idea how you could do the first without any loops:
% W/E
I1 = I(2:end-1,1:end-2);
I2 = I(2:end-1,3:end );
C = (I1 .* I2 < 0) .* (abs(I1 - I2)>=T);
% N/S
I1 = I(1:end-2,2:end-1);
I2 = I(3:end, 2:end-1);
C = C + (I1 .* I2 < 0) .* (abs(I1 - I2)>=T);
% proceed similarly with NW/SE and NE/SW
% ...
% zero-crossings where count is at least 2
ZC = C>=2;
Idea: form two subimages that are appropriately shifted, check for the difference in sign (product negative) and threshold the difference. Both tests return a logical (0/1) matrix, the element-wise product does the logical and, result is a 0/1 matrix with 1 where both tests have succeeded. These matrices can be added to keep track of the counts (ndiff).

Print integers in a Matrix coming in order without duplicates

I have several matrices <1x1000> containing integers such as:
matrix = [0,0,0,0,0,30,30,30,40,40,50,50,50,40,0,0,0,30,30,30]
I want to print (disp, and later plot) them like this: 30,40,50,40,30. Basically ignore the duplicates if they come after each other.
Another example:
matrix = [0,0,0,0,10,10,10,10,50,50,50,50,10,10,10,50,50] shall give: 10,50,10,50
Help is very much appreciated!
Use this:
[~,c]=find([NaN diff(matrix)]);
output=matrix(c);
output = output(output~=0)
and to plot the output, simply use: plot(output)
Result = 0;
% loop over all nonzero values in matrix
for Element = matrix
if Element == Result(end)
% skip if equal
continue
else
% add new value
Result(end+1) = Element;
end
end
% discard zero entries
Result = Result(Result ~= 0);
All solutions provided so far use either loops or the function find which are both inefficient.
Just use matrix indexation:
[matrix((matrix(1:end-1)-matrix(2:end))~=0), matrix(end)]
ans =
0 30 40 50 40 0 30
By the way in your example are you discarting the 0s even if they come in repeated sequences?
Lets call the output matrix um then
um(1) = matrix(1);
j = 1;
for i=2: length(matrix)
% Ignore repeating numbers
if (um(j) ~= matrix(i))
j = j + 1;
um(j) = matrix(i);
end
end
% Remove zeros
um = um(um~=0);

Matlab recursion. Sum off all odd numbers in a vector

I am trying to implement a recursive function to add the odd numbers in a vector v.
So far this is my attempt
function result = sumOdd(v)
%sum of odd numbers in a vector v
%sumOdd(v)
n = 1;
odds = [];
if length(v) > 0
if mod(v(n),2) == 1
odds(n) = v(n);
v(n) = [];
n = n + 1;
sumOdd(v)
elseif mod(v(n),2) == 0
v(n) = [];
n = n + 1;
sumOdd(v)
end
else
disp(sum(odds))
end
end
This does not work and returns a value of zero. I am new to programming and recursion and would like to know what I'm doing wrong.
Thank you.
There is a better way to solve this in MATLAB:
function result=summOdd(v)
odd_numbers=v(mod(v,2)); % Use logical indexing to get all odd numbers
result=sum(odd_numbers); % Summ all numbers.
end
To give a recursive solution:
When implementing a recursive function, there is a pattern you should always follow. First start with the trivial case, where the recursion stops. In this case, the sum of an empty list is 0:
function result = sumOdd(v)
%sum of odd numbers in a vector v
%sumOdd(v)
if length(v) == 0
result=0;
else
%TBD
end
end
I always start this way to avoid infinite recursions when trying my code. Where the %TBD is placed you have to put your actual recursion. In this case your idea was to process the first element and put all remaining into the recursion. First write a variable s which contains 0 if the first element is even and the first element itself when it is odd. This way you can calculate the result using result=s+sumOdd(v)
function result = sumOdd(v)
%sum of odd numbers in a vector v
%sumOdd(v)
if length(v) == 0
result=0;
else
if mod(v(1),2) == 1
s=v(1);
else
s=0;
end
v(1) = [];
result=s+sumOdd(v);
end
end
Now having your code finished, read the yellow warning the editor gives to you, it tells you to replace length(v) == 0 with isempty(v).
Instead of keeping 'n', you can always delete the first element of v. Also, you need to pass 'odds' as an argument, as you're initializing it as an empty array at each time you call the function (hence the zero output).
The following example seems to do the job:
function result = sumOdd(v,odds)
%sum of odd numbers in a vector v
%sumOdd(v)
if ~isempty(v)
if mod(v(1),2) == 1
odds = [odds;v(1)];
v(1) = [];
sumOdd(v,odds)
elseif mod(v(1),2) == 0
v(1) = [];
sumOdd(v,odds)
end
else
disp(sum(odds))
end
end