init
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
cmake_minimum_required(VERSION 3.28)
|
||||
|
||||
# define CXX interface library
|
||||
|
||||
# define target & alias (i.e. consuming projects can experience namespaces)
|
||||
add_library(iue-io_csv INTERFACE)
|
||||
target_link_libraries(iue-io_csv INTERFACE iue-other_honly)
|
||||
#target_link_libraries(iue-io_csv INTERFACE OpenMP::OpenMP_CXX)
|
||||
|
||||
# define sources of target
|
||||
target_sources(iue-io_csv
|
||||
INTERFACE FILE_SET public_headers TYPE HEADERS BASE_DIRS ${PROJECT_SOURCE_DIR} FILES csv.hpp)
|
||||
|
||||
# define installation step
|
||||
install(TARGETS iue-io_csv EXPORT iue-io-export
|
||||
FILE_SET public_headers DESTINATION "./")
|
||||
|
||||
# define test
|
||||
if(BUILD_TESTING AND PROJECT_IS_TOP_LEVEL)
|
||||
add_executable(iue-io_csv.test csv.test.cpp)
|
||||
target_link_libraries(iue-io_csv.test iue-io_csv)
|
||||
add_test(NAME iue-io_csv.test COMMAND iue-io_csv.test)
|
||||
endif()
|
||||
|
||||
# define C interface library
|
||||
|
||||
# define target & alias (i.e. consuming projects can experience namespaces)
|
||||
add_library(iue-io_ccsv INTERFACE)
|
||||
|
||||
# define sources of target
|
||||
target_sources(iue-io_ccsv
|
||||
INTERFACE FILE_SET public_headers TYPE HEADERS BASE_DIRS ${PROJECT_SOURCE_DIR} FILES ccsv.h)
|
||||
|
||||
# define installation step
|
||||
install(TARGETS iue-io_ccsv EXPORT iue-io-export
|
||||
FILE_SET public_headers DESTINATION "./")
|
||||
|
||||
# define test
|
||||
|
||||
if(BUILD_TESTING AND PROJECT_IS_TOP_LEVEL)
|
||||
add_executable(iue-io_ccsv.test ccsv.test.c)
|
||||
target_link_libraries(iue-io_ccsv.test iue-io_ccsv)
|
||||
add_test(NAME iue-io_ccsv.test COMMAND iue-io_ccsv.test)
|
||||
endif()
|
||||
|
||||
# export targets for consuming cmake projects
|
||||
|
||||
# generate and install a .cmake file to allow dependend projects consuming the "install-tree" to import all targets
|
||||
install(EXPORT iue-io-export DESTINATION "iue-io" )
|
||||
|
||||
# allow dependend projects consuming the "build-tree" to use all targets
|
||||
export(EXPORT iue-io-export )
|
||||
@@ -0,0 +1,143 @@
|
||||
#pragma once
|
||||
|
||||
#include <assert.h> // assert
|
||||
#include <math.h> // NAN
|
||||
#include <stddef.h> // size_t
|
||||
#include <stdio.h> // FILE|snprintf
|
||||
#include <stdlib.h> // EXIT_FAILURE|EXIT_SUCCESS
|
||||
#include <string.h> // strtok|strdup|memcpy
|
||||
|
||||
/// @brief Set of values in a row
|
||||
struct Row {
|
||||
double* values; ///< pointer to row value array
|
||||
size_t n; ///< length of array 'value'
|
||||
};
|
||||
|
||||
/// @brief Set of rows
|
||||
struct Table {
|
||||
struct Row* rows; ///< pointer to array of rows
|
||||
size_t n; ///< length of array 'rows'
|
||||
};
|
||||
|
||||
static void table_clear(struct Table* table) {
|
||||
for (size_t i = 0; i != table->n; ++i) {
|
||||
free(table->rows[i].values);
|
||||
}
|
||||
free(table->rows);
|
||||
table->rows = NULL;
|
||||
table->n = 0;
|
||||
}
|
||||
|
||||
static void table_append_copy(struct Table* table, const double* data, size_t n) {
|
||||
assert(table != NULL);
|
||||
table->rows = realloc(table->rows, sizeof(struct Row) * ++table->n);
|
||||
double* ptr = malloc(sizeof(double) * n);
|
||||
memcpy(ptr, data, sizeof(double) * n);
|
||||
table->rows[table->n - 1].values = ptr;
|
||||
table->rows[table->n - 1].n = n;
|
||||
}
|
||||
|
||||
static void table_append_move(struct Table* table, const struct Row* row) {
|
||||
assert(table != NULL);
|
||||
table->rows = realloc(table->rows, sizeof(struct Row) * ++table->n);
|
||||
table->rows[table->n - 1] = *row;
|
||||
}
|
||||
|
||||
/// @brief Deserialize a row from a string
|
||||
static void row_serialize(const struct Row* row, FILE* stream, char del) {
|
||||
for (size_t r = 0; r != row->n - 1; ++r) {
|
||||
fprintf(stream, "%.18e%c", row->values[r], del);
|
||||
}
|
||||
fprintf(stream, "%.18e", row->values[row->n - 1]); // avoid trailing return
|
||||
fprintf(stream, "\n");
|
||||
}
|
||||
|
||||
/// @brief Writes a numeric values to a file using a csv-format
|
||||
/// @param filepath file to be written including the desired extension
|
||||
/// @param table table with the rows to be written
|
||||
/// @param del delimiter between individual values
|
||||
/// @param header informative comment in the output file
|
||||
/// @param comments character signalling that a line is a comment (if used as first character of the line )
|
||||
static int iueio_savetxt(const char* filepath, const struct Table* table, char del, const char* header, char comments) {
|
||||
|
||||
// check if data is present
|
||||
if (table->n == 0)
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
// open file
|
||||
FILE* stream = fopen(filepath, "w");
|
||||
if (stream == NULL)
|
||||
return EXIT_FAILURE;
|
||||
|
||||
// write header lines
|
||||
if (header[0] != '\0') {
|
||||
char* tmp = malloc((strlen(header) + 1) * sizeof(char));
|
||||
strcpy(tmp, header);
|
||||
char* pos = strtok(tmp, "\n");
|
||||
while (pos != NULL) {
|
||||
fprintf(stream, "%c %s%c\n", comments, pos, del);
|
||||
pos = strtok(NULL, "\n");
|
||||
}
|
||||
free(tmp);
|
||||
}
|
||||
|
||||
// write data
|
||||
for (size_t i = 0; i != table->n; ++i) {
|
||||
row_serialize(&table->rows[i], stream, del);
|
||||
}
|
||||
|
||||
fclose(stream);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/// @brief Serializes a Row from a stream
|
||||
static int row_deserialize(struct Row* row, const char* line, char del) {
|
||||
assert(row->n == 0);
|
||||
char format[32];
|
||||
// snprintf(format, sizeof(format) / sizeof(char), "%%lf %c", del);
|
||||
double value = NAN;
|
||||
const char* iter = line;
|
||||
int n = 0;
|
||||
while (sscanf(iter, "%lf %n", &value, &n) != EOF) {
|
||||
iter += n;
|
||||
row->values = realloc(row->values, sizeof(double) * ++row->n);
|
||||
row->values[row->n - 1] = value;
|
||||
char format[16];
|
||||
snprintf(format, sizeof(format) / sizeof(char), " %%*[%c] %%n", del);
|
||||
sscanf(iter, format, &n);
|
||||
iter += n;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/// @brief Read numeric values stored in a csv-format
|
||||
/// @param filepath input file containing the numeric values
|
||||
/// @param table table to be read into (must be empty)
|
||||
/// @param del delimiter between individual values in a row
|
||||
/// @return integral values signalling success or failure
|
||||
static int iueio_loadtxt(const char* filepath, struct Table* table, char del, char comment) {
|
||||
|
||||
// check if table is empty
|
||||
assert(table->n == 0);
|
||||
|
||||
// open file
|
||||
FILE* stream = fopen(filepath, "r");
|
||||
if (stream == NULL)
|
||||
return EXIT_FAILURE;
|
||||
|
||||
// read from stream row-by-row (fixed size buffer)
|
||||
static char line[1024];
|
||||
|
||||
// read each line into a row
|
||||
while (fgets(&line[0], sizeof(line) / sizeof(char), stream)) {
|
||||
if (line[0] == comment)
|
||||
continue;
|
||||
|
||||
struct Row row = {NULL, 0};
|
||||
int n = row_deserialize(&row, line, del);
|
||||
table_append_move(table, &row);
|
||||
}
|
||||
|
||||
fclose(stream);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
#include "iue-io/ccsv.h"
|
||||
|
||||
#include <assert.h> // assert
|
||||
#include <stddef.h> // size_t
|
||||
|
||||
int main(void) {
|
||||
|
||||
// prep input data
|
||||
|
||||
struct Table table1 = {NULL, 0};
|
||||
{
|
||||
double data[3] = {1, 2, 3};
|
||||
table_append_copy(&table1, data, 3);
|
||||
}
|
||||
{
|
||||
double data[4] = {10, 20, 30, 40};
|
||||
table_append_copy(&table1, data, 4);
|
||||
}
|
||||
{
|
||||
double data[4] = {1e3, 2e3, 3e3, 4e3};
|
||||
table_append_copy(&table1, data, 4);
|
||||
}
|
||||
|
||||
{ // write
|
||||
|
||||
int res = iueio_savetxt("ccsv_test.csv", &table1, ';', "this is a multiline \n header comment", '#');
|
||||
|
||||
if (res != EXIT_SUCCESS)
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
struct Table table2 = {NULL, 0};
|
||||
|
||||
{ // read
|
||||
|
||||
int res = iueio_loadtxt("ccsv_test.csv", &table2, ';', '#');
|
||||
|
||||
if (res != EXIT_SUCCESS)
|
||||
return EXIT_FAILURE;
|
||||
|
||||
for (size_t i = 0; i != table2.n; ++i) {
|
||||
for (size_t j = 0; j != table2.rows[i].n; ++j)
|
||||
printf("%lf ", table2.rows[i].values[j]);
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
// compare
|
||||
|
||||
assert(table1.n == table2.n);
|
||||
|
||||
for (size_t r = 0; r != table1.n; ++r) {
|
||||
assert(table1.rows[r].n == table2.rows[r].n);
|
||||
for (size_t c = 0; c != table1.rows[r].n; ++c) {
|
||||
assert(table1.rows[r].values[c] == table2.rows[r].values[c]);
|
||||
}
|
||||
}
|
||||
|
||||
table_clear(&table1);
|
||||
table_clear(&table2);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem> // std::filesystem::path
|
||||
#include <fstream> // std::ofstream|ifstream
|
||||
#include <iomanip> // std::scientifc|setprecision
|
||||
#include <iostream> // std::cout|endl
|
||||
#include <limits> // std::numeric_limits
|
||||
#include <sstream> // std::istringstream
|
||||
#include <string> // std::string
|
||||
#include <vector> // std::vector
|
||||
|
||||
namespace iue::io {
|
||||
|
||||
/// @brief Writes a numeric values to a file using a csv-format
|
||||
/// @param filepath file to be written including the desired extension
|
||||
/// @param table table with the rows to be written
|
||||
/// @param del delimiter between individual values
|
||||
/// @param header informative comment in the output file
|
||||
/// @param comments character signalling that a line is a comment (if used as first character of the line )
|
||||
inline void savetxt(std::filesystem::path filepath, const std::vector<std::vector<double>>& table, char del = ' ',
|
||||
std::string header = "", char comments = '#') {
|
||||
|
||||
// setup row writer callable
|
||||
auto serialize = [&del](const std::vector<double>& row) -> std::string {
|
||||
std::ostringstream oss;
|
||||
for (auto iter = row.begin(); iter != std::prev(row.end()); ++iter)
|
||||
oss << std::scientific << std::setprecision(18) << *iter << del;
|
||||
oss << std::scientific << std::setprecision(18) << *std::prev(row.end()); // avoid trailing return
|
||||
oss << std::endl;
|
||||
return oss.str();
|
||||
};
|
||||
|
||||
// check if data is present
|
||||
if (table.empty())
|
||||
return;
|
||||
|
||||
// open file
|
||||
std::ofstream stream;
|
||||
stream.exceptions(std::ifstream::badbit);
|
||||
stream.open(filepath);
|
||||
|
||||
// write header lines
|
||||
if (!header.empty()) {
|
||||
std::string line;
|
||||
std::istringstream iss(header);
|
||||
while (std::getline(iss, line)) {
|
||||
stream << comments << ' ' << line << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// write data
|
||||
for (const auto& row : table)
|
||||
stream << serialize(row);
|
||||
}
|
||||
|
||||
/// @brief Read numeric values stored in a csv-format
|
||||
/// @param filepath input file containing the numeric values
|
||||
/// @param del delimiter between individual values
|
||||
/// @param comments character signalling that a line is a comment (if used as first character of the line )
|
||||
/// @return table which was read
|
||||
inline std::vector<std::vector<double>> loadtxt(std::filesystem::path filepath, char del = ' ', char comments = '#') {
|
||||
|
||||
// setup row parser callable
|
||||
auto deserialize = [&del](const std::string& line) -> std::vector<double> {
|
||||
std::vector<double> res;
|
||||
std::stringstream stream(line);
|
||||
std::string item;
|
||||
while (std::getline(stream, item, del)) {
|
||||
std::stringstream ss(item);
|
||||
ss.exceptions(std::ios::failbit);
|
||||
auto value = std::numeric_limits<double>::quiet_NaN();
|
||||
ss >> value;
|
||||
res.push_back(value);
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
// setup table
|
||||
std::vector<std::vector<double>> table;
|
||||
|
||||
// open file
|
||||
std::ifstream stream;
|
||||
stream.exceptions(std::ifstream::badbit);
|
||||
stream.open(filepath);
|
||||
|
||||
// read from stream row-by-row
|
||||
std::string line;
|
||||
|
||||
// read line to row vectors
|
||||
while (std::getline(stream, line)) {
|
||||
if (line.front() == comments)
|
||||
continue;
|
||||
table.push_back(deserialize(line));
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
} // namespace iue::io
|
||||
@@ -0,0 +1,73 @@
|
||||
#include "iue-io/csv.hpp"
|
||||
|
||||
#include <cassert> // assert
|
||||
#include <cstddef> // std::size_t
|
||||
#include <iostream> // std::cout|endl
|
||||
#include <stdlib.h>
|
||||
#include <vector> // std::vector
|
||||
|
||||
int main() {
|
||||
|
||||
// prep input data
|
||||
|
||||
std::vector<std::vector<double>> table1;
|
||||
{
|
||||
auto row = std::vector<double>{1, 2, 3};
|
||||
table1.push_back(row);
|
||||
}
|
||||
{
|
||||
auto row = std::vector<double>{10, 20, 30, 40};
|
||||
table1.push_back(row);
|
||||
}
|
||||
{
|
||||
auto row = std::vector<double>{1e3, 2e3, 3e3, 4e3};
|
||||
table1.push_back(row);
|
||||
}
|
||||
|
||||
// write
|
||||
|
||||
try {
|
||||
|
||||
for (const auto& row : table1) {
|
||||
for (const auto& value : row)
|
||||
std::cout << value << " ";
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
iue::io::savetxt("csv_test.csv", table1, ';', "this is a multiline \n header comment");
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
std::cout << e.what();
|
||||
}
|
||||
|
||||
std::vector<std::vector<double>> table2;
|
||||
|
||||
// read
|
||||
|
||||
try {
|
||||
|
||||
table2 = iue::io::loadtxt("csv_test.csv", ';');
|
||||
|
||||
for (const auto& row : table2) {
|
||||
for (const auto& value : row)
|
||||
std::cout << value << " ";
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
std::cout << e.what();
|
||||
}
|
||||
|
||||
// compare
|
||||
|
||||
assert(table1.size() == table2.size());
|
||||
|
||||
for (size_t r = 0; r != table1.size(); ++r) {
|
||||
assert(table1[r].size() == table2[r].size());
|
||||
for (size_t c = 0; c != table1[r].size(); ++c) {
|
||||
assert(table1[r][c] == table2[r][c]);
|
||||
}
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
Reference in New Issue
Block a user