How do I do "map" in octave/matlab? [duplicate] - matlab

This question already has answers here:
Map function in MATLAB?
(7 answers)
Closed 8 years ago.
Here's a bit of octave code
>> [4,5]([1,2,1])
ans =
4 5 4
I'd call that mapping the function 1->4, 2->5 over the vector [1,2,1].
But the map I want to do is 0->1,25->2,240->9, NaN->0 over a very long vector.
I'd also like it to bomb if there are any values except 0,25,240,NaN in the vector.
I can see various ways to make this happen, none of them elegant, and wondered if there was an idiomatic way to express it.

If you can use MATLAB, I would recommend you use the containers.Map paradigm, which is essentially an associative array that performs key/value lookups. It also spits out errors if you throw in a value that is not part of the dictionary. As such, you simply provide an input/output relationship for each element in your mapping. The inputs are what are known as keys, and the outputs are what are known as values.
Once you're finished, you provide a cell array of input values into your dictionary / associate array with the values function, then convert this cell array back into a numeric vector when you're finished. Judging from your input/output pairs, you want the inputs to be double and the outputs to be double as well. However, the problem with containers.Map is that NaN can't be used as a key. As such, a workaround would be to convert each element in your input numeric vector as a cell array of character keys, with the outputs defined as a cell array of character values.
We can achieve this with arrayfun, which performs an operation over each value in an array. This is very much like a for loop, but what's special is that if you specify the uni flag to be 0, the output of each corresponding element will be converted into a string. You then create your dictionary with these cell array of characters. Now, to do the mapping on your inputs, you'll have to convert these into a cell array of characters as well, then use values to get what the corresponding outputs are, then use str2double to convert each output cell element back and place it into a numeric array.
As such, borrowing from huntj's sample input, this is what you would have to do:
%// Input-Output relationships
in = [0,25,240,NaN];
out = [1,2,9,0];
%// Test inputs
vector = [0, 25, 240, NaN, 0, 25, 240, NaN];
% // For declaring our dictionary
in_cell = arrayfun(#num2str, in, 'uni', 0);
out_cell = arrayfun(#num2str, out, 'uni', 0);
% // Input test into dictionary
vector_cell = arrayfun(#num2str, vector, 'uni', 0);
% // Create dictionary
dict = containers.Map(in_cell, out_cell);
% // Put in inputs to be mapped
output_cell = values(dict,vector_cell);
% // Convert back to numeric array
output = str2double(output_cell);
This is what I get with the above code, with our final output stored in output:
output =
1 2 9 0 1 2 9 0

The following code is probably not elegant, but it is vectorized, at least.
vector = [0, 25, 240, NaN, 0, 25, 240, NaN];
old_value = [0, 25, 240, NaN];
new_value = [1, 2, 9, 0];
assert(size_equal(old_value, new_value))
total_mask = false;
for idx = 1:numel(old_value)
if isnan(old_value(idx))
partial_mask = isnan(vector);
else
partial_mask = vector == old_value(idx);
endif
vector(partial_mask) = new_value(idx);
total_mask |= partial_mask;
endfor
assert(all(total_mask), "some values were unexpected")
vector
which gives
1 2 9 0 1 2 9 0
and yields an error if a value is not in the old_values given.
EDIT [more compact, but more memory hungry and not faster]:
vector = [0, 25, 240, NaN, 0, 25, 240, NaN];
old_values = [0, 25, 240, NaN];
new_values = [1, 2, 9, 0];
mask__ = vector == old_values(:);
mask__(isnan(old_values), :) = isnan(vector);
assert(all(any(mask__, 1)), "some values were unexpected")
vector = cellfun(#(mask_) new_values(mask_), num2cell(mask__, 1))

This can be easily done through logical indexing. Using huntj's starting vectors:
allowed = [0, 25, 240];
v = [0; 25; 240; NaN; 0; 25; 240; NaN];
notAllowed = not(any(bsxfun(#eq,v,allowed),2));
notNaN = not(isnan(v));
if any(notAllowed & notNaN)
error('Illegal entry');
end
v( v == 0 ) = 1;
v( v == 25 ) = 2;
v( v == 240 ) = 9;
v( isnan(v) ) = 0;
In order for the bsxfun() to work, v needs to be a column vector. NaN was not included in the allowed table since NaN == NaN is always false.

Related

Extracting a matrix from a cell vector in MATLAB

A sparse matrix is a large matrix with almost all elements of the same
value (typically zero). The normal representation of a sparse matrix
takes up lots of memory when the useful information can be captured
with much less. A possible way to represent a sparse matrix is with a
cell vector whose first element is a 2-element vector representing the
size of the sparse matrix. The second element is a scalar specifying
the default value of the sparse matrix. Each successive element of the
cell vector is a 3-element vector representing one element of the
sparse matrix that has a value other than the default. The three
elements are the row index, the column index and the actual value.
Write a function called "sparse2matrix" that takes a single input of a
cell vector as defined above and returns the output argument called
"matrix", the matrix in its traditional form. Consider the following
run:
cellvec = {[2 3], 0, [1 2 3], [2 2 -3]};
matrix = sparse2matrix(cellvec)
matrix =
0 3 0
0 -3 0
Good morning/afternoon/night, everyone
I was wondering if you could help me with this.
I am trying to complete this, but I am not sure how to deal with this. I understand that I am interested in the first part of the cell vector, but I am not sure about how to tell Matlab I need that. This is my code:
function matrix = sparse2matrix(x)
A = [2 3];
B = 0;
C = [1, 2, 3];
x = {A, 0, C};
matrix = cell2mat(x);
end
The result of this code is different from the result I showed above.
I am not getting the right answer and honestly I do not know what to do, so I would appreciate if you guide me a little bit.
Not the most elegant way of doing it but it gets the job done. Uses the cell2mat() function and indexing to grab the necessary values for each step. A for-loop is then used to obtain the 3-element vectors that is used to change the array values from the default values in the respective indices.
cellvec = {[2 3], 0, [1 2 3], [2 2 -3]};
[matrix] = sparse2matrix(cellvec)
function [matrix] = sparse2matrix(x)
Matrix_Size = cell2mat(x(1,1));
Matrix_Height = Matrix_Size(1);
Matrix_Width = Matrix_Size(2);
Default_Value = cell2mat(x(1,2));
matrix = Default_Value*ones(Matrix_Height,Matrix_Width);
Triplets = x(3:end);
Number_Of_Triplets = length(Triplets);
for Triplet_Index = 1: Number_Of_Triplets
Current_Triplet = cell2mat(Triplets(1,Triplet_Index));
Row = Current_Triplet(1,1);
Column = Current_Triplet(1,2);
Value = Current_Triplet(1,3);
matrix(Row,Column) = Value;
end
end
Explanation:
The first line in the local function sparse2matrix() indicates to retrieve row 1, column 1 of the cell array x and then convert it to a matrix. The conversion is done using the cell2mat() function.
Matrix_Size = cell2mat(x(1,1));
Results in:
Matrix_Size = [2 3]
The following line indicates to grab index 1 of the previous array Matrix_Size. This will be used to determine the height of the output array matrix. This is done in a similar fashion to evaluate the Matrix_Width using index 2.
Matrix_Height = Matrix_Size(1);
Results in:
Matrix_Height = 2
Ran using MATLAB R2019b

Replace array value at SOME specific rows in matlab

I try to replace the array values at some specific row (row number 1, 10, 12, 20, 39 etc, not continuous) using the linear index from find. However I don't know how to proceed after this few lines of code:
[valmax, ~]=max(A); %Where A will consist of more than one MAX value
idxmax=find(A==valmax);
mclr=repmat([1 0 0],[10 1]); %Create the matrix of my value
mclr(idxmax,:)=[0 1 0]; %replace the value at idxmax index, this line won't work
Any idea how to fix this? Or there are other function instead of using find?
Thanks!
you can use ind2sub to convert the linear indexes into row indexes:
A = randi(5,[10 3]); % random matrix
[valmax, ~] = max(A(:)); %Where A will consist of more than one MAX value
idxmax = find(A == valmax);
% convert linear index into row index
[rowmax,colmax] = ind2sub(size(A),idxmax);
rowmax = unique(rowmax); % get unique rows
mclr = repmat([1 0 0],[10 1]); %Create the matrix of my value
mclr(rowmax,:) = repmat([0 1 0],[numel(rowmax) 1]); %replace the value at idxmax index
however it's more efficient to directly get the rows containing max values using any(X, 2):
rowmax = find(any(A == valmax,2));

Bitwise or over an array in Matlab?

I have a large array of binary numbers, and I want to do a bitwise OR over one dimension of the array:
X = [ 192, 96, 96, 2, 3
12, 12, 128, 49, 14
....
];
union_of_bits_on_dim2 = [
bitor(X(:,1), bitor(X(:,2), bitor(X(:,3), ... )))
];
ans =
[ 227
191
... ]
Is there a simple way of doing this? I'm actually working on an n-dimensional array. I tried bi2de but it flattens out my array and so the subscripting becomes complicated.
I could do it easily if matlab had a fold function but I don't think it does.
OK #Divakar asked for runnable code so to make it clear here is a long-winded version that might work for a 2D array.
function U=union_of_bits_on_dim2(X)
U=zeros(size(X,1),1);
for i=1:size(X,2)
U=bitor(U,X(:,i));
end
Surely it be done without looping? I was of course hoping that bitor could take arbitrary numbers of arguments. Then it could have been done with mat2cell.
One vectorized approach -
[m,n] = size(X) %// Get size of input array
bd = dec2bin(X)-'0' %// Get binary digits
%// Get cumulative "OR-ed" version with ANY(..,1)
cum_or = reshape(any(permute(reshape(bd,m,n,[]),[2 3 1]),1),8,[])
%// Finally convert to decimals
U = 2.^(7: -1:0)*cum_or
I don't know any function that can do that automatically. However you can loop over the dimension you are interested in:
function result = bitor2d(A)
result = A(1,:);
for i=2:size(A,1)
result = bitor(result,A(i,:));
end
end
If your array has more than 2 dimensions, then you need to prepare it to have only 2.
function result = bitornd(A,whichdimension)
B = shiftdim(A,whichdimension-1); % change dimensions order
s = size(B);
B = reshape(B,s(1),[]); % back to the original shape
result = bitor2d(B);
s(1) = 1;
result = reshape(result,s); % back to the original shape
result = shiftdim(result,1-whichdimension); % back to the original dimension order
end

Vectorizing the Notion of Colon (:) - values between two vectors in MATLAB

I have two vectors, idx1 and idx2, and I want to obtain the values between them. If idx1 and idx2 were numbers and not vectors, I could do that the following way:
idx1=1;
idx2=5;
values=idx1:idx2
% Result
% values =
%
% 1 2 3 4 5
But in my case, idx1 and idx2 are vectors of variable length. For example, for length=2:
idx1=[5,9];
idx2=[9 11];
Can I use the colon operator to directly obtain the values in between? This is, something similar to the following:
values = [5 6 7 8 9 9 10 11]
I know I can do idx1(1):idx2(1) and idx1(2):idx2(2), this is, extract the values for each column separately, so if there is no other solution, I can do this with a for-loop, but maybe Matlab can do this more easily.
Your sample output is not legal. A matrix cannot have rows of different length. What you can do is create a cell array using arrayfun:
values = arrayfun(#colon, idx1, idx2, 'Uniform', false)
To convert the resulting cell array into a vector, you can use cell2mat:
values = cell2mat(values);
Alternatively, if all vectors in the resulting cell array have the same length, you can construct an output matrix as follows:
values = vertcat(values{:});
Try taking the union of the sets. Given the values of idx1 and idx2 you supplied, run
values = union(idx1(1):idx1(2), idx2(1):idx2(2));
Which will yield a vector with the values [5 6 7 8 9 10 11], as desired.
I couldn't get #Eitan's solution to work, apparently you need to specify parameters to colon. The small modification that follows got it working on my R2010b version:
step = 1;
idx1 = [5, 9];
idx2 = [9, 11];
values = arrayfun(#(x,y)colon(x, step, y), idx1, idx2, 'UniformOutput', false);
values=vertcat(cell2mat(values));
Note that step = 1 is actually the default value in colon, and Uniform can be used in place of UniformOutput, but I've included these for the sake of completeness.
There is a great blog post by Loren called Vectorizing the Notion of Colon (:). It includes an answer that is about 5 times faster (for large arrays) than using arrayfun or a for-loop and is similar to run-length-decoding:
The idea is to expand the colon sequences out. I know the lengths of
each sequence so I know the starting points in the output array. Fill
the values after the start values with 1s. Then I figure out how much
to jump from the end of one sequence to the beginning of the next one.
If there are repeated start values, the jumps might be negative. Once
this array is filled, the output is simply the cumulative sum or
cumsum of the sequence.
function x = coloncatrld(start, stop)
% COLONCAT Concatenate colon expressions
% X = COLONCAT(START,STOP) returns a vector containing the values
% [START(1):STOP(1) START(2):STOP(2) START(END):STOP(END)].
% Based on Peter Acklam's code for run length decoding.
len = stop - start + 1;
% keep only sequences whose length is positive
pos = len > 0;
start = start(pos);
stop = stop(pos);
len = len(pos);
if isempty(len)
x = [];
return;
end
% expand out the colon expressions
endlocs = cumsum(len);
incr = ones(1, endlocs(end));
jumps = start(2:end) - stop(1:end-1);
incr(endlocs(1:end-1)+1) = jumps;
incr(1) = start(1);
x = cumsum(incr);

Aggregate 3rd dimension of a 3d array for the subscripts of the first dimension

I have a 3 Dimensional array Val 4xmx2 dimension. (m can be variable)
Val{1} = [1, 280; 2, 281; 3, 282; 4, 283; 5, 285];
Val{2} = [2, 179; 3, 180; 4, 181; 5, 182];
Val{3} = [2, 315; 4, 322; 5, 325];
Val{4} = [1, 95; 3, 97; 4, 99; 5, 101];
I have a subscript vector:
subs = {1,3,4};
What i want to get as output is the average of column 2 in the above 2D Arrays (only 1,3 an 4) such that the 1st columns value is >=2 and <=4.
The output will be:
{282, 318.5, 98}
This can probably be done by using a few loops, but just wondering if there is a more efficient way?
Here's a one-liner:
output = cellfun(#(x)mean(x(:,1)>=2 & x(:,1)<=4,2),Val(cat(1,subs{:})),'UniformOutput',false);
If subs is a numerical array (not a cell array) instead, i.e. subs=[1,3,4], and if output doesn't have to be a cell array, but can be a numerical array instead, i.e. output = [282,318.5,98], then the above simplifies to
output = cellfun(#(x)mean(x(x(:,1)>=2 & x(:,1)<=4,2)),Val(subs));
cellfun applies a function to each element of a cell array, and the indexing makes sure only the good rows are being averaged.