Create a MATLAB dictionary like in Python

Viewed 5385

I would like to know if there exists a way in MATLAB to create a dictionary like in Python.

I have several Port Name and Port Type and I would like to create a dictionary like this :

dict = {PortName : PortType, PortName : PortType, ...}
4 Answers

The closest analogy is containers.Map:

containers.Map: Object that maps values to unique keys

The keys are character vectors, strings, or numbers. The values can have arbitrary types.

To create the map you pass containers.Map a cell array of keys and a cell array of values (there are other, optional input arguments):

>> dict = containers.Map({ 'a' 'bb' 'ccc' }, { [1 2 3 4], 'Hey', {2 3; 4 5} });
>> dict('a')
ans =
     1     2     3     4
>> dict('bb')
ans =
    'Hey'
>> dict('ccc')
ans =
  2×2 cell array
    {[2]}    {[3]}
    {[4]}    {[5]}

You can also append key-value pairs to an existing map:

>> dict('dddd') = eye(3);
>> dict('dddd')
ans =
     1     0     0
     0     1     0
     0     0     1

However, depending on what you want to do there are probably more Matlab-like ways to do it. Maps are not so widely used in Matlab as dictionaries are in Python.

You can use containers.Map as suggested by Luis Mendo in the other answer, but I think in this case a struct is much simpler:

>> dict = struct('a',[1 2 3 4], 'bb','Hey', 'ccc',{2 3; 4 5});
>> dict.('a')
ans =
     1     2     3     4
>> dict.a
ans =
     1     2     3     4
>> dict.b
ans =
    'Hey'
>> dict.ccc
ans =
  2×2 cell array
    {[2]}    {[3]}
    {[4]}    {[5]}

>> dict.dddd = eye(3);
>> dict.('eee') = eye(3);
>> dict.dddd
ans =
     1     0     0
     0     1     0
     0     0     1

That is, the struct is always indexed using .('name') or simply .name. But there are restrictions on what 'name' can be (it must be a valid variable name), unlike for the Map.

See this other answer to learn about important differences between the two approaches.

MATLAB R2022b has a new dictionary datatype that is significantly better than containers.Map. A tutorial describing it can be found on The MATLAB blog https://blogs.mathworks.com/matlab/2022/09/15/an-introduction-to-dictionaries-associative-arrays-in-matlab/

For example:

names = ["Mike","Dave","Bob"];
weights = [89,75,68]; % weight in kilograms
gym = dictionary(names,weights) % Vectorised constructor. Performs elementwise mapping.

gives

gym = 

      dictionary (string ⟼ double) with 3 entries:
    
        "Mike" ⟼ 89
        "Dave" ⟼ 75
        "Bob"  ⟼ 68

I wrote a func to make containers.Map via dict(a=1, b='yes') syntax. Dunno if foulproof but seems to work, also wrote a function to pretty print a containers.Map. Demo:

d = dict(a=1, mode='fast', arr=[[1 2 3]; [5 6 7]], yes=dict(k=2));
disp(dict2str(d))
a=1, arr=[1 2 3] [5 6 7], mode='fast', yes=dict(k=2,),

also guards against very long prints but this can be improved:

d = dict(a=randn(1000));
disp(dict2str(d))
a=<double>,

function out = dict(varargin)
    % DICT Python dictionary syntax
    %
    %    out = dict(a=1, mode='fast', arr=[1 2 3], yes=dict(k=2))
    %
    % Show with
    %
    %    disp(dict2str(out))
    out = containers.Map('UniformValues', false);
    current_key = '';
    for i=1:length(varargin)
        if mod(i, 2)
            current_key = varargin{i};
        else
            out(current_key) = varargin{i};
        end
    end
end


function out = dict2str(d, C)
    % DICT2STR
    %
    %    `disp(out)` is like
    %        analytic=true, normalize='l1', paths_exclude=None, r_psi=.70711
    arguments
        d;
        C.delim = ', ';
        C.max_value_length = 50;
    end
    keys = d.keys;
    values = d.values;

    out = "";
    for i=1:length(keys)
        k = keys{i};
        v = values{i};
        if class(v) == "containers.Map"
            vs = "dict(" + dict2str(v) + ")";
        elseif isnumeric(v) && length(v) > 1
            vs = "[" + join(string(v)) + "]";
        elseif isempty(v)
            vs = "None";
        elseif ismember(class(v), ["string", "char"])
            vs = sprintf("'%s'", join(string(v)));
        else
            vs = join(string(v));
        end
        if C.max_value_length && length(vs) > C.max_value_length
            vs = "<" + class(v) + ">";
        end
        out = out + sprintf("%s=%s%s", join(string(k)), join(string(vs)), C.delim);
    end

    % strip delimiter
    for i=1:length(C.delim)
        out = strip(out, 'right', C.delim(i));
    end
end
Related