NAME

libslack(map) - map module


SYNOPSIS

    #include <slack/map.h>

    typedef struct Map Map;
    typedef struct Mapper Mapper;
    typedef struct Mapping Mapping;
    typedef list_release_t map_release_t;
    typedef list_copy_t map_copy_t;
    typedef list_cmp_t map_cmp_t;
    typedef size_t map_hash_t(size_t table_size, const void *key);
    typedef void map_action_t(void *key, void *item, void *data);

    Map *map_create(map_release_t *destroy);
    Map *map_create_sized(size_t size, map_release_t *destroy);
    Map *map_create_with_hash(map_hash_t *hash, map_release_t *destroy);
    Map *map_create_sized_with_hash(size_t size, map_hash_t *hash, map_release_t *destroy);
    Map *map_create_locked(Locker *locker, map_release_t *destroy);
    Map *map_create_locked_sized(size_t size, Locker *locker, map_release_t *destroy);
    Map *map_create_locked_with_hash(Locker *locker, map_hash_t *hash, map_release_t *destroy);
    Map *map_create_locked_sized_with_hash(size_t size, Locker *locker, map_hash_t *hash, map_release_t *destroy);
    Map *map_create_generic(map_copy_t *copy, map_cmp_t *cmp, map_hash_t *hash, map_release_t *key_destroy, map_release_t *value_destroy);
    Map *map_create_generic_sized(size_t size, map_copy *copy, map_cmp_t *cmp, map_hash_t *hash, map_release_t *key_destroy, map_release_t *value_destroy);
    Map *map_create_generic_locked(Locker *locker, map_copy_t *copy, map_cmp_t *cmp, map_hash_t *hash, map_release_t *key_destroy, map_release_t *value_destroy);
    Map *map_create_generic_locked_sized(Locker *locker, size_t size, map_copy *copy, map_cmp_t *cmp, map_hash_t *hash, map_release_t *key_destroy, map_release_t *value_destroy);
    void map_release(Map *map);
    void *map_destroy(Map **map);
    int map_own(Map *map, map_release_t *destroy);
    map_release_t *map_disown(Map *map);
    int map_add(Map *map, const void *key, void *value);
    int map_put(Map *map, const void *key, void *value);
    int map_insert(Map *map, const void *key, void *value, int replace);
    int map_remove(Map *map, const void *key);
    void *map_get(const Map *map, const void *key);
    Mapper *mapper_create(Map *map);
    void mapper_release(Mapper *mapper);
    void *mapper_destroy(Mapper **mapper);
    int mapper_has_next(Mapper *mapper);
    void *mapper_next(Mapper *mapper);
    const Mapping *mapper_next_mapping(Mapper *mapper);
    void mapper_remove(Mapper *mapper);
    int map_has_next(Map *map);
    void map_break(Map *map);
    void *map_next(Map *map);
    const Mapping *map_next_mapping(Map *map);
    void map_remove_current(Map *map);
    const void *mapping_key(const Mapping *mapping);
    const void *mapping_value(const Mapping *mapping);
    List *map_keys(Map *map);
    List *map_values(Map *map);
    void map_apply(Map *, map_action_t *action, void *data);
    size_t map_size(const Map *map);


DESCRIPTION

This module provides functions for manipulating and iterating over a set of mappings from strings to homogeneous data (or heterogeneous data if it's polymorphic), also known as hashes or associative arrays. Maps may own their items. Maps created with a non-NULL destroy function use that function to destroy an item when it is removed from the map and to destroy each item when the map itself it destroyed. Maps are hash tables with 11 buckets by default. They grow when necessary, approximately doubling in size each time up to a maximum size of 26,214,401 buckets.

Map *map_create(map_release_t *destroy)

Creates a Map with string keys, 11 buckets and destroy as its item destructor. On success, returns the new map. On error, returns NULL.

Map *map_create_sized(size_t size, map_release_t *destroy)

Creates a Map with string keys, approximately size buckets and destroy as its item destructor. The actual size will be the first prime greater than or equal to size in a prebuilt sequence of primes between 11 and 26,214,401 that double at each step. On success, returns the new map. On error, returns NULL.

Map *map_create_with_hash(map_hash_t *hash, map_release_t *destroy)

Creates a Map with strings keys, 11 buckets, hash as the hash function and destroy as its item destructor. On success, returns the new map. On error, returns NULL. The arguments to hash are a size_t specifying the number of buckets and a const void * specifying the key to hash. It returns a size_t between zero and the table size - 1.

Map *map_create_sized_with_hash(size_t size, map_hash_t *hash, map_release_t *destroy)

Creates a Map with string keys, approximately size buckets, hash as its hash function and destroy as its item destructor. The actual size will be the first prime greater than or equal to size in a built in sequence of primes between 11 and 26,214,401 that double at each step. On success, returns the new map. On error, returns NULL. The arguments to hash are a size_t specifying the number of buckets and a const void * specifying the key to hash. It must return a size_t between zero and the table size - 1.

Map *map_create_locked(Locker *locker, map_release_t *destroy)

Creates a Map with string keys, 11 buckets and destroy as its item destructor. Multiple threads accessing this map will be synchronised by locker. On success, returns the new map. On error, returns NULL.

Map *map_create_locked_sized(Locker *locker, size_t size, map_release_t *destroy)

Creates a Map with string keys, approximately size buckets and destroy as its item destructor. Multiple threads accessing this map will be synchronised by locker. The actual size will be the first prime greater than or equal to size in a prebuilt sequence of primes between 11 and 26,214,401 that double at each step. On success, returns the new map. On error, returns NULL.

Map *map_create_locked_with_hash(Locker *locker, map_hash_t *hash, map_release_t *destroy)

Creates a Map with strings keys, 11 buckets, hash as the hash function and destroy as its item destructor. Multiple threads accessing this map will be synchronised by locker. On success, returns the new map. On error, returns NULL. The arguments to hash are a size_t specifying the number of buckets and a const void * specifying the key to hash. It returns a size_t between zero and the table size - 1.

Map *map_create_locked_sized_with_hash(Locker *locker, size_t size, map_hash_t *hash, map_release_t *destroy)

Creates a Map with string keys, approximately size buckets, hash as its hash function and destroy as its item destructor. The actual size will be the first prime greater than or equal to size in a built in sequence of primes between 11 and 26,214,401 that double at each step. Multiple threads accessing this map will be synchronised by locker. On success, returns the new map. On error, returns NULL. The arguments to hash are a size_t specifying the number of buckets and a const void * specifying the key to hash. It must return a size_t between zero and the table size - 1.

Map *map_create_generic(map_copy_t *copy, map_cmp_t *cmp, map_hash_t *hash, map_release_t *key_destroy, map_release_t *value_destroy)

Creates a Map with arbitrary keys, 11 buckets, copy as its key copy function, cmp as its key comparison function, hash as its hash function, key_destroy as its key destructor and value_destroy as its item destructor. On success, returns the new map. On error, returns NULL. The argument to copy is the key to be copied. It returns the copy. The arguments to cmp are two keys to be compared. It returns < 0 if the first compares less than the second, 0 if they compare equal and > 0 if the first compares greater than the second. The arguments to hash are a size_t specifying the number of buckets and a const void * specifying the key to hash. It must return a size_t between zero and the table size - 1.

Map *map_create_generic_sized(size_t size, map_copy_t *copy, map_cmp_t *cmp, map_hash_t *hash, map_release_t *key_destroy, map_release_t *value_destroy)

Creates a Map with arbitrary keys, approximately size buckets, copy as its key copy function, cmp as its key comparison function, hash as its hash function, key_destroy as its key destructor and value_destroy as its item destructor. The actual size will be the first prime greater than or equal to size in a built in sequence of primes between 11 and 26,214,401 that double at each step. On success, returns the new map. On error, returns NULL. The argument to copy is the key to be copied. It returns the copy. The arguments to cmp are two keys to be compared. It returns < 0 if the first compares less than the second, 0 if they compare equal and > 0 if the first compares greater than the second. The arguments to hash are a size_t specifying the number of buckets and a const void * specifying the key to hash. It must return a size_t between zero and the table size - 1.

Map *map_create_generic_locked(Locker *locker, map_copy_t *copy, map_cmp_t *cmp, map_hash_t *hash, map_release_t *key_destroy, map_release_t *value_destroy)

Creates a Map with arbitrary keys, 11 buckets, copy as its key copy function, cmp as its key comparison function, hash as its hash function, key_destroy as its key destructor and value_destroy as its item destructor. Multiple threads accessing this map will be synchronised by locker. On success, returns the new map. On error, returns NULL. The argument to copy is the key to be copied. It returns the copy. The arguments to cmp are two keys to be compared. It returns < 0 if the first compares less than the second, 0 if they compare equal and > 0 if the first compares greater than the second. The arguments to hash are a size_t specifying the number of buckets and a const void * specifying the key to hash. It must return a size_t between zero and the table size - 1.

Map *map_create_generic_locked_sized(Locker *locker, size_t size, map_copy_t *copy, map_cmp_t *cmp, map_hash_t *hash, map_release_t *key_destroy, map_release_t *value_destroy)

Creates a Map with arbitrary keys, approximately size buckets, copy as its key copy function, cmp as its key comparison function, hash as its hash function, key_destroy as its key destructor and value_destroy as its item destructor. The actual size will be the first prime greater than or equal to size in a built in sequence of primes between 11 and 26,214,401 that double at each step. Multiple threads accessing this map will be synchronised by locker. On success, returns the new map. On error, returns NULL. The argument to copy is the key to be copied. It returns the copy. The arguments to cmp are two keys to be compared. It returns < 0 if the first compares less than the second, 0 if they compare equal and > 0 if the first compares greater than the second. The arguments to hash are a size_t specifying the number of buckets and a const void * specifying the key to hash. It must return a size_t between zero and the table size - 1.

void map_release(Map *map)

Releases (deallocates) map, destroying its items if necessary.

void *map_destroy(Map **map)

Destroys (deallocates and sets to NULL) *map. Returns NULL. Note: maps shared by multiple threads must not be destroyed until after the threads have finished with it.

int map_own(Map *map, map_release_t *key_destroy, map_release_t *value_destroy)

Causes map to take ownership of its items. The keys will be destroyed using key_destroy. The items will be destroyed using value_destroy when their mappings are removed from map or when map is destroyed. On success, returns 0. On error, returns -1.

map_release_t *map_disown(Map *map)

Causes map to relinquish ownership of its items. The items will not be destroyed when their mappings are removed from map or when map is destroyed. On success, returns the previous destroy function, if any. On error, returns NULL.

item map_add(Map *map, const void *key, void *value)

Adds the (key, value) mapping to map if key is not already present. Note that key is copied but value is not. On success, returns 0. On error, returns -1.

item map_put(Map *map, const void *key, void *value)

Adds the (key, value) mapping to map, replacing any existing (key, value) mapping. Note that key is copied but value is not. On success, returns 0. On error, returns -1.

int map_insert(Map *map, const void *key, void *value, int replace)

Adds the (key, value) mapping to map, replacing any existing (key, value) mapping if replace is non-zero. Note that key is copied but value is not. On success, returns 0. on error, or if key is already present and replace is zero, returns -1.

int map_remove(Map *map, const void *key)

Removes (key, value) mapping from map if it is present. If map was created with a destroy function, then the value will be destroyed. On success, returns 0. On error, returns -1.

void *map_get(const Map *map, const void *key)

Returns the value associated with key in map, or NULL if there is none.

Mapper *mapper_create(Map *map)

Creates an iterator for map. On success, returns the iterator. On error, returns NULL.

void mapper_release(Mapper *mapper)

Releases (deallocates) mapper.

void *mapper_destroy(Mapper **mapper)

Destroys (deallocates and sets to NULL) *mapper. Returns NULL. Note: lists shared by multiple threads must not be destroyed until after the threads have finished with it.

int mapper_has_next(Mapper *mapper)

Returns whether or not there is another item in the map that mapper is iterating over.

void *mapper_next(Mapper *Mapper)

Returns the next item in the map that mapper is iterating over.

const Mapping *mapper_next_mapping(Mapper *Mapper)

Returns the next mapping (key, value pair) in the map over which mapper is iterating.

void mapper_remove(Mapper *mapper)

Removes the current item in the iteration mapper. The next item in the iteration is the item following the removed item, if any. This must be called after mapper_next().

int map_has_next(Map *map)

Returns whether or not there is another item in map. The first time this is called, a new, internal Mapper will be created (Note: there can be only one). When there are no more items, returns zero and destroys the internal iterator. When it returns a non-zero value, use map_next() to retrieve the next item.

Note: If an iteration using an internal iterator terminates before the end of the map, it is the caller's responsibility to call map_break(). Failure to do so will cause the internal iterator to leak which will leave the map write locked. The next use of map_has_next() for the same map will not do what you expect. In fact, the next attempt to use the map would deadlock the prgoram.

void map_break(Map *map)

Unlocks map and destroys its internal iterator. Must be used only when an iteration using an internal iterator has terminated before reaching the end of map.

void *map_next(Map *map)

Returns the next item in map using it's internal iterator. On error, returns NULL.

const Mapping *map_next_mapping(Map *map)

Returns the next mapping (key, value pair) in map using it's internal iterator.

void map_remove_current(Map *map)

Removes the current item in map using it's internal iterator. The next item in the iteration is the item following the removed item, if any. This must be called after map_next().

const void *mapping_key(const Mapping *mapping)

Returns the key in mapping. On error, returns NULL.

const void *mapping_value(const Mapping *mapping)

Returns the value in mapping. On error, returns NULL.

List *map_keys(Map *map)

Creates and returns a list of all of the keys contained in map. The caller is required to destroy the list. Also, the keys in the list are owned by map so the list returned must not outlive the map. On error, returns NULL.

List *map_values(Map *map)

Creates and returns a list of all of the values contained in map. The caller is required to destroy the list. Also, the values in the list are not owned by the list so it must not outlive map if map owns them. On error, returns NULL.

void map_apply(const Map *map, map_action_t *action, void *data)

Invokes action for each of map's items. The arguments passed to action are the key, the item and data.

size_t map_size(const Map *map)

Returns the number of mappings in map. On error, returns 0.


MT-Level

MT-Disciplined

By default, Maps are not MT-Safe because most programs are single threaded and synchronisation doesn't come for free. Even in multi threaded programs, not all Maps are necessarily shared between multiple threads.

When a Map is shared between multiple threads which need to be synchronised, the method of synchronisation must be carefully selected by the client code. There are tradeoffs between concurrency and overhead. The greater the concurrency, the greater the overhead. More locks give greater concurrency but have greater overhead. Readers/Writer locks can give greater concurrency than Mutex locks but have greater overhead. One lock for each Map may be required, or one lock for all (or a set of) Maps may be more appropriate.

Generally, the best synchronisation strategy for a given application can only be determined by testing/benchmarking the written application. It is important to be able to experiment with the synchronisation strategy at this stage of development without pain.

To facilitate this, Maps can be created with map_create_locked() which takes a Locker argument. The Locker specifies a lock and a set of functions for manipulating the lock. Each Map can have it's own lock by creating a separate Locker for each Map. Multiple Maps can share the same lock by sharing the same Locker. Only the application developer can determine what is appropriate for each application on a case by case basis.

MT-Disciplined means that the application developer has a mechanism for specifying the synchronisation requirements to be applied to library code.


BUGS

NULL can't be a key. Neither can zero when using integers as keys.

If you use an internal iterator in a loop that terminates before the end of the map, and fail to call map_break(), the internal iterator will leak and the map will remain write locked, deadlocking the program the time you attempt to access the map.

Uses malloc(3). Need to decouple memory type and allocation strategy from this code.


SEE ALSO

libslack(3), list(3), mem(3), thread(3)


AUTHOR

20010215 raf <raf@raf.org>