diff --git a/README.md b/README.md index 1d8f19b..b87249a 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,18 @@ ## Datastructures, algorithms, machine-learning and utilities library in C++ A medium-sized modern header only library of selected datastructures, algorithms, machinelearning topics and utilities. -This -collection is built for educational purposes and for use in non-essential projects. All implementations are easy to read -and well documented. +This collection is built for educational purposes and for use in non-essential projects. + +**Documentation is provided in-header.** While I am not an expert in datastructures nor C++, I am still aiming for reference type implementations in terms of efficiency and interface. The general namespace is `cxstructs`. +*Note: Currently the old datastructures are modernized with focus on better includes for compile times and correctness.* +*Non-Stack datastructure might not be usable for now* + **1.** [Contents](#contents) **2.** [Usage Guide](#usage-guide) **3.** [Installation](#installation) @@ -19,12 +22,12 @@ The general namespace is `cxstructs`. ### Speed Comparison -*Relative to the fastest / with CXPoolAllocator* +*Relative to the fastest / with CXPoolAllocator (if applicable)* -| | vector | Stack | HashMap | HashSet | LinkedList | Queue | DeQueue | -|:----------------|:--------:|:--------:|:--------:|:-------:|:----------:|:--------:|:--------:| -| **std::** | *0.81* | *0.52* | *0.51* | *0.52* | *0.71* | *0.46* | *0.57* | -| **cxstructs::** | **1.00** | **1.00** | **1.00** | **1.0** | **1.00** | **1.00** | **1.00** | +| | vector | Stack | HashMap | StackHashMap | HashSet | LinkedList | Queue | DeQueue | +|:----------------|:--------:|:--------:|:--------:|:------------:|:-------:|:----------:|:--------:|:--------:| +| **std::** | *0.81* | *0.52* | *0.51* | *0.5* | *0.52* | *0.71* | *0.46* | *0.57* | +| **cxstructs::** | **1.00** | **1.00** | **1.00** | **1.00** | **1.0** | **1.00** | **1.00** | **1.00** | ### Features @@ -92,12 +95,14 @@ The general namespace is `cxstructs`. #### Utilities -- **cxtime**: *easily measure the time from `now()` to `printTime()`* -- **cxio**: *load_text,* - **cxassert**: *custom assertions with optional text* -- **cxmath**: *activation functions,distance function, next_power_of_2* +- **cxbits**: *bit operations on numbers for embedding and retrieving information* - **cxgraphics**: *simple native windowing and graphics output header* -- **cxbits**: *bit operations on numbers* +- **cxio**: *simple, readable and symmetric file io format* +- **cxmath**: *activation functions,distance functions, next_power_of_2, square root* +- **cxstring**: *operations on strings* +- **cxtime**: *simple time measurements with multiple time points and formats* +- **cxtips**: *collection of helpful resources and personal guidelines with examples* --- diff --git a/src/cxconfig.h b/src/cxconfig.h index 226dde8..eec6b7c 100644 --- a/src/cxconfig.h +++ b/src/cxconfig.h @@ -53,6 +53,7 @@ namespace cxtests {} #define CX_STACK_ABORT_IMPL() (void(0)) #endif + #ifndef CX_USE_INT typedef uint_fast32_t uint_32_cx; typedef uint_fast16_t uint_16_cx; diff --git a/src/cxstructs/PriorityQueue.h b/src/cxstructs/PriorityQueue.h index fe4a583..38c49f4 100644 --- a/src/cxstructs/PriorityQueue.h +++ b/src/cxstructs/PriorityQueue.h @@ -46,7 +46,7 @@ class PriorityQueue { uint_32_cx size_; Compare comp; - inline void resize() noexcept { + void resize() noexcept { len_ *= 2; T* n_arr = alloc.allocate(len_); @@ -60,7 +60,7 @@ class PriorityQueue { alloc.deallocate(arr_, size_); arr_ = n_arr; } - inline void shrink() noexcept { + void shrink() noexcept { auto old_len = len_; len_ = size_ * 1.5; @@ -77,7 +77,7 @@ class PriorityQueue { alloc.deallocate(arr_, old_len); arr_ = n_arr; } - inline void sift_up(uint_32_cx index) noexcept { + void sift_up(uint_32_cx index) noexcept { auto parent = (index - 1) / 2; while (index != 0 && !comp(arr_[index], arr_[parent])) { std::swap(arr_[index], arr_[parent]); @@ -85,7 +85,7 @@ class PriorityQueue { parent = (index - 1) / 2; } } - inline void sift_down(uint_32_cx index) noexcept { + void sift_down(uint_32_cx index) noexcept { auto left = 2 * index + 1; auto right = 2 * index + 2; while ((left < size_ && comp(arr_[index], arr_[left])) || @@ -97,7 +97,7 @@ class PriorityQueue { right = 2 * index + 2; } } - inline void heapify() noexcept { + void heapify() noexcept { for (uint_fast32_t i = len_ - 1; i > -1; i--) { sift_down(i); } @@ -108,7 +108,7 @@ class PriorityQueue { * Per default is a min heap. Pass std::greater<> as comparator to bits_get a max-heap * @param len initial length and expansion factor */ - inline explicit PriorityQueue(uint_32_cx len = 32) + explicit PriorityQueue(uint_32_cx len = 32) : arr_(alloc.allocate(len)), len_(len), size_(0) {} /** * @brief Constructor that initializes the priority queue with an existing array. @@ -116,7 +116,7 @@ class PriorityQueue { * @param arr Pointer to the array to copy elements from. * @param len The number of elements in the array. */ - inline explicit PriorityQueue(T*&& arr, uint_32_cx len) : arr_(arr), len_(len), size_(len) { + explicit PriorityQueue(T*&& arr, uint_32_cx len) : arr_(arr), len_(len), size_(len) { heapify(); arr = nullptr; //avoid double deletion } @@ -126,7 +126,7 @@ class PriorityQueue { * @param arr Pointer to the array to copy elements from. * @param len The number of elements in the array. */ - inline explicit PriorityQueue(const T* arr, uint_32_cx len) + explicit PriorityQueue(const T* arr, uint_32_cx len) : arr_(alloc.allocate(len)), len_(len), size_(len) { if (std::is_trivial_v) { std::copy(arr, arr + len, arr_); diff --git a/src/cxstructs/StackVector.h b/src/cxstructs/StackVector.h index 13ef35b..a3a8320 100644 --- a/src/cxstructs/StackVector.h +++ b/src/cxstructs/StackVector.h @@ -184,7 +184,7 @@ class StackVector { #ifdef CX_INCLUDE_TESTS static void TEST() { - StackArray arr; + StackVector arr; for (int i = 0; i < 10; i++) { arr.push_back(100); } diff --git a/src/cxstructs/mat.h b/src/cxstructs/mat.h index 16558a0..75ac0fd 100644 --- a/src/cxstructs/mat.h +++ b/src/cxstructs/mat.h @@ -22,8 +22,8 @@ #ifndef CXSTRUCTS_SRC_CXSTRUCTS_MAT_H_ #define CXSTRUCTS_SRC_CXSTRUCTS_MAT_H_ -#include #include "../cxconfig.h" +#include #include "vec.h" namespace cxstructs { @@ -449,7 +449,7 @@ class mat { /**Prints out the matrix * @param header optional header */ - void print(const std::string& header = "") const { + void print(const char* name = "") const { if (!header.empty()) { std::cout << header << std::endl; for (uint_32_cx i = 0; i < n_rows_; i++) { diff --git a/src/cxutil/cxbits.h b/src/cxutil/cxbits.h index b552973..34ca7bf 100644 --- a/src/cxutil/cxbits.h +++ b/src/cxutil/cxbits.h @@ -210,7 +210,8 @@ static void TEST_BITS() { "Concatenation of uint16_t failed."); CX_ASSERT(bits_concat(static_cast(0x1), static_cast(0x2)) == 0x200000001, "Concatenation of uint32_t failed."); - uint16_t num1 = 1, num2 = 2; + uint16_t num1 = 1; + uint16_t num2 = 2; CX_ASSERT(bits_concat(num1, num2) == 0x20001, "Concatenation of uint16_t with different values failed."); diff --git a/src/cxutil/cxio.h b/src/cxutil/cxio.h index 1d0426a..aaf5a9b 100644 --- a/src/cxutil/cxio.h +++ b/src/cxutil/cxio.h @@ -21,36 +21,246 @@ #ifndef CXSTRUCTS_SRC_CXIO_H_ #define CXSTRUCTS_SRC_CXIO_H_ -#include -#include -#include -#include +#include "../cxconfig.h" +#include "cxstring.h" +//Simple, readable and fast symmetric serialization structure with loading and saving +//Each line is a concatenates list of values and delimiter '|' +//13|45|23|56| +//These lines can then be parsed by load_property + +//Using the CXX23 std::print() is about 10% slower namespace cxstructs { +//inline static constexpr char DELIMITER = '|'; //Not used yet -/** - * @brief Loads the entire contents of a text file into a string. - * - * @param filePath The path to the text file to be loaded. - * @param contents A reference to a string where the contents of the file will be stored. - * @return Returns true if the file was successfully loaded, false otherwise. - */ -inline bool load_txt(const std::string& filePath, std::string& contents) { +//-----------SAVING-----------// +// Writes a new line to file +inline void io_save_newline(FILE* file) { + fputc('\n', file); +} +// Writes a string value to file +inline void io_save(FILE* file, const char* value) { + fprintf(file, "%s|", value); +} +// Writes an integer or enum property to the file +inline void io_save(FILE* file, int value) { + fprintf(file, "%d|", value); +} +// Writes a float property to the file +inline void io_save(FILE* file, float value) { + fprintf(file, "%.3f|", value); +} - std::ifstream file(filePath, std::ios::in); +//-----------LOADING-----------// +//Attempts to skip a new line until the next delimiter +inline void io_load_newline(FILE* file) { + char ch; + while (fread(&ch, 1, 1, file) == 1 && ch != '|') { + if (ch == '\n') return; + } +} +//include to use or define _STRING_ +#ifdef _STRING_ +inline void io_load(FILE* file, std::string& s, int reserve_amount = 50) { + s.reserve(reserve_amount); + char ch; + while (fread(&ch, 1, 1, file) == 1 && ch != '|') { + s.push_back(ch); + } +} +#endif +// Load a string property into a user-supplied buffer +inline void io_load(FILE* file, char* buffer, size_t buffer_size) { + size_t count = 0; + char ch; + while (count < buffer_size - 1 && fread(&ch, 1, 1, file) == 1 && ch != '|') { + buffer[count++] = ch; + } + buffer[count] = '\0'; +} +// Directly load an integer property from the file +inline void io_load(FILE* file, int& i) { + fscanf_s(file, "%d|", &i); +} +// Directly load a float property from the file +inline void io_load(FILE* file, float& f) { + fscanf_s(file, "%f|", &f); +} + +} // namespace cxstructs - if (!file.is_open()) { - std::cerr << "Could not open the file: " << filePath << std::endl; - return false; +#ifdef CX_INCLUDE_TESTS +#include +namespace cxtests { +using namespace cxstructs; +using namespace std::chrono; +static void benchMark() { + FILE* file; + const char* filename = "hello.txt"; + constexpr int num = 100; + int val = 5; + auto start_write = high_resolution_clock::now(); + fopen_s(&file, filename, "w"); + if (file != nullptr) { + for (int i = 0; i < num; i++) { + for (int j = 0; j < num; j++) { + io_save(file, j); + } + io_save_newline(file); + } + fclose(file); } + auto end_write = high_resolution_clock::now(); + auto start_read = high_resolution_clock::now(); - std::stringstream buffer; - buffer << file.rdbuf(); + fopen_s(&file, filename, "r"); + if (file != nullptr) { + for (int i = 0; i < num; i++) { + for (int j = 0; j < num; j++) { + io_load(file, val); + } + } + fclose(file); + } + auto end_read = high_resolution_clock::now(); + auto duration_write = duration_cast(end_write - start_write).count(); + auto duration_read = duration_cast(end_read - start_read).count(); + printf("Write time: %lld ms\n", duration_write); + printf("Read time: %lld ms\n", duration_read); +} +void test_save_load_string() { + const char* test_filename = "test_string.txt"; + const char* original_string = "Hello, world!"; + char buffer[256]; - contents = std::move(buffer.str()); + // Save + FILE* file = std::fopen(test_filename, "w"); + cxstructs::io_save(file, original_string); + cxstructs::io_save_newline(file); + std::fclose(file); - return true; + // Load + file = std::fopen(test_filename, "r"); + cxstructs::io_load(file, buffer, sizeof(buffer)); + std::fclose(file); + + // Assert + CX_ASSERT(std::strcmp(original_string, buffer) == 0, "String save/load failed"); } +void test_save_load_int() { + const char* test_filename = "test_int.txt"; + const int original_int = 42; + int loaded_int; -} // namespace cxstructs + // Save + FILE* file = std::fopen(test_filename, "w"); + cxstructs::io_save(file, original_int); + cxstructs::io_save_newline(file); + std::fclose(file); + + // Load + file = std::fopen(test_filename, "r"); + cxstructs::io_load(file, loaded_int); + std::fclose(file); + + // Assert + CX_ASSERT(original_int == loaded_int, "Int save/load failed"); +} +void test_save_load_float() { + const char* test_filename = "test_float.txt"; + constexpr float original_float = 3.141; + float loaded_float; + + // Save + FILE* file; + fopen_s(&file, test_filename, "w"); + cxstructs::io_save(file, original_float); + cxstructs::io_save_newline(file); + std::fclose(file); + + // Load + fopen_s(&file, test_filename, "r"); + cxstructs::io_load(file, loaded_float); + std::fclose(file); + + // Assert + CX_ASSERT(original_float == loaded_float, "Float save/load failed"); +} +void delete_test_files() { + // List of test files to delete + const char* files[] = {"test_string.txt", "test_int.txt", "test_float.txt", "test_complex.txt", + "hello.txt"}; + + // Iterate over the array and delete each file + for (const char* filename : files) { + if (std::remove(filename) != 0) { + perror("Error deleting file"); + } else { + printf("%s deleted successfully.\n", filename); + } + } +} +void test_complex_save_load() { + const char* test_filename = "test_complex.txt"; + const char* original_str1 = "TestString1"; + const int original_int = 123; + const float original_float = 456.789f; + const char* original_str2 = "TestString2"; + + char buffer_str1[256]; + int loaded_int = -1; + float loaded_float = -1; + std::string buffer_str2; + std::string hello; + std::string bye; + std::string hello2; + + // Save complex data + FILE* file; + fopen_s(&file, test_filename, "w"); + if (file) { + cxstructs::io_save(file, original_str1); + cxstructs::io_save(file, original_int); + cxstructs::io_save(file, original_float); + cxstructs::io_save(file, original_str2); + cxstructs::io_save_newline(file); + io_save(file, "hello"); + io_save(file, "bye"); + io_save(file, "hello"); + std::fclose(file); + } + + // Load complex data + file = std::fopen(test_filename, "r"); + if (file) { + cxstructs::io_load(file, buffer_str1, sizeof(buffer_str1)); + cxstructs::io_load(file, loaded_int); + cxstructs::io_load(file, loaded_float); + cxstructs::io_load(file, buffer_str2); + io_load_newline(file); + io_load(file, hello); + io_load(file, bye); + io_load(file, hello2); + std::fclose(file); + } + + // Assert all loaded data matches original + CX_ASSERT(std::strcmp(original_str1, buffer_str1) == 0, "String1 save/load failed"); + CX_ASSERT(original_int == loaded_int, "Int save/load failed"); + CX_ASSERT(std::fabs(original_float - loaded_float) < 0.001, + "Float save/load failed"); // Allow for slight floating-point inaccuracies + CX_ASSERT(std::strcmp(original_str2, buffer_str2.c_str()) == 0, "String2 save/load failed"); + CX_ASSERT(hello == hello2, ""); + CX_ASSERT(bye == "bye", ""); +} +static void TEST_IO() { + benchMark(); + test_save_load_float(); + test_save_load_int(); + test_save_load_string(); + test_complex_save_load(); + delete_test_files(); +} +} // namespace cxtests +#endif #endif //CXSTRUCTS_SRC_CXIO_H_ diff --git a/src/cxutil/cxmath.h b/src/cxutil/cxmath.h index 6074348..f6fe51e 100644 --- a/src/cxutil/cxmath.h +++ b/src/cxutil/cxmath.h @@ -21,8 +21,8 @@ #ifndef CXSTRUCTS_SRC_CXUTIL_MATH_H_ #define CXSTRUCTS_SRC_CXUTIL_MATH_H_ -#include #include "../cxconfig.h" +#include namespace cxstructs { // for compatibility | apparently this is only in c++ through std::numbers which is CX20 and not on all compilers equal diff --git a/src/cxutil/cxstring.h b/src/cxutil/cxstring.h index 4f6d47c..e74ba97 100644 --- a/src/cxutil/cxstring.h +++ b/src/cxutil/cxstring.h @@ -17,7 +17,7 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#define FINISHED +#define CX_FINISHED #ifndef CXSTRUCTS_SRC_CXUTIL_CXSTRING_H_ #define CXSTRUCTS_SRC_CXUTIL_CXSTRING_H_ @@ -27,6 +27,7 @@ namespace cxstructs { + template inline auto str_parse_token(char** context, char delim) { const char* start = *context; @@ -51,8 +52,6 @@ inline auto str_parse_token(char** context, char delim) { return result; } } - - constexpr auto fnv1a_32(char const* s) noexcept -> uint32_t { uint32_t hash = 2166136261U; while (*s != 0) { @@ -61,7 +60,6 @@ constexpr auto fnv1a_32(char const* s) noexcept -> uint32_t { } return hash; } - struct Fnv1aHash { auto operator()(const char* s) const noexcept -> std::size_t { uint32_t hash = 2166136261U; @@ -72,7 +70,6 @@ struct Fnv1aHash { return hash; } }; - #ifdef CX_INCLUDE_TESTS static void TEST_STRING() { char buff[64] = "ParseME|0.3|55|-55|"; diff --git a/src/cxutil/cxtime.h b/src/cxutil/cxtime.h index 543e63d..8643fbd 100644 --- a/src/cxutil/cxtime.h +++ b/src/cxutil/cxtime.h @@ -24,75 +24,65 @@ #include "../cxconfig.h" #include #include +#include namespace cxstructs { using namespace std; //std:: makes this code unreadable - -inline chrono::time_point activeTimeStamp; -inline chrono::time_point checkpoints[3]; +inline static constexpr int MAX_CHECKPOINTS = 4; +inline chrono::time_point checkpoints[MAX_CHECKPOINTS]; /** * Sets the activeTimeStamp or alternatively the time of a checkpoint * @param checkpoint (optional, max=2) the checkpoint to set the current time */ -inline void now(int checkpoint = -1) { - if (checkpoint >= 0 && checkpoint < 3) { - checkpoints[checkpoint] = std::chrono::high_resolution_clock::now(); - } else { - activeTimeStamp = std::chrono::high_resolution_clock::now(); - } +inline void now(int checkpoint = 0) { + checkpoints[checkpoint] = std::chrono::high_resolution_clock::now(); } template -const char* get_duration_unit(); +auto get_duration_unit() -> const char*; template <> -inline const char* get_duration_unit() { +inline auto get_duration_unit() -> const char* { return "seconds"; } template <> -inline const char* get_duration_unit() { +inline auto get_duration_unit() -> const char* { return "milliseconds"; } template <> -inline const char* get_duration_unit() { +inline auto get_duration_unit() -> const char* { return "microseconds"; } template <> -inline const char* get_duration_unit() { +inline auto get_duration_unit() -> const char* { return "nanoseconds"; } template <> -inline const char* get_duration_unit>() { +inline auto get_duration_unit>() -> const char* { return "seconds"; } template > -inline void printTime(const char* prefix = nullptr, int checkpoint = -1) { - std::chrono::time_point start_time; - if (checkpoint >= 0 && checkpoint < 3) { - start_time = checkpoints[checkpoint]; - } else { - start_time = activeTimeStamp; - } - - const auto diff = std::chrono::high_resolution_clock::now() - start_time; +inline void printTime(const char* prefix = nullptr, int checkpoint = 0) { + const auto diff = std::chrono::high_resolution_clock::now() - checkpoints[checkpoint]; const auto diffInDesiredUnits = std::chrono::duration_cast(diff); if (prefix) { - printf("%s ", prefix); +#if _HAS_CXX23 + std::print("{} ", prefix); +#else + printf("%s", prefix); +#endif } - // Print with fixed precision of 3 decimal places - printf("%.3f %s\n", diffInDesiredUnits.count(), get_duration_unit()); +#if _HAS_CXX23 + std::print("{} {}\n", diffInDesiredUnits.count(), get_duration_unit()); +#else + printf("%lld %s\n", diffInDesiredUnits.count(), get_duration_unit()); +#endif } template > -inline long long getTime(int checkpoint = -1) { - std::chrono::time_point start_time; - if (checkpoint >= 0 && checkpoint < 3) { - start_time = checkpoints[checkpoint]; - } else { - start_time = activeTimeStamp; - } - auto diff = std::chrono::high_resolution_clock::now() - start_time; +inline long long getTime(int checkpoint = 0) { + const auto diff = std::chrono::high_resolution_clock::now() - checkpoints[checkpoint]; auto diffInDesiredUnits = std::chrono::duration_cast(diff); return diffInDesiredUnits.count(); } diff --git a/src/cxutil/cxtips.h b/src/cxutil/cxtips.h index 1679e77..05d6905 100644 --- a/src/cxutil/cxtips.h +++ b/src/cxutil/cxtips.h @@ -17,22 +17,43 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. - +#define CX_FINISHED #ifndef CXSTRUCTS_SRC_CXUTIL_CXTIPS_H_ #define CXSTRUCTS_SRC_CXUTIL_CXTIPS_H_ //Links provided without warranty of function, implied ownership or support of the linked website //Extensive ranking of header compile times -////https://artificial-mind.net/projects/compile-health/ +//https://artificial-mind.net/projects/compile-health/ -/* - * Rules: - * - * 1. Explicit is better than implicit - * 2. Use const when you can - * 3. Use extra variables to keep every code line without line breaks - * - */ +// +// Tips: +// +/// 1. Explicit is better than implicit (constructors) +// +/// 2. Use const when you can +// +/// 3. Use extra variables to keep every code line without line breaks +// const auto dx = 5; +// const auto dy = 10; +// DrawText(dx,dy,"Text"); +// +/// 4. Use constexpr for inside function constants +// constexpr int threshHold = 42; +// if(val > threshHold){} +// +/// 5. Use anonymous namespace for internal linked function +// +// namespace{ +// inline DoSomething(Foo bar){} +// } +// +// DoSomething(myBar) +// +///6. Use modern C++ features (generally) +// +// std::print({},3.0F); //instead of printf(%.f,3.0F); +// +// #endif //CXSTRUCTS_SRC_CXUTIL_CXTIPS_H_