Dictionaries

7 min read

4 min read

5 min read

5 min read

5 min read

5 min read

4 min read

4 min read

4 min read

A dictionary is a mapping from a key type to a value type.

For example:

Slice
module M
{
struct Employee
{
long number;
string firstName;
string lastName;
}
dictionary<long, Employee> EmployeeMap;
}

This definition creates a dictionary named EmployeeMap that maps from an employee number to a structure containing the details for an employee. Whether or not the key type (the employee number, of type long in this example) is also part of the value type (the Employee structure in this example) is up to you — as far as Slice is concerned, there is no need to include the key as part of the value.

Dictionaries can be used to implement sparse arrays, or any lookup data structure with non-integral key type. Even though a sequence of structures containing key-value pairs could be used to model the same thing, a dictionary is more appropriate:

  • A dictionary clearly signals the intent of the designer, namely, to provide a mapping from a domain of values to a range of values. (A sequence of structures of key-value pairs does not signal that same intent as clearly.)
  • At the programming language level, sequences are implemented as vectors (or possibly lists), that is, they are not well suited to model sparsely populated domains and require a linear search to locate an element with a particular value. On the other hand, dictionaries are implemented as a data structure (typically a hash table or red-black tree) that supports efficient searching in O(log n) average time or better.

The key type of a dictionary need not be an integral type. For example, we could use the following definition to translate the names of the days of the week:

Slice
dictionary<string, string> WeekdaysEnglishToGerman;

The server implementation would take care of initializing this map with the key-value pairs Monday-Montag, Tuesday-Dienstag, and so on.

The value type of a dictionary can be any Slice type. However, the key type of a dictionary is limited to one of the following types:

Other complex types, such as dictionaries, and floating-point types (float and double) cannot be used as the key type. Complex types are disallowed because they complicate the language mappings for dictionaries, and floating-point types are disallowed because representational changes of values as they cross machine boundaries can lead to ill-defined semantics for equality.

Here is the definition of our EmployeeMap once more:

Slice
dictionary<long, Employee> EmployeeMap;

The following code is generated for this definition:

C++
using EmployeeMap = std::map<long long, Employee>;

Again, there are no surprises here: a Slice dictionary simply maps to a standard std::map. As a result, you can use the dictionary like any other map, for example:

C++
EmployeeMap em;
Employee e;
e.number = 42;
e.firstName = "Stan";
e.lastName = "Lippman";
em[e.number] = e;
e.number = 77;
e.firstName = "Herb";
e.lastName = "Sutter";
em[e.number] = e;

You can override the default mapping of Slice dictionaries to C++ with a cpp:type metadata directive, for example:

Slice
[["cpp:include:unordered_map"]]
["cpp:type:std::unordered_map<std::int64_t, Employee>"]
dictionary<long, Employee> EmployeeMap;

With this metadata directive, the dictionary now maps to a C++ std::unordered_map:

C++
#include <unordered_map>
using EmployeeMap = std::unordered_map<std::int64_t, Employee>;

Like with sequences, anything following the cpp:type: prefix is taken to be the name of the type. For example, we could use ["cpp:type:::std::unordered_map<std::int64_t, std::string>"]. In that case, the compiler would use a fully-qualified name to define the type:

C++
using IntStringDict = ::std::unordered_map<std::int64_t, std::string>;

To avoid compilation errors in the generated code, you must instruct the compiler to generate an appropriate include directive with the cpp:include file metadata directive. This causes the compiler to add the line

C++
#include <unordered_map>

to the generated header file.

Instead of std::unordered_map, you can specify a type of your own as the dictionary type, for example:

Slice
[["cpp:include:CustomMap.h"]]
["cpp:type:MyCustomMap<std::int64_t, Employee>"]
dictionary<long, Employee> EmployeeMap;

With these metadata directives, the compiler will use a C++ type MyCustomMap as the dictionary type, and add an include directive for the header file CustomMap.h to the generated code.

The class or template class you provide must meet the following requirements:

  • The class must have a default constructor.
  • The class must have a copy constructor.
  • The class must provide nested types named key_type, mapped_type and value_type.
  • The class must provide iterator and const_iterator types and provide begin and end member functions with the usual semantics; these iterators must be comparable for equality and inequality.
  • The class must provide a clear function.
  • The class must provide an insert function that takes an iterator (as location hint) plus a value_type parameter, and returns an iterator to the new entry or to the existing entry with the given key.

Less formally, this means you can use any class or template class that looks like a standard map or unordered_map as your custom dictionary type.

In addition to modifying the type of a dictionary itself, you can also modify the mapping for particular return values or parameters. For example:

Slice
[["cpp:include:unordered_map"]]
module HR
{
struct Employee
{
long number;
string firstName;
string lastName;
}
dictionary<long, Employee> EmployeeMap;
interface Office
{
["cpp:type:std::unordered_map<long long, Employee>"]
EmployeeMap getAllEmployees();
}
}

With this definition, getAllEmployees returns an unordered_map, while other unqualified parameters of type EmployeeMap would use the default mapping (to a std::map).

Here is the definition of our EmployeeMap once more:

Slice
dictionary<long, Employee> EmployeeMap;

By default, the Slice-to-C# compiler maps the dictionary to the following type:

C#
// Standard Dictionary from System.Collections.Generic
Dictionary<long, Employee>

You can use the "cs:generic:SortedDictionary" or "cs:generic:SortedList" metadata directives to change the default mapping to use a sorted dictionary or sorted list instead. For example:

Slice
["cs:generic:SortedDictionary"]
dictionary<long, Employee> EmployeeMap;

With this definition, the type of the dictionary becomes:

C#
// From namespace System.Collections.Generic
SortedDictionary<long, Employee>

Here is the definition of our EmployeeMap once more:

Slice
dictionary<long, Employee> EmployeeMap;

As for sequences, the Java mapping does not create a separate named type for this definition. Instead, the dictionary is simply an instance of the generic type java.util.Map<K, V>, where K is the mapping of the key type and V is the mapping of the value type. In the example above, EmployeeMap is mapped to the Java type java.util.Map<Long, Employee>. The following code demonstrates how to allocate and use an instance of EmployeeMap:

Java
var em = new java.util.HashMap<Long, Employee>();
Employee e = new Employee();
e.number = 31;
e.firstName = "James";
e.lastName = "Gosling";
em.put(e.number, e);

If the semantics of a HashMap are not suitable for your application, you can specify an alternate type using the java:type metadata directive as shown in the example below:

Slice
["java:type:java.util.TreeMap<String, String>"]
dictionary<string, string> StringMap;

It is your responsibility to use type parameters for the Java class (String in the example above) that are the correct mappings for the dictionary's key and value types.

The compiler requires the formal type to implement java.util.Map<K, V>. If you do not specify a formal type, the compiler uses this type by default.

Note that extra care must be taken when defining dictionary types that contain nested generic types, such as a dictionary whose element type is a custom sequence. The Java compiler strictly enforces type safety, therefore any compatibility issues in the custom type metadata will be apparent when the generated code is compiled.

Refer to the Sequences for more information about java:type.

A Slice dictionary maps to:

  • A JavaScript Map when the key is a Slice built-in type or an enumeration.
  • An Ice.HashMap when the key is a Slice structure.

This distinction is necessary because:

  • JavaScript Map uses the === operator for key equality.
  • Ice.HashMap allows custom comparators, so struct keys can use their equals method for equality.
Slice
struct Employee
{
long number;
string firstName;
string lastName;
}
dictionary<long, Employee> EmployeeMap;

In this example, EmployeeMap maps to a JavaScript Map with:

  • key type = BigInt (from Slice long)
  • value type = Employee (the JavaScript class generated from the Slice struct).

If the key is a Slice struct, the compiler generates code that uses Ice.HashMap.

Slice
dictionary<Employee, string> EmployeeDeptMap;

Generated JavaScript/TypeScript code:

JavaScript
class EmployeeDeptMap extends Ice.HashMap {
constructor(h) {
const keyComparator = ...;
super(h || keyComparator);
}
}
TypeScript
class EmployeeDeptMap extends Ice.HashMap<Employee, string> { ... }
  • new EmployeeDeptMap() automatically sets the comparators for struct keys and values.
  • Using new Ice.HashMap() directly would require you to provide custom comparators yourself.

A Slice dictionary maps to a MATLAB dictionary.

The key type of the MATLAB dictionary is the mapped type for the Slice dictionary key. For example, a Slice string key maps to a MATLAB char key, which MATLAB interprets as a string type.

The value type of the MATLAB dictionary depends on the Slice value type:

Slice Value TypeMATLAB Value Type
bool, numeric type, enum, struct
Corresponding MATLAB type
string
MATLAB string
class, proxy, sequence, dictionary
Cell of the corresponding MATLAB type

Consider the definition of our EmployeeMap once more:

Slice
struct Employee
{
["matlab:identifier:Number"]
long number;
["matlab:identifier:FirstName"]
string firstName;
["matlab:identifier:LastName"]
string lastName;
}
dictionary<long, Employee> EmployeeMap;

EmployeeMap maps to a dictionary with key type = int64 and value type = Employee (a MATLAB class mapped from a Slice struct).

MATLAB
em = configureDictionary('int64', 'M.Employee');
e = M.Employee();
e.Number = 31;
e.FirstName = 'James';
e.LastName = 'Gosling';
em(e.Number) = e;

A Slice dictionary maps to a native PHP associative array. The PHP mapping does not currently support all Slice dictionary types, however, because native PHP associative arrays support only integers and strings as keys.

A Slice dictionary whose key type is an enumeration or one of the primitive types boolean, byte, short, int, or long is mapped as an associative array with an integer key.

A Slice dictionary with a string key type is mapped as an associative array with a string key. All other key types cause a warning to be generated.

Here is the definition of our EmployeeMap:

Slice
dictionary<long, Employee> EmployeeMap;

You can create an instance of this dictionary as shown below:

PHP
$e1 = new Employee;
$e1->number = 42;
$e1->firstName = "Stan";
$e1->lastName = "Lippman";
$e2 = new Employee;
$e2->number = 77;
$e2->firstName = "Herb";
$e2->lastName = "Sutter";
$em = array($e1->number => $e1, $e2->number => $e2);

The Ice runtime validates the elements of a dictionary to ensure that they are compatible with the declared type; InvalidArgumentException exception is thrown if an incompatible type is encountered.

Here is the definition of our EmployeeMap once more:

Slice
dictionary<long, Employee> EmployeeMap;

As for sequences, the Python mapping does not create a separate named type for this definition. Instead, all dictionaries are simply instances of Python's dictionary type. For example:

Python
em = {}
e = Employee()
e.number = 31
e.firstName = "James"
e.lastName = "Gosling"
em[e.number] = e

The Ice runtime validates the elements of a dictionary to ensure that they are compatible with the declared type; a ValueError exception is raised if an incompatible type is encountered.

Here is the definition of our EmployeeMap once more:

Slice
dictionary<long, Employee> EmployeeMap;

As for sequences, the Ruby mapping does not create a separate named type for this definition. Instead, all dictionaries are simply instances of Ruby's hash collection type. For example:

Ruby
em = {}
e = Employee.new
e.number = 31
e.firstName = "James"
e.lastName = "Gosling"
em[e.number] = e

The Ice runtime validates the elements of a dictionary to ensure that they are compatible with the declared type; a TypeError exception is thrown if an incompatible type is encountered.

Here is the definition of our EmployeeMap once more:

Slice
dictionary<long, Employee> EmployeeMap;

The following code is generated for this definition:

Swift
public typealias EmployeeMap = [Int64: Employee]

Again, there are no surprises here: a Slice dictionary simply maps to a standard Swift dictionary. As a result, you can use the dictionary like any other dictionary, for example:

Swift
let stan = Employee(number: 42, firstName: "Stan", lastName: "Lippman")
let herb = Employee(number: 77, firstName: "Herb", lastName: "Sutter")
let em = [stan.number: stan, herb.number: herb]