2

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.

John Lawrence Aspden
  • 17,124
  • 11
  • 67
  • 110
  • Note sure if I got that right, but you can look into [`ismember`](http://www.mathworks.in/help/matlab/ref/ismember.html). – Divakar Oct 10 '14 at 18:37
  • see this question: http://stackoverflow.com/questions/983163/map-function-in-matlab – danny Oct 10 '14 at 18:45
  • Do you really need `NaN` and `0` as an index? And are all other numeric values integer? – Luis Mendo Oct 10 '14 at 18:59
  • @Shai This is not exactly a duplicate of [Map function in MATLAB?](http://stackoverflow.com/questions/983163/map-function-in-matlab). It is more specifically about value replacement, with NaNs, along a long vector. – ederag Oct 12 '14 at 08:58

3 Answers3

2

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))
ederag
  • 2,409
  • 25
  • 49
  • I wouldn't call it "vectorized" because you're using a `for` loop, but good code nonetheless! – rayryeng Oct 10 '14 at 20:31
  • @rayryeng Admittedly I should have written "partially vectorized", but it is really vectorized along the largest dimension (the question mentioned a "very long vector"). So the performance should be fairly good already, don't you think ? – ederag Oct 10 '14 at 21:51
  • yup... I agree there :) This will probably be JIT accelerated anyway, as the operations seem fairly benign. – rayryeng Oct 10 '14 at 22:24
2

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
rayryeng
  • 102,964
  • 22
  • 184
  • 193
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.

TroyHaskin
  • 8,361
  • 3
  • 22
  • 22