A question about matrix manipulation - arrays

Given a 1*N matrix or an array, how do I find the first 4 elements which have the same value and then store the index for those elements?
PS:
I'm just curious. What if we want to find the first 4 elements whose value differences are within a certain range, say below 2? For example, M=[10,15,14.5,9,15.1,8.5,15.5,9.5], the elements I'm looking for will be 15,14.5,15.1,15.5 and the indices will be 2,3,5,7.

If you want the first value present 4 times in the array 'tab' in Matlab, you can use
num_min = 4
val=NaN;
for i = tab
if sum(tab==i) >= num_min
val = i;
break
end
end
ind = find(tab==val, num_min);
By instance with
tab = [2 4 4 5 4 6 4 5 5 4 6 9 5 5]
you get
val =
4
ind =
2 3 5 7

Here is my MATLAB solution:
array = randi(5, [1 10]); %# random array of integers
n = unique(array)'; %'# unique elements
[r,~] = find(cumsum(bsxfun(#eq,array,n),2) == 4, 1, 'first');
if isempty(r)
val = []; ind = []; %# no answer
else
val = n(r); %# the value found
ind = find(array == val, 4); %# indices of elements corresponding to val
end
Example:
array =
1 5 3 3 1 5 4 2 3 3
val =
3
ind =
3 4 9 10
Explanation:
First of all, we extract the list of unique elements. In the example used above, we have:
n =
1
2
3
4
5
Then using the BSXFUN function, we compare each unique value against the entire vector array we have. This is equivalent to the following:
result = zeros(length(n),length(array));
for i=1:length(n)
result(i,:) = (array == n(i)); %# row-by-row
end
Continuing with the same example we get:
result =
1 0 0 0 1 0 0 0 0 0
0 0 0 0 0 0 0 1 0 0
0 0 1 1 0 0 0 0 1 1
0 0 0 0 0 0 1 0 0 0
0 1 0 0 0 1 0 0 0 0
Next we call CUMSUM on the result matrix to compute the cumulative sum along the rows. Each row will give us how many times the element in question appeared so far:
>> cumsum(result,2)
ans =
1 1 1 1 2 2 2 2 2 2
0 0 0 0 0 0 0 1 1 1
0 0 1 2 2 2 2 2 3 4
0 0 0 0 0 0 1 1 1 1
0 1 1 1 1 2 2 2 2 2
Then we compare that against four cumsum(result,2)==4 (since we want the location where an element appeared for the forth time):
>> cumsum(result,2)==4
ans =
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
Finally we call FIND to look for the first appearing 1 according to a column-wise order: if we traverse the matrix from the previous step column-by-column, then the row of the first appearing 1 indicates the index of the element we are looking for. In this case, it was the third row (r=3), thus the third element in the unique vector is the answer val = n(r). Note that if we had multiple elements repeated 4 times or more in the original array, then the one first appearing for the forth time will show up first as a 1 going column-by-column in the above expression.
Finding the indices of the corresponding answer value is a simple call to FIND...

Here is C++ code
std::map<int,std::vector<int> > dict;
std::vector<int> ans(4);//here we will store indexes
bool noanswer=true;
//my_vector is a vector, which we must analize
for(int i=0;i<my_vector.size();++i)
{
std::vector<int> &temp = dict[my_vector[i]];
temp.push_back(i);
if(temp.size()==4)//we find ans
{
std::copy(temp.begin(),temp.end(),ans.begin() );
noanswer = false;
break;
}
}
if(noanswer)
std::cout<<"No Answer!"<<std::endl;

Ignore this and use Amro's mighty solution . . .
Here is how I'd do it in Matlab. The matrix can be any size and contain any range of values and this should work. This solution will automatically find a value and then the indicies of the first 4 elements without being fed the search value a priori.
tab = [2 5 4 5 4 6 4 5 5 4 6 9 5 5]
%this is a loop to find the indicies of groups of 4 identical elements
tot = zeros(size(tab));
for nn = 1:numel(tab)
idxs=find(tab == tab(nn), 4, 'first');
if numel(idxs)<4
tot(nn) = Inf;
else
tot(nn) = sum(idxs);
end
end
%find the first 4 identical
bestTot = find(tot == min(tot), 1, 'first' );
%store the indicies you are interested in.
indiciesOfInterst = find(tab == tab(bestTot), 4, 'first')

Since I couldn't easily understand some of the solutions, I made that one:
l = 10; m = 5; array = randi(m, [1 l])
A = zeros(l,m); % m is the maximum value (may) in array
A(sub2ind([l,m],1:l,array)) = 1;
s = sum(A,1);
b = find(s(array) == 4,1);
% now in b is the index of the first element
if (~isempty(b))
find(array == array(b))
else
disp('nothing found');
end
I find this easier to visualize. It fills '1' in all places of a square matrix, where values in array exist - according to their position (row) and value (column). This is than summed up easily and mapped to the original array. Drawback: if array contains very large values, A may get relative large too.

You're PS question is more complicated. I didn't have time to check each case but the idea is here :
M=[10,15,14.5,9,15.1,8.5,15.5,9.5]
val = NaN;
num_min = 4;
delta = 2;
[Ms, iMs] = sort(M);
dMs = diff(Ms);
ind_min=Inf;
n = 0;
for i = 1:length(dMs)
if dMs(i) <= delta
n=n+1;
else
n=0;
end
if n == (num_min-1)
if (iMs(i) < ind_min)
ind_min = iMs(i);
end
end
end
ind = sort(iMs(ind_min + (0:num_min-1)))
val = M(ind)

Related

Matlab array that decreases from the center

I've been trying to make a 2-dimensional array that has the largest number in the center, and numbers around it decrement by one like this:
[0 0 0 0 0 0 0;
0 1 1 1 1 1 0;
0 1 2 2 2 1 0;
0 1 2 3 2 1 0;
0 1 2 2 2 1 0;
0 1 1 1 1 1 0;
0 0 0 0 0 0 0]
Any help?
This is easy using implicit expansion:
M = 7; % desired size. Assumed to be odd
t = [0:(M-1)/2 (M-3)/2:-1:0].';
result = min(t, t.');
Alternatively, you can use the gallery function with the 'minij' option to produce one quadrant of the result, and then extend symmetrically:
M = 7; % desired size. Assumed to be odd
result = gallery('minij',(M+1)/2)-1;
result = [result result(:,end-1:-1:1)];
result = [result; result(end-1:-1:1,:)];
Another approach, using padarray from the Image Processing toolbox:
result = 0;
for k = 1:(M-1)/2;
result = padarray(result+1, [1 1]);
end

Finding array elements close to another array in space?

I basically want to use the function ismember, but for a range. For example, I want to know what data points in array1 are within n distance to array2, for each element in array2.
I have the following:
array1 = [1,2,3,4,5]
array2 = [2,2,3,10,20,40,50]
I want to know what values in array2 are <= 2 away from array1:
indices(1,:) (where array1(1) = 1) = [1 1 1 0 0 0 0]
indices(2,:) (where array1(2) = 2) = [1 1 1 0 0 0 0]
indices(3,:) (where array1(3) = 3) = [1 1 1 0 0 0 0]
indices(4,:) (where array1(4) = 4) = [1 1 1 0 0 0 0]
indices(5,:) (where array1(5) = 5) = [0 0 1 0 0 0 0]
Drawbacks:
My array1 is 496736 elements, my array2 is 9268 elements, so aI would rather not use a loop.
Looping is a valid option here. Intialise an array output to the size of array1 X array2, then loop over all elements in array1 an subtract array2 from that, then check whether the absolute value is less than or equal to 2:
array1 = [1,2,3,4,5];
array2 = [2,2,3,10,20,40,50];
output = zeros(numel(array1), numel(array2),'logical');
for ii = 1:numel(array1)
output(ii,:) = abs(array1(ii)-array2)<=2;
end
output =
1 1 1 0 0 0 0
1 1 1 0 0 0 0
1 1 1 0 0 0 0
1 1 1 0 0 0 0
0 0 1 0 0 0 0
i.e. loops are not the problem.
Thanks to Rahnema1's suggestion, you can initialise output directly as a logical matrix:
output = zeros(numel(array1),numel(array2),'logical');
whose size is just 4.3GB.
On timings: Hans' code runs in a matter of seconds for array1 = 5*rand(496736,1); array2 = 25*rand(9286,1);, the looped solution takes about 15 times longer. Both solutions are equal to one another. obcahrdon's ismembertol solution is somewhere in between on my machine.
On RAM usage:
Both implicit expansion, as per Hans' answer, as well as the loop suggested in mine work with just 4.3GB RAM on your expanded problem size (496736*9286)
pdist2 as per Luis' answer and bsxfun as per Hans' on the other hand try to create an intermediate double matrix of 34GB (which doesn't even fit in my RAM, so I cannot compare timings).
obchardon's ismembertol solution outputs a different form of the solution, and takes ~5.04GB (highly dependent on the amount of matches found, the more, the larger this number will be).
In general this leads me to the conclusion that implicit expansion should be your option of choice, but if you have R2016a or earlier, ismembertol or a loop is the way to go.
Using implicit expansion, introduced in MATLAB R2016b, you can simply write:
abs(array1.' - array2) <= 2
ans =
1 1 1 0 0 0 0
1 1 1 0 0 0 0
1 1 1 0 0 0 0
1 1 1 0 0 0 0
0 0 1 0 0 0 0
For earlier MATLAB versions, you can get this using the bsxfun function:
abs(bsxfun(#minus, array1.', array2)) <= 2
ans =
1 1 1 0 0 0 0
1 1 1 0 0 0 0
1 1 1 0 0 0 0
1 1 1 0 0 0 0
0 0 1 0 0 0 0
Hope that helps!
P.S. On the "MATLAB is slow for loops" myth, please have a look at that blog post for example.
EDIT: Please read Adriaan's answer on the RAM consumption using this and/or his approach!
If you have the Statistics Toolbox, you can use pdist2 to compute the distance matrix:
result = pdist2(array1(:), array2(:)) <= 2;
As noted by #Adriaan, this is not efficient for large input sizes. In that case a better approach is a loop with preallocation of the logical matrix output, as in his answer.
You can also use ismembertol with some specific option:
A = [1,2,3,4,5];
B = [2,2,3,10,20,40,5000];
tol = 2;
[~,ind] = ismembertol([A-tol;A+tol].',[B-tol;B+tol].',tol, 'ByRows', true, ...
'OutputAllIndices', true, 'DataScale', [1,Inf])
It will create a 5x1 cell array containing the corresponding linear indice
ind{1} = [1,2,3]
ind{2} = [1,2,3]
...
ind{5} = [3]
In this case using linear indices instead of logical indices will greatly reduce the memory consumption.

How to change elements of a matrix with reference to a vector of column indices without using for-loop?

I have a matrix
a =
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
and b vector
b =
1 2 3 4 5 5
I want to replace value of each row in a matrix with reference value of b matrix value and finally generate a matrix as follows without using for loop.
a_new =
1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 1 0
0 0 0 0 1
0 0 0 0 1
if first element of b, b(1) = 1 so change take first row of a vector and make first element as 1 because b(1) = 1.
How can I implement this without using for loop?
Sure. You only need to build a linear index from b and use it to fill the values in a:
a = zeros(6,5); % original matrix
b = [1 2 3 4 5 5]; % row or column vector with column indices into a
ind = (1:size(a,1)) + (b(:).'-1)*size(a,1); % build linear index
a(ind) = 1; % fill value at those positions
Same as Luis Mendo's answer, but using the dedicated function sub2ind:
a( sub2ind(size(a),(1:numel(b)).',b(:)) ) = 1
Also via the subscript to indices conversion way,
a = zeros(6,5);
b = [1 2 3 4 5 5];
idx = sub2ind(size(a), [1:6], b); % 1:6 just to create the row index per b entry
a(idx) = 1
any of these methods works in Octave:
bsxfun(#eq, [1:5 5]',(1:5))
[1:5 5].' == (1:5)

MATLAB find first elements in columns of array

Within the context of writing a certain function, I have the following example matrix:
temp =
1 2 0 0 1 0
1 0 0 0 0 0
0 1 0 0 0 1
I want to obtain an array whose each element indicates the number of the element out of all non-zero elements which starts that column. If a column is empty, the element should correspond to the next non-empty column. For the matrix temp, the result would be:
result = [1 3 5 5 5 6]
Because the first non-zero element starts the first column, the third starts the second column, the fifth starts the fifth column and the sixth starts the sixth column.
How can I do this operation for any general matrix (one which may or may not contain empty columns) in a vectorized way?
Code:
temp = [1 2 0 0 1 0; 1 0 0 0 0 0; 0 1 0 0 0 1]
t10 = temp~=0
l2 = cumsum(t10(end:-1:1))
temp2 = reshape(l2(end)-l2(end:-1:1)+1, size(temp))
result = temp2(1,:)
Output:
temp =
1 2 0 0 1 0
1 0 0 0 0 0
0 1 0 0 0 1
t10 =
1 1 0 0 1 0
1 0 0 0 0 0
0 1 0 0 0 1
l2 =
1 1 1 1 1 2 2 2 2 2 2 2 3 3 4 4 5 6
temp2 =
1 3 5 5 5 6
2 4 5 5 6 6
3 4 5 5 6 6
result =
1 3 5 5 5 6
Printing values of each step may be clearer than my explanation. Basically we use cumsum to get the IDs of the non-zero elements. As you need to know the ID before reaching the element, a reversed cumsum will do. Then the only thing left is to reverse the ID numbers back.
Here's another way:
temp = [1 2 0 0 1 0; 1 0 0 0 0 0; 0 1 0 0 0 1]; % data
[~, c] = find(temp); % col indices of nonzero elements
result = accumarray(c, 1:numel(c), [], #min, NaN).'; % index, among all nonzero
% values, of the first nonzero value of each col; or NaN if none exists
result = cummin(result, 'reverse'); % fill NaN's using backwards cumulative maximum

Select n elements in matrix left-wise based on certain value

I have a logical matrix A, and I would like to select all the elements to the left of each of my 1s values given a fixed distant. Let's say my distance is 4, I would like to (for instance) replace with a fixed value (saying 2) all the 4 cells at the left of each 1 in A.
A= [0 0 0 0 0 1 0
0 1 0 0 0 0 0
0 0 0 0 0 0 0
0 0 0 0 1 0 1]
B= [0 2 2 2 2 1 0
2 1 0 0 0 0 0
0 0 0 0 0 0 0
2 2 2 2 2 2 1]
In B is what I would like to have, considering also overwrting (last row in B), and cases where there is only 1 value at the left of my 1 and not 4 as the fixed searching distance (second row).
How about this lovely one-liner?
n = 3;
const = 5;
A = [0 0 0 0 0 1 0;
0 1 0 0 0 0 0;
0 0 0 0 0 0 0;
0 0 0 0 1 0 1]
A(bsxfun(#ne,fliplr(filter(ones(1,1+n),1,fliplr(A),[],2)),A)) = const
results in:
A =
0 0 5 5 5 1 0
5 1 0 0 0 0 0
0 0 0 0 0 0 0
0 5 5 5 5 5 1
here some explanations:
Am = fliplr(A); %// mirrored input required
Bm = filter(ones(1,1+n),1,Am,[],2); %// moving average filter for 2nd dimension
B = fliplr(Bm); %// back mirrored
mask = bsxfun(#ne,B,A) %// mask for constants
A(mask) = const
Here is a simple solution you could have come up with:
w=4; % Window size
v=2; % Desired value
B = A;
for r=1:size(A,1) % Go over all rows
for c=2:size(A,2) % Go over all columns
if A(r,c)==1 % If we encounter a 1
B(r,max(1,c-w):c-1)=v; % Set the four spots before this point to your value (if possible)
end
end
end
d = 4; %// distance
v = 2; %// value
A = fliplr(A).'; %'// flip matrix, and transpose to work along rows.
ind = logical( cumsum(A) ...
- [ zeros(size(A,1)-d+2,size(A,2)); cumsum(A(1:end-d-1,:)) ] - A );
A(ind) = v;
A = fliplr(A.');
Result:
A =
0 2 2 2 2 1 0
2 1 0 0 0 0 0
0 0 0 0 0 0 0
2 2 2 2 2 2 1
Approach #1 One-liner using imdilate available with Image Processing Toolbox -
A(imdilate(A,[ones(1,4) zeros(1,4+1)])==1)=2
Explanation
Step #1: Create a morphological structuring element to be used with imdilate -
morph_strel = [ones(1,4) zeros(1,4+1)]
This basically represents a window extending n places to the left with ones and n places to the right including the origin with zeros.
Step #2: Use imdilate that will modify A such that we would have 1 at all four places to the left of each 1 in A -
imdilate_result = imdilate(A,morph_strel)
Step #3: Select all four indices for each 1 of A and set them to 2 -
A(imdilate_result==1)=2
Thus, one can write a general form for this approach as -
A(imdilate(A,[ones(1,window_length) zeros(1,window_length+1)])==1)=new_value
where window_length would be 4 and new_value would be 2 for the given data.
Approach #2 Using bsxfun-
%// Paramters
window_length = 4;
new_value = 2;
B = A' %//'
[r,c] = find(B)
extents = bsxfun(#plus,r,-window_length:-1)
valid_ind1 = extents>0
jump_factor = (c-1)*size(B,1)
extents_valid = extents.*valid_ind1
B(nonzeros(bsxfun(#plus,extents_valid,jump_factor).*valid_ind1))=new_value
B = B' %// B is the desired output

Resources