|
| 1 | +# array_alg.h |
| 2 | + |
| 3 | +Unintrusive algorithms for C arrays OR a C implementation of \<algorithm> from C++ |
| 4 | + |
| 5 | +## Pitch |
| 6 | + |
| 7 | +The C++ STL is one of the most complete and reusable algorithm libraries available. |
| 8 | +This single header file brings 80% of that functionality to C99 in a non-intrusive way. |
| 9 | +There are no new data structures. Just include the library and call functions on C arrays. |
| 10 | + |
| 11 | +Features: |
| 12 | + |
| 13 | +- Sets (intersection, union, subset, etc) |
| 14 | +- Heaps (priority queues) |
| 15 | +- Binary search (lower bound, upper bound, etc) |
| 16 | +- Sorts (insertion sort, quick sort, merge/stable sort, heap sort, partial sort, etc) |
| 17 | +- Partitioning (partition, unique, etc) |
| 18 | +- Predicates (all of, any of, none of, etc) |
| 19 | +- Uniform random sampling and shuffling |
| 20 | + |
| 21 | +## Usage |
| 22 | + |
| 23 | +This library uses the preprocessor to implement generic functions. |
| 24 | +Each time you include the library, you will need to define the array element type and a function prefix: |
| 25 | + |
| 26 | + #define ARRAY_ALG_TYPE int |
| 27 | + #define ARRAY_ALG_PREFIX intv_ |
| 28 | + #include "array_alg.h" |
| 29 | + |
| 30 | +The above will only generate the declarations. |
| 31 | +In at least one C file, you will also need to generate implementations. |
| 32 | +To generate implementations, define `ARRAY_ALG_IMPLEMENTATION` in a C file and include the library: |
| 33 | + |
| 34 | + #define ARRAY_ALG_TYPE int |
| 35 | + #define ARRAY_ALG_PREFIX intv_ |
| 36 | + #define ARRAY_ALG_IMPLEMENTATION |
| 37 | + #include "array_alg.h" |
| 38 | + |
| 39 | +Alternatively, you can include the algorithms as static functions to avoid the need for separate implementations: |
| 40 | + |
| 41 | + #define ARRAY_ALG_STATIC |
| 42 | + #define ARRAY_ALG_TYPE int |
| 43 | + #define ARRAY_ALG_PREFIX intv_ |
| 44 | + #define ARRAY_ALG_IMPLEMENTATION |
| 45 | + #include "array_alg.h" |
| 46 | + |
| 47 | +Repeat this process for each array type you want to use. |
| 48 | + |
| 49 | +## Examples |
| 50 | + |
| 51 | +Remove duplicate entries: |
| 52 | + |
| 53 | + #define ARRAY_ALG_TYPE int |
| 54 | + #define ARRAY_ALG_PREFIX intv_ |
| 55 | + #include "array_alg.h" |
| 56 | + |
| 57 | + int compare_int(const int *a, const int *b, void *ctx) { |
| 58 | + return *a - *b; |
| 59 | + } |
| 60 | + |
| 61 | + ... |
| 62 | + |
| 63 | + int nums[100] = ...; |
| 64 | + intv_sort(nums, nums + 100, compare_int, NULL); |
| 65 | + int* end = intv_unique(nums, nums + 100, compare_int, NULL); |
| 66 | + |
| 67 | + |
| 68 | +## Design |
| 69 | + |
| 70 | +1. Iterators and Arrays |
| 71 | + |
| 72 | +The C++ STL is designed around the concept of iterators. |
| 73 | +With iterators, one algorithm can be reused not just for multiple types, but also for many data structures. |
| 74 | +This is an ingenious design. |
| 75 | +However, in practice, this capability is rarely needed. |
| 76 | +The vast majority of real world \<algorithm> invocations are on contiguous arrays/vectors. |
| 77 | + |
| 78 | +For those cases where you do have a fancy data structure (graphs, trees, etc), |
| 79 | +copy its contents to an array, perform the algorithm, and then copy the contents back. |
| 80 | +This will often help it perform better anyway! |
| 81 | + |
| 82 | +2. Bounds vs counted ranges |
| 83 | + |
| 84 | +STL algorithms typically operate on half-open ranges bounded by iterators [first, last). |
| 85 | +This convention is not used as often in C, but we think it offers some benefits. |
| 86 | +Internally, the functions can maintain less state by simply incrementing pointers |
| 87 | +rather than keeping track of pointers, indices, and counts. |
| 88 | + |
| 89 | +Operations also compose a little easier. |
| 90 | +When a pointer is returned to an element of interest, |
| 91 | +that same pointer can be used as an argument for another algorithm. |
| 92 | + |
| 93 | +3. What's left out |
| 94 | + |
| 95 | +Because it's a bit verbose to define a C closure (function pointer and context), some STL algorithms are less useful in C. |
| 96 | +If an algorithm can be written as a simple for loop with no additional state or control flow, this library doesn't implement it. |
| 97 | + |
| 98 | + transform -> for (int i = 0; i < n; ++i) out[i] = f(in[i]) |
| 99 | + fill -> for (int i = 0; i < n; ++i) out[i] = x; |
| 100 | + iota -> for (int i = 0; i < n; ++i) out[i] = i; |
| 101 | + generate -> for (int i = 0; i < n; ++i) out[i] = f(); |
| 102 | + |
| 103 | +The algorithms which rely on ordered types always require a comparison function. |
| 104 | +We do not include any variants that operate on the `==` operator, as operators cannot be overloaded in C. |
| 105 | + |
| 106 | +4. Generics vs `void*` |
| 107 | + |
| 108 | +Including a header multiple times with various `#define`s is a little cumbersome. |
| 109 | +However, we think it's a superior way to achieve C generics compared to the `void*` style used by `qsort` and `bsearch`. |
| 110 | +The preprocessor approach provides: |
| 111 | + |
| 112 | +- Better type safety and avoids verbose casting logic. |
| 113 | + |
| 114 | +- Better peformance (as `void*` functions are difficult to optimize). |
| 115 | + |
| 116 | + Note: The C compiler can only create one non-inlined version of each function. |
| 117 | + For example, it could not choose to use `int` instructions, even if it knew the type at compile time. |
| 118 | + With the single header approach you get a new instance of each function optimized for each application. |
0 commit comments