Dictionaries and containers.Map
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
Written against MATLAB R2026b. The dictionary type needs R2022b, and the configureDictionary, insert, lookup and remove functions need R2023b; readdictionary and writedictionary need R2024b. The Octave 11.3.0 sessions below cover only the legacy containers.Map, because Octave has no dictionary. Everything about dictionary is checked line by line against the R2026b documentation and is labelled "not run here".
A dictionary stores data as key-value pairs. Every key is unique and maps to one value, and you look values up by key instead of by position. Other languages call the idea a map, a hash map, a hash table or an associative array. In MATLAB, an ordinary array is indexed by positive integers; a cell array or structure can be indexed by position or field name; a dictionary lets the key be a string, a number or almost any other value. It is the right container when the program asks "what is the value for this name?" many times, for example a sensor identifier to plate coordinates in the heatflow scenario (The heatflow scenario). The documentation page says lookups and updates take constant time, regardless of the number of entries. Cell arrays are on Cell Arrays, structures on Structures and tables on Tables.
Choosing a container
| Need | Use | Why |
|---|---|---|
Fixed named fields, known when you write the code |
A structure ( |
Field names are identifiers; tooling and tab completion know them. |
Many records of the same columns, to filter and summarise |
A table |
Columns have types; tables are not accepted as dictionary keys or values. |
Values looked up by a key that is only known at run time |
A |
Keys can be strings, numbers or other types; lookup is by key and vectorised. |
Code that must run on a release before R2022b |
|
It has existed since R2008b; the documentation now recommends |
Creating a dictionary
From keys and values
d = dictionary(keys,values) creates a dictionary from two arrays of the same size (or a scalar value, which is mapped to every key). d = dictionary(k1,v1,...,kN,vN) takes the pairs as separate arguments. If a key is repeated, only the last value is kept. The result is always a 1-by-1 object, whatever the number of entries.
ids = ["S1" "S2" "S3"];
temps = [21.5 22.0 19.8];
d = dictionary(ids,temps)
The documentation shows output of this form for a similar dictionary (MATLAB R2026b layout, adapted to these values, not run here):
d =
dictionary (string ⟼ double) with 3 entries:
"S1" ⟼ 21.5000
"S2" ⟼ 22.0000
"S3" ⟼ 19.8000
The header string ⟼ double shows the configured key and value types. Keys and values do not need to share a type, but all keys must share one type and all values must share one type, or be convertible to the configured ones. Character vectors given as keys or values are converted to string scalars.
Source: MATLAB documentation: dictionary
Empty and configured dictionaries
d = dictionary creates an unconfigured dictionary: it has no entries and no types. The first entry you add fixes the key type and the value type. When you want the types fixed in advance, create an empty configured dictionary with configureDictionary (R2023b):
positions = configureDictionary("string","cell");
temps = configureDictionary("string","double");
Both calls display dictionary (string ⟼ cell) with no entries. and the corresponding line for double (not run here). Before R2023b the way to configure an empty dictionary was to pass empty arrays of the intended types, such as dictionary(string.empty,double.empty). The type arguments are class names given as text; the documentation says a type must be a non-abstract class or a subclass of matlab.mixin.Heterogeneous, and that "char" is converted to "string".
A configured dictionary converts compatible entries. With temps configured as string keys and double values, temps("S1") = int8(21) and temps("S2") = "22.5" both store doubles, and an entry that cannot be converted raises an error. An unconfigured dictionary has no types to convert to, so several functions refuse it: isKey, lookup and entries throw an error for an unconfigured dictionary, while numEntries returns 0.
Looking up, adding, changing and removing
The dictionary is indexed with parentheses and a key. Every operation also works on arrays of keys, which is the vectorised form.
d = dictionary(["S1" "S2" "S3"],[21.5 22.0 19.8]);
d("S2") % look up one value
d(["S1" "S3"]) % look up several: returns [21.5 19.8]
d("S2") = 22.4; % change an existing entry
d("S4") = 20.3; % a new key adds an entry at the end
d(["S5" "S6"]) = [18.9 19.1]; % several at once
d("S3") = []; % assigning [] removes the entry
Rules, from the documentation:
-
Order. Entries keep the order in which they were added, and
keys,valuesandentriesreturn them in that order. Changing the value of an existing key does not move it; each new key goes to the end. (containers.Mapis different: it returns keys sorted.) -
Missing key. Looking up a key that is not in the dictionary is an error, not an empty result. Test with
isKeyfirst or uselookupwith a fallback (below). The wording of the error varies by release (not run here). -
Duplicates. If you assign several values to one key in a single statement, only the last is stored.
-
Merge.
d(keys(d2)) = values(d2)copies every entry ofd2intod; keys that exist in both take the value fromd2. The key and value types must be identical or convertible, and the values must support concatenation.
insert, lookup and remove (R2023b)
Three functions do the same work as the indexing forms, with options that indexing lacks. Each returns the new or looked-up value instead of modifying its input in place, so reassign the result.
| Syntax | Meaning |
|---|---|
|
Same as |
|
Adds only the keys that are new; existing entries are left unchanged. |
|
Same as |
|
Returns |
|
Same as |
temps = dictionary(["S1" "S2"],[21.5 22.0]);
temps = insert(temps,["S2" "S3"],[99 19.8],Overwrite=false); % S2 keeps 22.0
t5 = lookup(temps,["S1" "S9"],FallbackValue=NaN); % [21.5 NaN]
temps = remove(temps,"S1");
Source: MATLAB documentation: insert, lookup, remove
Inspecting a dictionary
| Function | Result |
|---|---|
|
Number of key-value pairs; 0 for an unconfigured dictionary. |
|
Logical array, the size of |
|
An N-by-1 array of keys, in insertion order. |
|
An N-by-1 array of values, in the same order. When values are cells it is a cell array. |
|
A table with variables |
|
The key and value types. |
|
Whether the dictionary has types assigned. |
To loop over a dictionary, iterate over a row of keys. A for loop visits one column at a time, and keys(d) is a column, so transpose it; the documentation uses this form:
for id = keys(d)'
fprintf("%s: %.1f\n",id,d(id));
end
Because the loop variable id is a string scalar on each pass, d(id) is one value. numEntries replaces the Count property of containers.Map and length, and a dictionary is an object, not an array, so size(d) is 1 1.
Source: MATLAB documentation: keys, entries, isKey, numEntries
Which types fit
Keys and values can be of almost any type, with these limits from the documentation:
-
All keys share one type and all values share one type (or can be converted to it).
-
Tables, tall arrays and distributed arrays are not accepted as keys or values. Wrap a value in a cell to store it anyway; keys can also be wrapped in a cell if the type supports
keyHashandkeyMatch. -
Vectorised operations (several lookups at once) need values that can be concatenated, and are not supported for structures with different fields.
-
A dictionary hashes each key to a
uint64code (keyHash) and compares keys withkeyMatch. For a custom class that compares only some of its properties, overload both so that equal keys hash equally. -
Adding to a string-typed dictionary converts a number to text:
d("newKey") = 1stores"1".
Values that are arrays, structures or objects
Values must be of one type, so a dictionary of arrays of different sizes, or of mixed types, uses cell as the value type. When the values are cells (R2023a), curly braces read and write the contents directly: d{key} returns the contents of the cell, a comma-separated list for an array of keys (see Comma-Separated Lists), and d{key} = value stores any value. Using braces on a dictionary whose values are not cells is an error.
positions = configureDictionary("string","cell");
positions{"S1"} = [0.2 0.8];
positions{"S2"} = [0.5 0.5];
positions{"S3"} = [0.9 0.1];
xy = positions{"S2"} % [0.5 0.5]
A dictionary can hold dictionaries, which gives a nested structure; read it with chained braces, as in dnest{"The Stranger"}{"author"} on the documentation page. Prefer a table or a structure array when the nested level is a record with fixed fields.
Value semantics
A dictionary behaves as a value, like a numeric array or a string: b = d followed by a change to b does not alter d (not run here; the reference pages describe insert, remove and lookup as returning a new dictionary, which is consistent with this). It is not a handle object. This is the main behavioural difference from containers.Map, which is a handle class, so two variables can refer to the same map and a function can modify a map passed to it. Writing d = insert(d,key,value) and d(key) = value are the same operation.
Saving and loading
A dictionary can be saved to a MAT file with save("mydict","d") and restored with load("mydict.mat","d"), as the documentation page shows (see Workspace Variables and MAT-Files). Since R2024b, writedictionary and readdictionary use JSON instead:
writedictionary(d,"sensor-temps.json")
d2 = readdictionary("sensor-temps.json")
readdictionary reads a JSON object into a dictionary: string keys, and a value type chosen from the contents. When all values are numbers the result is string ⟼ double; when the values mix types the value type is cell (a JSON null becomes <missing>, as in the documentation example). writedictionary(d,filename,PrettyPrint=false) writes without indentation, and FileType="json" forces the format when the extension is not .json. Numbers are written with a decimal point, so 1 is stored as 1.0. JSON in general is on JSON, XML and Web Access.
The older containers.Map
containers.Map is a class that has existed since R2008b. The documentation marks it as superseded: "dictionary is recommended over containers.Map because it accepts more data types as keys and values and provides better performance" (reference page note, since R2022b). It remains available, so existing code keeps working. You meet it in code written before 2022 and in books.
coords = containers.Map({'S3','S1','S2'},{[0.2 0.8],[0.5 0.5],[0.9 0.1]});
disp(class(coords))
disp(coords.Count)
disp(keys(coords))
disp(isKey(coords,'S1'))
disp(isKey(coords,{'S1','S9'}))
p = coords('S2')
alias = coords;
alias('S4') = [0.3 0.3];
disp(coords.Count)
remove(coords,'S3');
disp(keys(coords))
try
coords('S9')
catch err
disp(err.message)
end
n = containers.Map('KeyType','double','ValueType','any');
n(7) = 'seven';
disp(n(7))
disp(class(n.Count))
Octave 11.3.0 output (MATLAB’s layout of the keys cell array and the text of the missing-key error differ; MATLAB’s own wording is not run here):
containers.Map
3
{
[1,1] = S1
[1,2] = S2
[1,3] = S3
}
1
1 0
p =
0.9000 0.1000
4
{
[1,1] = S1
[1,2] = S2
[1,3] = S4
}
containers.Map: specified key <S9> does not exist
seven
uint64
What the run shows, and what the reference page confirms for MATLAB:
-
The keys come back sorted (
S1,S2,S3) although they were given in a different order; adictionarykeeps insertion order. -
isKeyaccepts a cell array of keys and returns a logical array. -
alias = coordsdoes not copy: adding throughaliaschangedcoords.Countfrom 3 to 4. This is handle behaviour. -
Countis auint64(the reference page showsuint64 4), which surprises arithmetic that expects adouble. -
Keys are character vectors or numeric scalars only: a string array is converted to character vectors and
KeyTypebecomes'char'. The permitted numeric key types aredouble,single,int32,uint32,int64anduint64. -
The values are uniform scalars (or all character vectors) unless you pass
'UniformValues',false, andValueTypecan be'any'. -
A missing key is an error.
Migrating from containers.Map to dictionary
| containers.Map | dictionary | Notes |
|---|---|---|
|
|
|
|
|
Use |
|
|
Both error on a missing key; |
|
|
Same. |
|
|
Same name and meaning. |
|
|
|
|
|
Add a |
|
|
|
|
|
|
Handle: copies share one map |
Value: copies are independent |
Check any function that mutated a map passed to it; it must now return the dictionary. |
Heterogeneous values with |
A |
Needs R2023a for the braces. |
Pitfalls
-
Forgetting to reassign.
insert(d,k,v)andremove(d,k)return the result; calling them as statements does nothing tod. Withcontainers.Mapthe same call mutates the map, so the habit carries over wrongly. -
Unconfigured is not empty-typed.
d = dictionaryhas no types andisKey(d,"a")is an error. UseconfigureDictionarywhen the dictionary may be queried before it is filled. -
Mixed key types. All keys share one configured type; a key of another type is converted to it if possible and is an error if not. Put heterogeneous keys in cells only if their types support
keyHashandkeyMatch. -
Scalar values only for arrays. Non-scalar or heterogeneous values (a coordinate pair) need a cell value type; with a numeric value type the assignment errors or the vectorised lookup fails.
-
Order. Do not assume sorted order, and do not assume the order of a dictionary rebuilt from JSON is the order in another program; sort explicitly where order matters.
-
Looking up a key you never added.
d("S9")is an error. Guard withisKeyor uselookupwithFallbackValue. -
charversusstringkeys. Character-vector keys are stored as strings; acharkey of a different case or with trailing blanks is a different key. Normalise withlowerandstrtrim.
The heatflow sensors
The thermocouples in the heatflow scenario are identified by strings in sensors.csv, and each one sits at a known place on the plate. A dictionary from sensor identifier to plate coordinates is the natural container (fragment, not run here; MATLAB R2026b):
positions = configureDictionary("string","cell");
positions{"S1"} = [0.2 0.8];
positions{"S2"} = [0.5 0.5];
positions{"S3"} = [0.9 0.1];
latest = configureDictionary("string","double");
latest("S1") = 21.5;
latest("S2") = 22.0;
latest("S3") = 19.8;
for id = keys(latest)'
xy = positions{id};
fprintf("%s at (%.1f, %.1f): %.1f degC\n",id,xy(1),xy(2),latest(id));
end
unknown = ~isKey(positions,"S7") % true: warn about an unregistered sensor
The same data as a containers.Map is the code run in the Octave session above. Once the log has many rows per sensor, keep the readings in a table (see Tables) with a categorical sensor variable (see Categorical Arrays), and keep the dictionary for per-sensor metadata.
Where older books differ
Books written before 2022 introduce containers.Map as the only built-in key-value type (for example the chapter on "other data types" in Davis’s text) and often describe structures with dynamic field names (s.(name)) as the substitute. Both still work. The current recommendation is dictionary: it takes more types as keys and values, keeps insertion order, is a value type, and has a documented vectorised syntax. A book that says a map must have character or numeric scalar keys is describing containers.Map. The functions insert, lookup, remove, configureDictionary, readdictionary and writedictionary appear in no book published before 2023.