libslack(map) - map module
#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);
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-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.
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.
libslack(3), list(3), mem(3), thread(3)
20010215 raf <raf@raf.org>