mirror of
https://github.com/bkthomps/Containers.git
synced 2025-11-16 12:34:47 +00:00
Add documentation (#107)
This commit is contained in:
29
README.md
29
README.md
@@ -5,20 +5,35 @@
|
|||||||
[](https://github.com/bkthomps/Containers/blob/master/LICENSE)
|
[](https://github.com/bkthomps/Containers/blob/master/LICENSE)
|
||||||
|
|
||||||
# Containers
|
# Containers
|
||||||
This library provides various containers. Each container has utility functions to manipulate the data it holds. This is an abstraction as to not have to manually manage and reallocate memory.
|
This library provides various containers. Each container has utility functions
|
||||||
|
to manipulate the data it holds. This is an abstraction as to not have to
|
||||||
|
manually manage and reallocate memory.
|
||||||
|
|
||||||
Inspired by the C++ standard library; however, implemented using C with different function interfaces as the C++ standard library but with the same container names.
|
Inspired by the C++ standard library; however, implemented using C with
|
||||||
|
different function interfaces as the C++ standard library but with the same
|
||||||
|
container names.
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
It is possible to compile this library as either static `.a` or dynamic `.so`:
|
It is possible to compile this library as either static `.a` or dynamic `.so`:
|
||||||
1. A static library is slightly faster than a dynamic one, however, if the library is modified, the entire project codebase which uses it will need to be recompiled.
|
1. A static library is slightly faster than a dynamic one, however, if the
|
||||||
2. A dynamic library can be changed without recompiling the codebase, assuming no function definitions have changed.
|
library is modified, the entire project codebase which uses it will need to be
|
||||||
|
recompiled.
|
||||||
|
2. A dynamic library can be changed without recompiling the codebase, assuming
|
||||||
|
no function definitions have changed.
|
||||||
|
|
||||||
The installation process is as follows:
|
The installation process is as follows:
|
||||||
1. Clone this repository and navigate to it.
|
1. Clone this repository and navigate to it.
|
||||||
2. Run `make static_clang`/`make static_gcc` or `make dynamic_clang`/`make dynamic_gcc` for either a static or dynamic library.
|
2. Run `make static_clang`/`make static_gcc` or
|
||||||
3. Then, you can copy-paste `containers.h` and `containers.a`/`containers.so` into your project to include the containers.
|
`make dynamic_clang`/`make dynamic_gcc` for either a static or dynamic library.
|
||||||
4. Finally, you remember to link the library by including `containers.a -ldl`/`containers.so -ldl` as an argument.
|
3. Then, you can copy-paste `containers.h` and `containers.a`/`containers.so`
|
||||||
|
into your project to include the containers.
|
||||||
|
4. Finally, you remember to link the library by including
|
||||||
|
`containers.a -ldl`/`containers.so -ldl` as an argument.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
For high-level documentation and usage, visit the
|
||||||
|
[documentation](documentation.md) page. For in-depth documentation, visit the
|
||||||
|
[code docs](https://codedocs.xyz/bkthomps/Containers/) page.
|
||||||
|
|
||||||
## Container Types
|
## Container Types
|
||||||
The container types that this library contains are described below.
|
The container types that this library contains are described below.
|
||||||
|
|||||||
126
documentation.md
Normal file
126
documentation.md
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# Documentation
|
||||||
|
For setup and compilation instructions visit the [readme](README.md). For
|
||||||
|
in-depth documentation, visit the
|
||||||
|
[code docs](https://codedocs.xyz/bkthomps/Containers/) page.
|
||||||
|
|
||||||
|
## General Overview
|
||||||
|
Each container has an initialization function which returns the container
|
||||||
|
object. For a deque, this would be `deque_init()` which returns a `deque`. The
|
||||||
|
returned object is a pointer to an internal struct which contains data and
|
||||||
|
book keeping information. However, this is abstracted away to reduce mistakes,
|
||||||
|
and since it is not stored in the most easy manner. More information about the
|
||||||
|
initialization type of function is presented below in its own section.
|
||||||
|
|
||||||
|
Once this object is initialized, it is possible to manipulate it using the
|
||||||
|
provided functions, which have in-depth documentation available. Each container
|
||||||
|
has adding and retrieval type functions, and each type of container has its own
|
||||||
|
specific set of function interfaces, which are explained in-depth at the
|
||||||
|
function level in the code docs link above. More high-level information will be
|
||||||
|
explained in its own section below.
|
||||||
|
|
||||||
|
Finally, each container will have to be destroyed to free the memory associated
|
||||||
|
with it.
|
||||||
|
|
||||||
|
# Container Initialization
|
||||||
|
When creating a container, first you must decide what type of data you wish to
|
||||||
|
store in it (or types for the case of a map). Then, you must either decide if
|
||||||
|
you wish to store a copy of the data in the container, or a pointer to it. The
|
||||||
|
benefit of a copy is that you don't have to manage the memory, and it is easier
|
||||||
|
to reason. However, it might only be reasonable to store copies if the data type
|
||||||
|
is relatively cheap to copy. Using pointers, you can either store references to
|
||||||
|
automatic variables (make sure the lifetime stays valid while the data is
|
||||||
|
stored), or dynamically-allocated variables (make sure to free at some point).
|
||||||
|
Either way, you must pass in a pointer of what you wish to store, be it a
|
||||||
|
pointer to a value, or a pointer to a pointer. Keep in mind that for some
|
||||||
|
containers, changing data which is stored will cause undefined behavior. Fret
|
||||||
|
not, as all containers document their potential undefined behavior in the
|
||||||
|
function documentation comments.
|
||||||
|
|
||||||
|
Going back to the initialization function and ways we can initialize containers
|
||||||
|
and and store data, we can use an example of a `deque`:
|
||||||
|
```
|
||||||
|
deque a = deque_init(sizeof(int)); /* OK: cheap to copy */
|
||||||
|
deque b = deque_init(sizeof(struct expensive_data)); /* Bad: valid, but expensive to copy */
|
||||||
|
deque c = deque_init(sizeof(struct expensive_data *)); /* OK: pointer to expensive data, but be careful about memory management */
|
||||||
|
```
|
||||||
|
|
||||||
|
Also, if the arguments which you passed in to the initialization function are
|
||||||
|
invalid, or the system is out of memory, the initialization function may return
|
||||||
|
NULL. Therefore, it is good to check for a return of NULL from the
|
||||||
|
initialization functions.
|
||||||
|
|
||||||
|
# Container Operations
|
||||||
|
Next, the two basic container manipulation functions of containers are to add
|
||||||
|
and remove elements. To add elements, pass a pointer of the element you wish to
|
||||||
|
copy to the add function. To remove an element, pass a pointer to a variable
|
||||||
|
which can store the size of element that is stored in the container.
|
||||||
|
|
||||||
|
Using the `deque` example, adding and removal can be done this way (if storing
|
||||||
|
int):
|
||||||
|
```
|
||||||
|
deque d = deque_init(sizeof(int));
|
||||||
|
...
|
||||||
|
int add = 5;
|
||||||
|
int rc = deque_push_back(d, &add); /* 5 has been added to the back of the deque */
|
||||||
|
...
|
||||||
|
int retrieve;
|
||||||
|
int rc = deque_pop_back(&retrieve, d); /* retrieve now is equal to 5 */
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Functions can fail for various reasons, such as the provided index argument
|
||||||
|
being out of bounds, or the system running out of memory. The in-depth
|
||||||
|
documentation linked above provides the exhaustive list of return codes for each
|
||||||
|
function, which are present in the `errno.h` header file. For example, an
|
||||||
|
invalid argument would return `-EINVAL`, and on success 0 would be returned.
|
||||||
|
|
||||||
|
# Comparators and Hash Functions
|
||||||
|
The associative containers and the priority queue require the user to initialize
|
||||||
|
the container with a comparator, and the unordered associative containers also
|
||||||
|
require a hash function to be passed in. State should not be modified in
|
||||||
|
comparators or in hash functions, or else it would lead to undefined behavior.
|
||||||
|
|
||||||
|
When a comparator function is called, two arguments are passed in, being two
|
||||||
|
elements to compare. The comparator must return 0 is they are equal, a negative
|
||||||
|
value if the first is less than the second, and a positive value is the first is
|
||||||
|
greater than the second. To be valid, a comparator must obey the following
|
||||||
|
rules:
|
||||||
|
1. Reflexive: `arg_1 == arg_1` and `arg_2 == arg_2` must always be true
|
||||||
|
2. Symmetric: if `arg_1 == arg_2` is true, then `arg_2 == arg_1` is true
|
||||||
|
3. Transitive: if the comparator is called twice with three distinct objects,
|
||||||
|
the first time being `(arg_1, arg_2)` and the second `(arg_2, arg_3)`, then if
|
||||||
|
`arg_1 == arg_2` and `arg_2 == arg_3` then `arg_1 == arg_3` must be true
|
||||||
|
4. Consistent: the truth of `arg_1 == arg_2` shall never change over time if
|
||||||
|
both arguments do not change
|
||||||
|
|
||||||
|
When a hash function is called, it is provided one argument, and must hash its
|
||||||
|
attributes. A hash is a mapping from the object to a number. Two objects which
|
||||||
|
are equal must always result in the same hash being produced. The inverse is not
|
||||||
|
required, but should be sufficiently improbable (meaning, two distinct objects
|
||||||
|
may produce the same hash, but it should be very rare).
|
||||||
|
|
||||||
|
Using unordered set as an example (and storing int), the comparator and hash
|
||||||
|
function can be passed in as follows when initializing:
|
||||||
|
```
|
||||||
|
unordered_set_init(sizeof(int), hash_int, compare_int)
|
||||||
|
```
|
||||||
|
|
||||||
|
A comparator can be as follows:
|
||||||
|
```
|
||||||
|
static int compare_int(const void *const one, const void *const two)
|
||||||
|
{
|
||||||
|
const int a = *(int *) one;
|
||||||
|
const int b = *(int *) two;
|
||||||
|
return a - b;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And a hash function can be as follows:
|
||||||
|
```
|
||||||
|
static unsigned long hash_int(const void *const key)
|
||||||
|
{
|
||||||
|
unsigned long hash = 17;
|
||||||
|
hash = 31 * hash + *(int *) key;
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user