Skip to content
IllidanS4 edited this page May 7, 2019 · 4 revisions

Introduction

Even though storing values in a linear fashion could be useful, sometimes there is need to pair a value with an arbitrary integer, or even a value with an arbitrary type. Maps (internally represented by std::unordered_map or std::map) contain a list of key-value pairs, with all keys unique. They are optimised to allow for fast access using a key to obtain a value paired with that key. Similarly to lists, maps are created by map_new and must be destroyed by calling map_delete or map_delete_deep.

Operations

Inserting new values inside a map is done by using two families of functions: map_add and map_set. The former tries to insert a new key-value pair into the map, but fails is the key is already present. The latter either adds a new pair into the map, or sets the new value for an existing pair if the key is present.

Conversely, obtaining values from the map can be done using the map_get family of functions:

new Map:m = map_new();
map_add(m, false, 12);
map_add(m, 0, 13);
map_str_add(m, "key1", 14);
map_str_add_arr(m, "key2", {1, 2, 3});
assert map_get(m, false) == 12;
assert map_get(m, 0) == 13;
assert map_str_get(m, "key1") == 14;
new buffer[16];
assert map_str_get_arr(m, "key2", buffer) == 3;

The word before add, set or get specifies the type of the key, and the word after it specifies the type of the value. Like for lists, applicable types are cells (no word), arrays (arr), strings (str), or variants (var). The tag of the key is taken into account for key comparisons and must be always correct. Since the key comparison has the same semantics as the == operator on variants, reference types have their value taken into account:

new Map:m = map_new();
map_add(m, str_new_static("str1"), 1);
map_add(m, str_new_static("str2"), 2);
assert map_has_key(m, str_new_static("str1"));
assert map_has_key(m, str_new_static("str2"));

If you want to use only the address of the strings for comparison, the same technique as for variants can be used:

new Map:m = map_new();
map_add(m, pawn_ref<String>(str_new_static("str1")), 1);
map_add(m, pawn_ref<String>(str_new_static("str2")), 2);
assert !map_has_key(m, pawn_ref<String>(str_new_static("str1")));
assert !map_has_key(m, pawn_ref<String>(str_new_static("str2")));

Since global strings and global variants are tagged using a subtag of String and Variant, the tags don't have to match exactly when comparing keys. Namely, you can index a map containing a const string key with a non-const string, or vice versa:

new Map:m = map_new();
map_add(m, str_new_static("str1"), 1);
assert map_has_key(m, ConstString:str_new_static("str1"));

Iterating

Maps can be iterated in virtually the same fashion as lists:

new Map:m = map_new();
map_add(m, 1, 5.0);
map_add(m, 1.1, false);
map_add(m, true, 0);
for_map(i : m)
//for(new Iter:i = map_iter(m); iter_inside(i); iter_move_next(i))
{
    new c, Float:f;
    if(iter_get_key_safe(i, c)) printf("%d", c);
    else if(iter_get_key_safe(i, f)) printf("%f", f);
    print("=>");
    if(iter_get_value_safe(i, c)) printf("%d", c);
    else if(iter_get_value_safe(i, f)) printf("%f", f);
}

While a value in a pair can be modified, the key cannot, since that could cause inconsistencies in the map. It is possible to modify a key by changing the object it points to in case of a string or a variant, but doing so is not recommended. You should first remove the pair and then insert a new one.

Ordered maps

By default, all pairs stored in a map are unordered, meaning their order of iteration is undefined. However, ordering can be turned on for any map:

new Map:m = map_new();
map_set_ordered(m, true);
map_set(m, 5, 0);
map_set(m, 9, 0);
map_set(m, 7, 0);
map_set(m, -2, 0);
map_set(m, 1, 0);
for_map(it : m)
{
    print_s(str_val(it));
}

map_set_ordered changes the map implementation from a hash map to a binary search tree, resulting in the order of keys being maintained (as ascending). Other operations behave the same way, but iterators can now be operated in the reverse order.

Lifetime

Maps are deleted in the same manner lists are. They must be deleted explicitly via map_delete or map_delete_deep, the former deleting just the map object, while the latter recursively deletes all objects that are referenced in the map.

Generic maps

Maps can be generic in the same fashion as lists.

new Map<_,Float>:m = map_new<_,Float>();
map_add<_,Float>(m, 1, 1.0);
map_add<_,Float>(m, 2, false); //tag mismatch
map_add<_,Float>(m, 1.0, 6.0); //tag mismatch
new Float:f = map_get<_,Float>(m, 1);

As with lists, this will only insert an int-float pair in the map, but retagging it to Map could break it in a similar way.

Clone this wiki locally