Aman Deep Gautam
Aman Deep Gautam

Reputation: 8747

Reverse a map matlab converting array to string keys

I have a map defined as:

diagonal = eye(4);
v = {diagonal(1,:), diagonal(2,:), diagonal(3,:), diagonal(4,:)}
k = {1, 3, 7, 8}
class_labels = containers.Map(k, v)

Now I need a reverse map, but Matlab does not allow key to be array and hence I need to convert each array to string.

So my class_labels map look like:

1 => [0 0 0 1]
3 => [0 0 1 0]
7 => [0 1 0 0]
8 => [1 0 0 0]

I need something like:

0001  => 1
0010  => 3
0100  => 7
1000  => 8

Upvotes: 1

Views: 911

Answers (3)

Luis Mendo
Luis Mendo

Reputation: 112749

The reverse map can be done using logical indexing:

 allValues = [1 3 7 8];
 value = allValues(logical(key));

For example,

 >> allValues = [1 3 7 8];
 >> key = [0; 0; 1; 0];
 >> value = allValues(logical(key))
 value =
      7

If the key happens to contain several ones, the corresponding values are returned as a vector

 >> key = [1; 0; 1; 0];
 >> value = allValues(logical(key))
 value =
      1     7

Upvotes: 0

rayryeng
rayryeng

Reputation: 104545

You can use the keys and values methods associated with the containers.Map class to extract the keys and values, then apply a string conversion to the values by concatenating all of the bits together.... then simply construct another containers.Map. What you'll do is use cellfun to iterate through each cell element of the values cell array and apply a function such that it converts the sequence of numbers in the array into a concatenated string.

Let's assume for the moment that you don't have access to the keys and values already defined by you and let's say we only have access to the containers.Map itself. You want to invert the dictionary, and so:

%// Your code
diagonal = eye(4);
v = {diagonal(1,:), diagonal(2,:), diagonal(3,:), diagonal(4,:)};
k = {1, 3, 7, 8};
class_labels = containers.Map(k, v);

%// New - Get the keys and labels
kr = keys(class_labels);
vr = values(class_labels);

%// Concatenate all of the bits of the values into a string
vr = cellfun(@(x) char(48+x), vr, 'uni', 0);

%// Create new dictionary
new_labels = containers.Map(vr, kr);

This line here is probably the most confusing: vr = cellfun(@(x) char(48+x), vr, 'uni', 0);. cellfun iterates over all cells in a cell array and applies a function to each cell. This function is the first input into cellfun. I declared an anonymous function where it takes in the contents of a cell in the cell array... so this would be the array of values, then adds 48 to each of the digits. This gives us an array of 48/49 instead of 0/1. Once we do this, we cast this array to char so that the digits are represented as their ASCII or string equivalents. The ASCII code for 0/1 is 48/49. By using char on this modified array, what is produced is a string that concatenates all of these characters together. The second input is the cell array we're working on, and the third and fourth parameters tell you that the output of cellfun is not a numeric vector but another cell array of values. 'uni' is short for 'UniformOutput', and this is set to 0/false because the output of this function is not a numeric vector, but a cell array of vectors. Each cell would be the string created by concatenating all of the numbers in the numeric array together.

If we show the keys and values, we get:

>> keys(new_labels)

ans = 

    '0001'    '0010'    '0100'    '1000'

>> values(new_labels)

ans = 

    [8]    [7]    [3]    [1]

You can see that each string key maps to the right inverse value.

Upvotes: 1

Rory Yorke
Rory Yorke

Reputation: 2236

The problem is to map your arrays to strings or doubles, and possibly back again. From the constraints you've given, you can use find to "encode" the vectors; decoding is not achievable in a one-liner (I don't think), but something like

function idx2vec(i,n)
% I - index to set to 1
% N - length of vector
  v = zeros([1,n]);
  v(i) = 1;
end

You could use this technique to get a single argument key->array converter:

function f= fidx2vec(n)
  function v=idx2vec(i)
    v = zeros([1,n]);
    v(i) = 1;
  end
  f = @idx2vec;
end

For general arrays, you could use mat2str (array to key) and eval (key to array) instead.

For readability, I wrapped find in vec2idx:

function i=vec2idx(v)
  i = find(v);
end

and added this to your code:

diagonal = eye(4);
v = {diagonal(1,:), diagonal(2,:), diagonal(3,:), diagonal(4,:)};
k = {1, 3, 7, 8};
class_labels = containers.Map(k, v);

rk = cellfun(@vec2idx, v, 'uniformoutput', false);

reverse_map = containers.Map(rk, k);

for iv = 1:length(v)
  fprintf('%s -> %g\n',mat2str(v{iv}),reverse_map(vec2idx(v{iv})));
end

to produce:

[1 0 0 0] -> 1
[0 1 0 0] -> 3
[0 0 1 0] -> 7
[0 0 0 1] -> 8

A final comment: this solution maps your arrays to the positive integers. Matlab has a perfectly good container mapping positive integers to doubles, called arrays! If your keys are dense (i.e., all or most values from 1:n will be used), I'd just use an array instead of a containers.Map. You could use nan to mark non-existent entries. If your keys are sparse, that is your arrays are length (say) 1000, but only 50 possibilities are used, containers.Map is a reasonable option.

Upvotes: 0

Related Questions