From 30c42138cec9de56ac407eb072bff3fd11d3c782 Mon Sep 17 00:00:00 2001 From: Hossein Moein Date: Wed, 15 May 2024 14:13:05 -0400 Subject: [PATCH 1/7] Implemented writing in binary --- .../DataFrame/Internals/DataFrame_functors.h | 19 ++ .../DataFrame/Internals/DataFrame_misc.tcc | 44 +++- .../Internals/DataFrame_private_decl.h | 2 +- .../Internals/DataFrame_standalone.tcc | 90 +++++++- .../DataFrame/Internals/DataFrame_write.tcc | 32 ++- include/DataFrame/Utils/Endianness.h | 209 ++++++++++++++++++ include/DataFrame/Utils/FixedSizeString.h | 6 + include/DataFrame/Utils/Utils.h | 12 +- test/dataframe_tester_3.cc | 27 +++ 9 files changed, 422 insertions(+), 19 deletions(-) create mode 100644 include/DataFrame/Utils/Endianness.h diff --git a/include/DataFrame/Internals/DataFrame_functors.h b/include/DataFrame/Internals/DataFrame_functors.h index 4552fc40..a6f4bce3 100644 --- a/include/DataFrame/Internals/DataFrame_functors.h +++ b/include/DataFrame/Internals/DataFrame_functors.h @@ -228,6 +228,25 @@ struct print_csv_functor_ : DataVec::template visitor_base { void operator() (const T &vec); }; +// ---------------------------------------------------------------------------- + +template +struct print_binary_functor_ : DataVec::template visitor_base { + + inline print_binary_functor_ (const char *n, + std::ostream &o, + long sr, + long er) + : name(n), os(o), start_row(sr), end_row(er) { } + + const char *name; + std::ostream &os; + const long start_row; + const long end_row; + + template + void operator() (const T &vec); +}; // ---------------------------------------------------------------------------- diff --git a/include/DataFrame/Internals/DataFrame_misc.tcc b/include/DataFrame/Internals/DataFrame_misc.tcc index 0dd1f27c..e326ff8c 100644 --- a/include/DataFrame/Internals/DataFrame_misc.tcc +++ b/include/DataFrame/Internals/DataFrame_misc.tcc @@ -197,16 +197,15 @@ DataFrame::print_csv_functor_::operator() (const T &vec) { using VecType = typename std::remove_reference::type; using ValueType = typename VecType::value_type; - _write_csv_df_header_(os, name, vec.size()) << ':'; + _write_csv_df_header_(os, name, vec.size()) + << ':'; const long vec_size = vec.size(); const long sr = std::min(start_row, vec_size); const long er = std::min(end_row, vec_size); - if (vec_size > 0) { - for (long i = sr; i < er; ++i) - _write_csv_df_index_(os, vec[i]) << ','; - } + for (long i = sr; i < er; ++i) + _write_csv_df_index_(os, vec[i]) << ','; os << '\n'; return; @@ -214,6 +213,31 @@ DataFrame::print_csv_functor_::operator() (const T &vec) { // ---------------------------------------------------------------------------- +template +template +template +void +DataFrame::print_binary_functor_::operator() (const T &vec) { + + using VecType = typename std::remove_reference::type; + using ValueType = typename VecType::value_type; + + char col_name[64]; + + std::strncpy(col_name, name, sizeof(col_name)); + os.write(col_name, sizeof(col_name)); + if constexpr (std::is_same_v) + _write_binary_string_(os, vec); + else if constexpr (std::is_same_v) + _write_binary_datetime_(os, vec); + else + _write_binary_data_(os, vec); + + return; +} + +// ---------------------------------------------------------------------------- + template template template @@ -696,7 +720,7 @@ operator() (T &vec) { using VecType = typename std::remove_reference::type; using ValueType = typename VecType::value_type; - using ViewType = typename DF::template ColumnVecType; + using ViewType = typename DF::template ColumnVecType; ViewType new_col; const size_type vec_size = vec.size(); @@ -737,12 +761,12 @@ operator() (T &vec) const { if (sel_indices[i] < vec_s) { if constexpr (std::is_base_of, H>::value) vec.erase(vec.begin() + (sel_indices[i] - del_count++)); - else + else vec.erase(sel_indices[i] - del_count++); - } + } else break; - } + } } // ---------------------------------------------------------------------------- @@ -772,7 +796,7 @@ random_load_data_functor_::operator() (const T &vec) { const size_type vec_s = vec.size(); const size_type n_rows = rand_indices.size(); - typename DF::template ColumnVecType new_vec; + typename DF::template ColumnVecType new_vec; size_type prev_value { 0 }; new_vec.reserve(n_rows); diff --git a/include/DataFrame/Internals/DataFrame_private_decl.h b/include/DataFrame/Internals/DataFrame_private_decl.h index bafc83cf..4e70ae36 100644 --- a/include/DataFrame/Internals/DataFrame_private_decl.h +++ b/include/DataFrame/Internals/DataFrame_private_decl.h @@ -933,7 +933,7 @@ col_vector_push_back_cont_func_(V &vec, value.reserve(2048); while (file.get(c)) [[likely]] { - if (c == '\n') break; + if (c == '\n') [[unlikely]] break; file.unget(); value.clear(); _get_token_from_file_(file, ',', value, '\0'); diff --git a/include/DataFrame/Internals/DataFrame_standalone.tcc b/include/DataFrame/Internals/DataFrame_standalone.tcc index cad56738..f33d0154 100644 --- a/include/DataFrame/Internals/DataFrame_standalone.tcc +++ b/include/DataFrame/Internals/DataFrame_standalone.tcc @@ -33,6 +33,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include #include #include @@ -881,6 +882,93 @@ inline static S &_write_csv_df_index_(S &o, unsigned char value) { // ---------------------------------------------------------------------------- +template +inline static STRM &_write_binary_string_(STRM &strm, const V &str_vec) { + + char buffer[32]; + + std::strncpy(buffer, "string", sizeof(buffer)); + strm.write(buffer, sizeof(buffer)); + + const uint64_t vec_size = str_vec.size(); + + strm.write(reinterpret_cast(&vec_size), sizeof(vec_size)); + for (const auto &str : str_vec) { + strm.write(str.data(), str.size()); + strm.put('\0'); + } + return (strm); +} + +// ---------------------------------------------------------------------------- + +template +inline static STRM &_write_binary_data_(STRM &strm, const V &vec) { + + using VecType = typename std::remove_reference::type; + using ValueType = typename VecType::value_type; + + char buffer[32]; + const auto &citer = _typeinfo_name_.find(typeid(ValueType)); + + if (citer != _typeinfo_name_.end()) [[likely]] + std::strncpy(buffer, citer->second, sizeof(buffer)); + else + std::strncpy(buffer, "N/A", sizeof(buffer)); + strm.write(buffer, sizeof(buffer)); + + const uint64_t vec_size = vec.size(); + + strm.write(reinterpret_cast(&vec_size), sizeof(vec_size)); + if constexpr (std::is_same_v) { + for (const auto &b : vec) { + const bool bval = b; + + strm.write(reinterpret_cast(&bval), sizeof(bool)); + } + } + else { + // Views don't have the data() method + // + constexpr bool has_data_method = + requires(const VecType &v) { v.data(); }; + + if constexpr (has_data_method) { + strm.write(reinterpret_cast(vec.data()), + sizeof(vec_size) * sizeof(ValueType)); + } + else { + for (std::size_t i = 0; i < vec.size(); ++i) + strm.write(reinterpret_cast(&(vec[i])), + sizeof(ValueType)); + } + } + return (strm); +} + +// ---------------------------------------------------------------------------- + +template +inline static STRM &_write_binary_datetime_(STRM &strm, const V &dt_vec) { + + char buffer[32]; + + std::strncpy(buffer, "DateTime", sizeof(buffer)); + strm.write(buffer, sizeof(buffer)); + + const uint64_t vec_size = dt_vec.size(); + + strm.write(reinterpret_cast(&vec_size), sizeof(vec_size)); + for (const auto &dt : dt_vec) { + const double val = static_cast(dt); + + strm.write(reinterpret_cast(&val), sizeof(double)); + } + return (strm); +} + +// ---------------------------------------------------------------------------- + // // Specializing std::hash for tuples // @@ -1203,7 +1291,7 @@ struct _LikeClauseUtil_ { // // NOTE: This could be, in some cases, n-squared. But it is pretty fast with // moderately sized strings. I have not tested this with huge/massive -// strings. +// strings. // static inline bool _like_clause_compare_(const char *pattern, diff --git a/include/DataFrame/Internals/DataFrame_write.tcc b/include/DataFrame/Internals/DataFrame_write.tcc index 8fed069b..ffa1e626 100644 --- a/include/DataFrame/Internals/DataFrame_write.tcc +++ b/include/DataFrame/Internals/DataFrame_write.tcc @@ -28,6 +28,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include +#include #include // ---------------------------------------------------------------------------- @@ -45,7 +46,7 @@ write(const char *file_name, long max_recs) const { std::ofstream stream; - const IOStreamOpti io_opti(stream, file_name); + const IOStreamOpti io_opti(stream, file_name, iof == io_format::binary); if (stream.fail()) [[unlikely]] { String1K err; @@ -83,7 +84,8 @@ write(S &o, if (iof != io_format::csv && iof != io_format::json && - iof != io_format::csv2) + iof != io_format::csv2 && + iof != io_format::binary) throw NotImplemented("write(): This io_format is not implemented"); bool need_pre_comma = false; @@ -193,10 +195,34 @@ write(S &o, o << '\n'; } } + else if (iof == io_format::binary) { + const auto endianness = get_system_endian(); + + o.write(reinterpret_cast(&endianness), sizeof(endians)); + + print_binary_functor_ idx_functor (DF_INDEX_COL_NAME, + o, + start_row, + end_row); + + idx_functor(indices_); + + const SpinGuard guard(lock_); + + for (const auto &[name, idx] : column_list_) [[likely]] { + print_binary_functor_ functor (name.c_str(), + o, + start_row, + end_row); + + data_[idx].change(functor); + } + } if (iof == io_format::json) o << "\n}"; - o << std::endl; + if (iof != io_format::binary) + o << std::endl; return (true); } diff --git a/include/DataFrame/Utils/Endianness.h b/include/DataFrame/Utils/Endianness.h new file mode 100644 index 00000000..a1a0f0a4 --- /dev/null +++ b/include/DataFrame/Utils/Endianness.h @@ -0,0 +1,209 @@ +// Hossein Moein +// May 14, 2024 +/* +Copyright (c) 2019-2026, Hossein Moein +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +* Neither the name of Hossein Moein and/or the DataFrame nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Hossein Moein BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include + +#include +#include +#include + +// ---------------------------------------------------------------------------- + +namespace hmdf +{ + +enum class endians : unsigned char { + + little_endian = 1, + big_endian = 2, + mixed_endian = 3, +}; + +// ---------------------------------------------------------------------------- + +template +struct SwapBytes { + + inline T operator()(T) const { + + throw NotImplemented("SwapBytes: Type/Size"); + } +}; + +// Specialisations +// +template +struct SwapBytes { + + inline T operator()(T value) const { return (value); } +}; + +template +struct SwapBytes { + + inline T operator()(T value) const { + + return (((value >> 8) & 0xff) | + ((value & 0xff) << 8)); + } +}; + +template +struct SwapBytes { + + inline T operator()(T value) const { + + return (((value & 0xff000000) >> 24) | + ((value & 0x00ff0000) >> 8) | + ((value & 0x0000ff00) << 8) | + ((value & 0x000000ff) << 24)); + } +}; + +template<> +struct SwapBytes { + + inline float operator()(float value) const { + + const uint32_t layout = + SwapBytes{ }(*((uint32_t *) &value)); + + return (*((float *) &layout)); + + } +}; + +template +struct SwapBytes { + + inline T operator()(T value) const { + + return (((value & 0xff00000000000000ULL) >> 56) | + ((value & 0x00ff000000000000ULL) >> 40) | + ((value & 0x0000ff0000000000ULL) >> 24) | + ((value & 0x000000ff00000000ULL) >> 8) | + ((value & 0x00000000ff000000ULL) << 8) | + ((value & 0x0000000000ff0000ULL) << 24) | + ((value & 0x000000000000ff00ULL) << 40) | + ((value & 0x00000000000000ffULL) << 56)); + } +}; + +template<> +struct SwapBytes { + + inline double operator()(double value) const { + + const uint64_t layout = + SwapBytes{ }(*((uint64_t *) &value)); + + return (*((double *) &layout)); + + } +}; + +/* +template +struct SwapBytes { + + inline T operator()(T value) const { + + return (((value & 0xff000000000000000000000000000000ULL) >> 120) | + ((value & 0x00ff0000000000000000000000000000ULL) >> 104) | + ((value & 0x0000ff00000000000000000000000000ULL) >> 88) | + ((value & 0x000000ff000000000000000000000000ULL) >> 72) | + ((value & 0x00000000ff0000000000000000000000ULL) >> 56) | + ((value & 0x0000000000ff00000000000000000000ULL) >> 40) | + ((value & 0x000000000000ff000000000000000000ULL) >> 24) | + ((value & 0x00000000000000ff0000000000000000ULL) >> 8) | + ((value & 0x0000000000000000ff00000000000000ULL) << 8) | + ((value & 0x000000000000000000ff000000000000ULL) << 24) | + ((value & 0x00000000000000000000ff0000000000ULL) << 40) | + ((value & 0x0000000000000000000000ff00000000ULL) << 56) | + ((value & 0x000000000000000000000000ff000000ULL) << 72) | + ((value & 0x00000000000000000000000000ff0000ULL) << 88) | + ((value & 0x0000000000000000000000000000ff00ULL) << 104) | + ((value & 0x000000000000000000000000000000ffULL) << 120)); + } +}; + +template<> +struct SwapBytes { + + inline long double operator()(long double value) const { + + const uint128_t layout = + SwapBytes{ }(*((uint128_t *) &value)); + + return (*((long double *) &layout)); + + } +}; +*/ + +// ---------------------------------------------------------------------------- + +static inline +constexpr endians get_system_endian() { + + if constexpr (std::endian::native == std::endian::big) + return (endians::big_endian); + else if constexpr (std::endian::native == std::endian::little) + return (endians::little_endian); + else + return (endians::mixed_endian); +} + +// ---------------------------------------------------------------------------- + +template +static inline +void flip_endianness(V &vec) { + + using VecType = typename std::remove_reference::type; + using ValueType = typename VecType::value_type; + + if constexpr (sizeof(ValueType) < 2) return; + + const SwapBytes swaper { }; + + for (auto &val : vec) val = swaper(val); +} + +} // namespace hmdf + +// ---------------------------------------------------------------------------- + +// Local Variables: +// mode:C++ +// tab-width:4 +// c-basic-offset:4 +// End: diff --git a/include/DataFrame/Utils/FixedSizeString.h b/include/DataFrame/Utils/FixedSizeString.h index b9056cf3..0ea6f4c3 100644 --- a/include/DataFrame/Utils/FixedSizeString.h +++ b/include/DataFrame/Utils/FixedSizeString.h @@ -291,6 +291,12 @@ class VirtualString { // [[nodiscard]] inline const_pointer c_str () const noexcept { return (string_); } + + [[nodiscard]] inline const_pointer + data () const noexcept { return (string_); } + [[nodiscard]] inline pointer + data () noexcept { return (string_); } + [[nodiscard]] inline const_pointer sub_c_str (size_type offset) const noexcept { diff --git a/include/DataFrame/Utils/Utils.h b/include/DataFrame/Utils/Utils.h index 60877c46..579de81e 100644 --- a/include/DataFrame/Utils/Utils.h +++ b/include/DataFrame/Utils/Utils.h @@ -347,17 +347,21 @@ shift_left(V &vec, std::size_t n) { // ---------------------------------------------------------------------------- -template +template struct IOStreamOpti { - IOStreamOpti (STR &stream, const char *file_name) + IOStreamOpti (STR &stream, const char *file_name, bool binary = false) : stream_(stream), tie_(std::cin.tie(nullptr)), sync_(std::ios_base::sync_with_stdio(false)) { stream_.rdbuf()->pubsetbuf(buffer_, SIZ); - if (file_name && ! stream_.is_open()) - stream_.open(file_name); + if (file_name && ! stream_.is_open()) { + if (! binary) + stream_.open(file_name); + else + stream_.open(file_name, std::ios::binary); + } } ~IOStreamOpti () { diff --git a/test/dataframe_tester_3.cc b/test/dataframe_tester_3.cc index a951f5b7..d4f8b23e 100644 --- a/test/dataframe_tester_3.cc +++ b/test/dataframe_tester_3.cc @@ -32,6 +32,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include #include @@ -3771,6 +3772,31 @@ static void test_EhlersBandPassFilterVisitor() { // ---------------------------------------------------------------------------- +static void test_writing_binary() { + + std::cout << "\nTesting test_writing_binary{ } ..." << std::endl; + + typedef StdDataFrame64 StrDataFrame; + + StrDataFrame df; + + try { + df.read("SHORT_IBM.csv", io_format::csv2); + df.write("./SHORT_IBM.csv", io_format::csv); + df.write("./SHORT_IBM.csv2", io_format::csv2); + df.write("./SHORT_IBM.dat", io_format::binary); + + std::remove("./SHORT_IBM.csv"); + std::remove("./SHORT_IBM.csv2"); + std::remove("./SHORT_IBM.data"); + } + catch (const DataFrameError &ex) { + std::cout << ex.what() << std::endl; + } +} + +// ---------------------------------------------------------------------------- + int main(int, char *[]) { MyDataFrame::set_optimum_thread_level(); @@ -3850,6 +3876,7 @@ int main(int, char *[]) { test_PeaksAndValleysVisitor(); test_EhlersHighPassFilterVisitor(); test_EhlersBandPassFilterVisitor(); + test_writing_binary(); return (0); } From a1f23f4759499b28b181b8fe93da645cf143aeac Mon Sep 17 00:00:00 2001 From: Hossein Moein Date: Wed, 15 May 2024 14:26:05 -0400 Subject: [PATCH 2/7] Added a view to testing writing binary --- test/dataframe_tester_3.cc | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/test/dataframe_tester_3.cc b/test/dataframe_tester_3.cc index d4f8b23e..5fc5d5f2 100644 --- a/test/dataframe_tester_3.cc +++ b/test/dataframe_tester_3.cc @@ -3782,13 +3782,24 @@ static void test_writing_binary() { try { df.read("SHORT_IBM.csv", io_format::csv2); - df.write("./SHORT_IBM.csv", io_format::csv); - df.write("./SHORT_IBM.csv2", io_format::csv2); - df.write("./SHORT_IBM.dat", io_format::binary); + df.write("./SHORT_IBM_dup.csv", io_format::csv); + df.write("./SHORT_IBM_dup.csv2", io_format::csv2); + df.write("./SHORT_IBM_dup.dat", io_format::binary); - std::remove("./SHORT_IBM.csv"); - std::remove("./SHORT_IBM.csv2"); - std::remove("./SHORT_IBM.data"); + auto vw = + df.get_view( + { "IBM_Open", "IBM_High", "IBM_Close", "IBM_Volume" }); + + vw.write("./FROM_VW_SHORT_IBM.csv", io_format::csv); + vw.write("./FROM_VW_SHORT_IBM.csv2", io_format::csv2); + vw.write("./FROM_VW_SHORT_IBM.dat", io_format::binary); + + std::remove("./SHORT_IBM_dup.csv"); + std::remove("./SHORT_IBM_dup.csv2"); + std::remove("./SHORT_IBM_dup.dat"); + std::remove("./FROM_VW_SHORT_IBM.csv"); + std::remove("./FROM_VW_SHORT_IBM.csv2"); + std::remove("./FROM_VW_SHORT_IBM.dat"); } catch (const DataFrameError &ex) { std::cout << ex.what() << std::endl; From 5a0ede3d8d05b867685e5d92ccf8cf63cfd08164 Mon Sep 17 00:00:00 2001 From: Hossein Moein Date: Thu, 16 May 2024 10:53:33 -0400 Subject: [PATCH 3/7] Fixed a few bugs in binary write --- docs/HTML/io_format.html | 2 +- .../DataFrame/Internals/DataFrame_standalone.tcc | 16 +++++++++++----- include/DataFrame/Internals/DataFrame_write.tcc | 4 +++- test/dataframe_tester_3.cc | 12 ++++++------ 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/docs/HTML/io_format.html b/docs/HTML/io_format.html index 7f6807b5..09479fe3 100644 --- a/docs/HTML/io_format.html +++ b/docs/HTML/io_format.html @@ -47,7 +47,7 @@ csv2 = 2, // Regular csv format (similar to Pandas) json = 3, hdf5 = 4, // Not Implemented - binary = 5, // Not Implemented + binary = 5, }; diff --git a/include/DataFrame/Internals/DataFrame_standalone.tcc b/include/DataFrame/Internals/DataFrame_standalone.tcc index f33d0154..287fd985 100644 --- a/include/DataFrame/Internals/DataFrame_standalone.tcc +++ b/include/DataFrame/Internals/DataFrame_standalone.tcc @@ -893,10 +893,16 @@ inline static STRM &_write_binary_string_(STRM &strm, const V &str_vec) { const uint64_t vec_size = str_vec.size(); strm.write(reinterpret_cast(&vec_size), sizeof(vec_size)); + + // It is better for compression, if you write the alike data together + // for (const auto &str : str_vec) { - strm.write(str.data(), str.size()); - strm.put('\0'); + const uint16_t str_sz = static_cast(str.size()); + + strm.write(reinterpret_cast(&str_sz), sizeof(str_sz)); } + for (const auto &str : str_vec) + strm.write(str.data(), str.size() * sizeof(char)); return (strm); } @@ -935,10 +941,10 @@ inline static STRM &_write_binary_data_(STRM &strm, const V &vec) { if constexpr (has_data_method) { strm.write(reinterpret_cast(vec.data()), - sizeof(vec_size) * sizeof(ValueType)); + vec_size * sizeof(ValueType)); } else { - for (std::size_t i = 0; i < vec.size(); ++i) + for (std::size_t i = 0; i < vec_size; ++i) strm.write(reinterpret_cast(&(vec[i])), sizeof(ValueType)); } @@ -962,7 +968,7 @@ inline static STRM &_write_binary_datetime_(STRM &strm, const V &dt_vec) { for (const auto &dt : dt_vec) { const double val = static_cast(dt); - strm.write(reinterpret_cast(&val), sizeof(double)); + strm.write(reinterpret_cast(&val), sizeof(val)); } return (strm); } diff --git a/include/DataFrame/Internals/DataFrame_write.tcc b/include/DataFrame/Internals/DataFrame_write.tcc index ffa1e626..24b8ff86 100644 --- a/include/DataFrame/Internals/DataFrame_write.tcc +++ b/include/DataFrame/Internals/DataFrame_write.tcc @@ -196,9 +196,11 @@ write(S &o, } } else if (iof == io_format::binary) { - const auto endianness = get_system_endian(); + const auto endianness = get_system_endian(); + const uint16_t col_num = static_cast(column_list_.size()); o.write(reinterpret_cast(&endianness), sizeof(endians)); + o.write(reinterpret_cast(&col_num), sizeof(col_num)); print_binary_functor_ idx_functor (DF_INDEX_COL_NAME, o, diff --git a/test/dataframe_tester_3.cc b/test/dataframe_tester_3.cc index 5fc5d5f2..05d642a6 100644 --- a/test/dataframe_tester_3.cc +++ b/test/dataframe_tester_3.cc @@ -3794,12 +3794,12 @@ static void test_writing_binary() { vw.write("./FROM_VW_SHORT_IBM.csv2", io_format::csv2); vw.write("./FROM_VW_SHORT_IBM.dat", io_format::binary); - std::remove("./SHORT_IBM_dup.csv"); - std::remove("./SHORT_IBM_dup.csv2"); - std::remove("./SHORT_IBM_dup.dat"); - std::remove("./FROM_VW_SHORT_IBM.csv"); - std::remove("./FROM_VW_SHORT_IBM.csv2"); - std::remove("./FROM_VW_SHORT_IBM.dat"); + std::remove("./SHORT_IBM_dup.csv"); + std::remove("./SHORT_IBM_dup.csv2"); + std::remove("./SHORT_IBM_dup.dat"); + std::remove("./FROM_VW_SHORT_IBM.csv"); + std::remove("./FROM_VW_SHORT_IBM.csv2"); + std::remove("./FROM_VW_SHORT_IBM.dat"); } catch (const DataFrameError &ex) { std::cout << ex.what() << std::endl; From ab37eb541a4199b643395217d64fb23d10b3f6ee Mon Sep 17 00:00:00 2001 From: Hossein Moein Date: Fri, 17 May 2024 14:20:11 -0400 Subject: [PATCH 4/7] Implemented binary reading --- .../Internals/DataFrame_private_decl.h | 1 + .../DataFrame/Internals/DataFrame_read.tcc | 231 +++++++++++++++++- .../Internals/DataFrame_standalone.tcc | 109 +++++++++ .../DataFrame/Internals/DataFrame_write.tcc | 3 +- include/DataFrame/Utils/Utils.h | 2 +- test/dataframe_tester.cc | 10 +- test/dataframe_tester_2.cc | 4 +- test/date_time_tester.cc | 17 +- 8 files changed, 364 insertions(+), 13 deletions(-) diff --git a/include/DataFrame/Internals/DataFrame_private_decl.h b/include/DataFrame/Internals/DataFrame_private_decl.h index 4e70ae36..4bab13b4 100644 --- a/include/DataFrame/Internals/DataFrame_private_decl.h +++ b/include/DataFrame/Internals/DataFrame_private_decl.h @@ -61,6 +61,7 @@ using JoinSortingPair = std::pair; // ---------------------------------------------------------------------------- void read_json_(std::istream &file, bool columns_only); +void read_binary_(std::istream &file); void read_csv_(std::istream &file, bool columns_only); void read_csv2_(std::istream &file, bool columns_only, diff --git a/include/DataFrame/Internals/DataFrame_read.tcc b/include/DataFrame/Internals/DataFrame_read.tcc index 4140f253..e65b7d05 100644 --- a/include/DataFrame/Internals/DataFrame_read.tcc +++ b/include/DataFrame/Internals/DataFrame_read.tcc @@ -28,9 +28,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include +#include #include #include +#include + // ---------------------------------------------------------------------------- namespace hmdf @@ -1270,6 +1273,221 @@ read_csv2_(std::istream &stream, // ---------------------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + +template +void DataFrame::read_binary_(std::istream &stream) { + + endians ed; + + stream.read(reinterpret_cast(&ed), sizeof(ed)); + + const bool needs_flipping = ed != get_system_endian(); + uint16_t col_num { 0 }; + + stream.read(reinterpret_cast(&col_num), sizeof(col_num)); + if (needs_flipping) + col_num = SwapBytes { }(col_num); + + char col_name[64]; + + std::memset(col_name, 0, sizeof(col_name)); + stream.read(col_name, sizeof(col_name)); + if (std::strcmp(col_name, DF_INDEX_COL_NAME)) { + String1K err; + + err.printf("read_binary_(): ERROR: Expecting name '%s'", + DF_INDEX_COL_NAME); + throw DataFrameError(err.c_str()); + } + + char col_type[32]; + + std::memset(col_type, 0, sizeof(col_type)); + stream.read(col_type, sizeof(col_type)); + + IndexVecType idx_vec; + + if constexpr (std::is_same_v) + _read_binary_string_(stream, idx_vec, needs_flipping); + else if constexpr (std::is_same_v) + _read_binary_datetime_(stream, idx_vec, needs_flipping); + else if constexpr (std::is_same_v) + _read_binary_data_(stream, idx_vec, needs_flipping); + else if constexpr (std::is_same_v) + _read_binary_data_(stream, idx_vec, needs_flipping); + else if constexpr (std::is_same_v) + _read_binary_data_(stream, idx_vec, needs_flipping); + else if constexpr (std::is_same_v) + _read_binary_data_(stream, idx_vec, needs_flipping); + else if constexpr (std::is_same_v) + _read_binary_data_(stream, idx_vec, needs_flipping); + else if constexpr (std::is_same_v) + _read_binary_data_(stream, idx_vec, needs_flipping); + else if constexpr (std::is_same_v) + _read_binary_data_(stream, idx_vec, needs_flipping); + else if constexpr (std::is_same_v) + _read_binary_data_(stream, idx_vec, needs_flipping); + else if constexpr (std::is_same_v) + _read_binary_data_(stream, idx_vec, needs_flipping); + else if constexpr (std::is_same_v) + _read_binary_data_(stream, idx_vec, needs_flipping); + else if constexpr (std::is_same_v) + _read_binary_data_(stream, idx_vec, needs_flipping); + else if constexpr (std::is_same_v) + _read_binary_data_(stream, idx_vec, needs_flipping); + else if constexpr (std::is_same_v) + _read_binary_data_(stream, idx_vec, needs_flipping); + else { + String1K err; + + err.printf( + "read_binary_(): ERROR: Type '%s' is not supported for index", + col_type); + throw DataFrameError(err.c_str()); + } + load_index(std::forward(idx_vec)); + + for (uint16_t i = 0; i < col_num; ++i) { + stream.read(col_name, sizeof(col_name)); + stream.read(col_type, sizeof(col_type)); + + if (! std::strcmp(col_type, "string")) { + ColumnVecType vec; + + _read_binary_string_(stream, vec, needs_flipping); + load_column(col_name, std::move(vec), + nan_policy::dont_pad_with_nans); + } + else if (! std::strcmp(col_type, "DateTime")) { + ColumnVecType vec; + + _read_binary_datetime_(stream, vec, needs_flipping); + load_column(col_name, std::move(vec), + nan_policy::dont_pad_with_nans); + } + else if (! std::strcmp(col_type, "float")) { + ColumnVecType vec; + + _read_binary_data_(stream, idx_vec, needs_flipping); + load_column(col_name, std::move(vec), + nan_policy::dont_pad_with_nans); + } + else if ( ! std::strcmp(col_type, "double")) { + ColumnVecType vec; + + _read_binary_data_(stream, idx_vec, needs_flipping); + load_column(col_name, std::move(vec), + nan_policy::dont_pad_with_nans); + } + else if ( ! std::strcmp(col_type, "short")) { + ColumnVecType vec; + + _read_binary_data_(stream, idx_vec, needs_flipping); + load_column(col_name, std::move(vec), + nan_policy::dont_pad_with_nans); + } + else if ( ! std::strcmp(col_type, "ushort")) { + ColumnVecType vec; + + _read_binary_data_(stream, idx_vec, needs_flipping); + load_column(col_name, std::move(vec), + nan_policy::dont_pad_with_nans); + } + else if ( ! std::strcmp(col_type, "int")) { + ColumnVecType vec; + + _read_binary_data_(stream, idx_vec, needs_flipping); + load_column(col_name, std::move(vec), + nan_policy::dont_pad_with_nans); + } + else if ( ! std::strcmp(col_type, "uint")) { + ColumnVecType vec; + + _read_binary_data_(stream, idx_vec, needs_flipping); + load_column(col_name, std::move(vec), + nan_policy::dont_pad_with_nans); + } + else if ( ! std::strcmp(col_type, "long")) { + ColumnVecType vec; + + _read_binary_data_(stream, idx_vec, needs_flipping); + load_column(col_name, std::move(vec), + nan_policy::dont_pad_with_nans); + } + else if ( ! std::strcmp(col_type, "ulong")) { + ColumnVecType vec; + + _read_binary_data_(stream, idx_vec, needs_flipping); + load_column(col_name, std::move(vec), + nan_policy::dont_pad_with_nans); + } + else if ( ! std::strcmp(col_type, "longlong")) { + ColumnVecType vec; + + _read_binary_data_(stream, idx_vec, needs_flipping); + load_column(col_name, std::move(vec), + nan_policy::dont_pad_with_nans); + } + else if ( ! std::strcmp(col_type, "ulonglong")) { + ColumnVecType vec; + + _read_binary_data_(stream, idx_vec, needs_flipping); + load_column(col_name, std::move(vec), + nan_policy::dont_pad_with_nans); + } + else if ( ! std::strcmp(col_type, "char")) { + ColumnVecType vec; + + _read_binary_data_(stream, idx_vec, needs_flipping); + load_column(col_name, std::move(vec), + nan_policy::dont_pad_with_nans); + } + else if ( ! std::strcmp(col_type, "uchar")) { + ColumnVecType vec; + + _read_binary_data_(stream, idx_vec, needs_flipping); + load_column(col_name, std::move(vec), + nan_policy::dont_pad_with_nans); + } + else if ( ! std::strcmp(col_type, "bool")) { + ColumnVecType vec; + + _read_binary_data_(stream, idx_vec, needs_flipping); + load_column(col_name, std::move(vec), + nan_policy::dont_pad_with_nans); + } + else { + String1K err; + + err.printf("read_binary_(): ERROR: Type '%s' is not supported", + col_type); + throw DataFrameError(err.c_str()); + } + } +} + +// ---------------------------------------------------------------------------- + template bool DataFrame:: read (const char *file_name, @@ -1279,7 +1497,7 @@ read (const char *file_name, size_type num_rows) { std::ifstream stream; - const IOStreamOpti io_opti(stream, file_name); + const IOStreamOpti io_opti(stream, file_name, iof == io_format::binary); if (stream.fail()) [[unlikely]] { String1K err; @@ -1306,7 +1524,7 @@ read (S &in_s, static_assert(std::is_base_of, DataVec>::value, "Only a StdDataFrame can call read()"); - if (iof == io_format::csv) [[likely]] { + if (iof == io_format::csv) { if (starting_row != 0 || num_rows != std::numeric_limits::max()) [[unlikely]] throw NotImplemented("read(): Reading files in chunks is currently" @@ -1325,6 +1543,15 @@ read (S &in_s, read_json_ (in_s, columns_only); } + else if (iof == io_format::binary) { + if (columns_only || starting_row != 0 || + num_rows != std::numeric_limits::max()) [[unlikely]] + throw NotImplemented("read(): Reading columns only or in chunks " + "currently not implemented for " + "io_format::binary"); + + read_binary_ (in_s); + } else throw NotImplemented("read(): This io_format is not implemented"); diff --git a/include/DataFrame/Internals/DataFrame_standalone.tcc b/include/DataFrame/Internals/DataFrame_standalone.tcc index 287fd985..3e062120 100644 --- a/include/DataFrame/Internals/DataFrame_standalone.tcc +++ b/include/DataFrame/Internals/DataFrame_standalone.tcc @@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #pragma once #include +#include #include #include @@ -903,6 +904,7 @@ inline static STRM &_write_binary_string_(STRM &strm, const V &str_vec) { } for (const auto &str : str_vec) strm.write(str.data(), str.size() * sizeof(char)); + return (strm); } @@ -949,6 +951,7 @@ inline static STRM &_write_binary_data_(STRM &strm, const V &vec) { sizeof(ValueType)); } } + return (strm); } @@ -970,6 +973,112 @@ inline static STRM &_write_binary_datetime_(STRM &strm, const V &dt_vec) { strm.write(reinterpret_cast(&val), sizeof(val)); } + + return (strm); +} + +// ---------------------------------------------------------------------------- + +template +inline static STRM & +_read_binary_string_(STRM &strm, V &str_vec, bool needs_flipping) { + + uint64_t vec_size { 0 }; + + strm.read(reinterpret_cast(&vec_size), sizeof(vec_size)); + if (needs_flipping) + vec_size = + SwapBytes { }(vec_size); + + std::vector sizes (vec_size, 0); + + strm.read(reinterpret_cast(sizes.data()), + vec_size * sizeof(uint16_t)); + if (needs_flipping) + for (auto &s : sizes) + s = SwapBytes { }(s); + + // Now read the strings + // + str_vec.reserve(vec_size); + for (const auto s : sizes) { + std::string str(std::size_t(s + 1), 0); + + strm.read(reinterpret_cast(str.data()), s); + str_vec.emplace_back(std::move(str)); + } + + return (strm); +} + +// ---------------------------------------------------------------------------- + +template +inline static STRM & +_read_binary_data_(STRM &strm, V &vec, bool needs_flipping) { + + using VecType = typename std::remove_reference::type; + using ValueType = typename VecType::value_type; + + uint64_t vec_size { 0 }; + + strm.read(reinterpret_cast(&vec_size), sizeof(vec_size)); + if (needs_flipping) + vec_size = + SwapBytes { }(vec_size); + + if constexpr (std::is_same_v) { + vec.reserve(vec_size); + for (uint64_t i = 0; i < vec_size; ++i) { + bool val; + + strm.read(reinterpret_cast(&val), sizeof(val)); + vec.push_back(val); + } + } + else { + vec.resize(vec_size); + strm.read(reinterpret_cast(vec.data()), + vec_size * sizeof(ValueType)); + } + if (needs_flipping) flip_endianness(vec); + + return (strm); +} + +// ---------------------------------------------------------------------------- + +template +inline static STRM & +_read_binary_datetime_(STRM &strm, V &dt_vec, bool needs_flipping) { + + uint64_t vec_size { 0 }; + + strm.read(reinterpret_cast(&vec_size), sizeof(vec_size)); + if (needs_flipping) + vec_size = + SwapBytes { }(vec_size); + + SwapBytes swaper { }; + + dt_vec.reserve(vec_size); + for (uint64_t i = 0; i < vec_size; ++i) { + double val { 0 }; + + strm.read(reinterpret_cast(&val), sizeof(val)); + if (needs_flipping) val = swaper(val); + + DateTime dt; + const DateTime::EpochType tm = + static_cast(val); + const DateTime::NanosecondType nano = + static_cast( + (val - static_cast(tm)) * 1'000'000'000.0); + + dt.set_time(tm, nano); + dt_vec.emplace_back(dt); + } + return (strm); } diff --git a/include/DataFrame/Internals/DataFrame_write.tcc b/include/DataFrame/Internals/DataFrame_write.tcc index 24b8ff86..ac86fa59 100644 --- a/include/DataFrame/Internals/DataFrame_write.tcc +++ b/include/DataFrame/Internals/DataFrame_write.tcc @@ -54,7 +54,8 @@ write(const char *file_name, err.printf("write(): ERROR: Unable to open file '%s'", file_name); throw DataFrameError(err.c_str()); } - write(stream, iof, precision, columns_only, max_recs); + write + (stream, iof, precision, columns_only, max_recs); return (true); } diff --git a/include/DataFrame/Utils/Utils.h b/include/DataFrame/Utils/Utils.h index 579de81e..19bb977f 100644 --- a/include/DataFrame/Utils/Utils.h +++ b/include/DataFrame/Utils/Utils.h @@ -200,7 +200,7 @@ get_nan() { return (std::numeric_limits::quiet_NaN()); } template<> inline DateTime -get_nan() { return (DateTime { 19700101 }); } +get_nan() { return (DateTime { DateTime::DateType(19700101) }); } // ---------------------------------------------------------------------------- diff --git a/test/dataframe_tester.cc b/test/dataframe_tester.cc index 094b298d..8cac9408 100644 --- a/test/dataframe_tester.cc +++ b/test/dataframe_tester.cc @@ -1256,11 +1256,11 @@ static void test_dataframe_with_datetime() { std::cout << "\nTesting DataFrame with DateTime ..." << std::endl; - DateTime dt(20010102); - StlVecType idx; - StlVecType d1; - StlVecType i1; - StlVecType s1; + DateTime dt (DateTime::DateType(20010102)); + StlVecType idx; + StlVecType d1; + StlVecType i1; + StlVecType s1; idx.reserve(20); d1.reserve(20); diff --git a/test/dataframe_tester_2.cc b/test/dataframe_tester_2.cc index 21f98f8b..6389bd95 100644 --- a/test/dataframe_tester_2.cc +++ b/test/dataframe_tester_2.cc @@ -2623,10 +2623,10 @@ static void test_DT_IBM_data() { assert(df.get_column("IBM_Open")[0] == 98.4375); assert(df.get_column("IBM_Close")[18] == 97.875); - assert(df.get_index()[18] == DateTime(20001128)); + assert(df.get_index()[18] == DateTime(DateTime::DateType(20001128))); assert(fabs(df.get_column("IBM_High")[5030] - 111.8) < 0.001); assert(df.get_column("IBM_Volume")[5022] == 21501100L); - assert(df.get_index()[5020] == DateTime(20201016)); + assert(df.get_index()[5020] == DateTime(DateTime::DateType(20201016))); } catch (const DataFrameError &ex) { std::cout << ex.what() << std::endl; diff --git a/test/date_time_tester.cc b/test/date_time_tester.cc index 33eca3f2..46b278e0 100644 --- a/test/date_time_tester.cc +++ b/test/date_time_tester.cc @@ -38,6 +38,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace hmdf; +using date_t = DateTime::DateType; + // ---------------------------------------------------------------------------- static void test_priority_queue() { @@ -216,7 +218,7 @@ int main (int, char *[]) { gmt_now.minute (), gmt_now.sec (), gmt_now.msec (), DT_TIME_ZONE::GMT); - DateTime local_time_1970 (19700101); + DateTime local_time_1970 (date_t(19700101)); DateTime gmt_time_1970 (19700101, 0, 0, 0, 0, DT_TIME_ZONE::GMT); DateTime local_time_1989 (19891214, 20, 15, 23, 0); @@ -1009,13 +1011,24 @@ int main (int, char *[]) { const DateTime dt8("2018-12-21 13:07:35.123456789+00", DT_DATE_STYLE::ISO_STYLE); - std::string result; + std::string result; dt8.date_to_str (DT_FORMAT::ISO_DT_NANO, result); std::cout << "2018-12-21 13:07:35.123456789+00 == " << result << std::endl; } + { + std::cout << "Testing DateTime double constructor ..." << std::endl; + + const DateTime dt1 ("2018-12-21 13:07:35.123456789", + DT_DATE_STYLE::ISO_STYLE); + const double val = static_cast(dt1); + const DateTime dt2 (val); + + assert(dt1 == dt2); + } + test_priority_queue(); return (EXIT_SUCCESS); } From 4a7ff0e37feab345dc47339ead10d14cb55eb32a Mon Sep 17 00:00:00 2001 From: Hossein Moein Date: Sun, 19 May 2024 10:02:45 -0400 Subject: [PATCH 5/7] Added test for binary read/write --- data/SHORT_IBM.dat | Bin 0 -> 103991 bytes .../DataFrame/Internals/DataFrame_read.tcc | 49 ++----- .../Internals/DataFrame_standalone.tcc | 13 +- .../DataFrame/Internals/DataFrame_write.tcc | 10 +- src/CommonMakefile.mk | 3 +- test/dataframe_tester_3.cc | 131 +++++++++++++++++- 6 files changed, 155 insertions(+), 51 deletions(-) create mode 100644 data/SHORT_IBM.dat diff --git a/data/SHORT_IBM.dat b/data/SHORT_IBM.dat new file mode 100644 index 0000000000000000000000000000000000000000..7cc43259e12d75cf1d7bdd0fad2e503056a7770b GIT binary patch literal 103991 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 void DataFrame::read_binary_(std::istream &stream) { @@ -1365,7 +1344,7 @@ void DataFrame::read_binary_(std::istream &stream) { col_type); throw DataFrameError(err.c_str()); } - load_index(std::forward(idx_vec)); + load_index(std::move(idx_vec)); for (uint16_t i = 0; i < col_num; ++i) { stream.read(col_name, sizeof(col_name)); @@ -1388,91 +1367,91 @@ void DataFrame::read_binary_(std::istream &stream) { else if (! std::strcmp(col_type, "float")) { ColumnVecType vec; - _read_binary_data_(stream, idx_vec, needs_flipping); + _read_binary_data_(stream, vec, needs_flipping); load_column(col_name, std::move(vec), nan_policy::dont_pad_with_nans); } else if ( ! std::strcmp(col_type, "double")) { ColumnVecType vec; - _read_binary_data_(stream, idx_vec, needs_flipping); + _read_binary_data_(stream, vec, needs_flipping); load_column(col_name, std::move(vec), nan_policy::dont_pad_with_nans); } else if ( ! std::strcmp(col_type, "short")) { ColumnVecType vec; - _read_binary_data_(stream, idx_vec, needs_flipping); + _read_binary_data_(stream, vec, needs_flipping); load_column(col_name, std::move(vec), nan_policy::dont_pad_with_nans); } else if ( ! std::strcmp(col_type, "ushort")) { ColumnVecType vec; - _read_binary_data_(stream, idx_vec, needs_flipping); + _read_binary_data_(stream, vec, needs_flipping); load_column(col_name, std::move(vec), nan_policy::dont_pad_with_nans); } else if ( ! std::strcmp(col_type, "int")) { ColumnVecType vec; - _read_binary_data_(stream, idx_vec, needs_flipping); + _read_binary_data_(stream, vec, needs_flipping); load_column(col_name, std::move(vec), nan_policy::dont_pad_with_nans); } else if ( ! std::strcmp(col_type, "uint")) { ColumnVecType vec; - _read_binary_data_(stream, idx_vec, needs_flipping); + _read_binary_data_(stream, vec, needs_flipping); load_column(col_name, std::move(vec), nan_policy::dont_pad_with_nans); } else if ( ! std::strcmp(col_type, "long")) { ColumnVecType vec; - _read_binary_data_(stream, idx_vec, needs_flipping); + _read_binary_data_(stream, vec, needs_flipping); load_column(col_name, std::move(vec), nan_policy::dont_pad_with_nans); } else if ( ! std::strcmp(col_type, "ulong")) { ColumnVecType vec; - _read_binary_data_(stream, idx_vec, needs_flipping); + _read_binary_data_(stream, vec, needs_flipping); load_column(col_name, std::move(vec), nan_policy::dont_pad_with_nans); } else if ( ! std::strcmp(col_type, "longlong")) { ColumnVecType vec; - _read_binary_data_(stream, idx_vec, needs_flipping); + _read_binary_data_(stream, vec, needs_flipping); load_column(col_name, std::move(vec), nan_policy::dont_pad_with_nans); } else if ( ! std::strcmp(col_type, "ulonglong")) { ColumnVecType vec; - _read_binary_data_(stream, idx_vec, needs_flipping); + _read_binary_data_(stream, vec, needs_flipping); load_column(col_name, std::move(vec), nan_policy::dont_pad_with_nans); } else if ( ! std::strcmp(col_type, "char")) { ColumnVecType vec; - _read_binary_data_(stream, idx_vec, needs_flipping); + _read_binary_data_(stream, vec, needs_flipping); load_column(col_name, std::move(vec), nan_policy::dont_pad_with_nans); } else if ( ! std::strcmp(col_type, "uchar")) { ColumnVecType vec; - _read_binary_data_(stream, idx_vec, needs_flipping); + _read_binary_data_(stream, vec, needs_flipping); load_column(col_name, std::move(vec), nan_policy::dont_pad_with_nans); } else if ( ! std::strcmp(col_type, "bool")) { ColumnVecType vec; - _read_binary_data_(stream, idx_vec, needs_flipping); + _read_binary_data_(stream, vec, needs_flipping); load_column(col_name, std::move(vec), nan_policy::dont_pad_with_nans); } diff --git a/include/DataFrame/Internals/DataFrame_standalone.tcc b/include/DataFrame/Internals/DataFrame_standalone.tcc index 3e062120..97090aa3 100644 --- a/include/DataFrame/Internals/DataFrame_standalone.tcc +++ b/include/DataFrame/Internals/DataFrame_standalone.tcc @@ -994,17 +994,20 @@ _read_binary_string_(STRM &strm, V &str_vec, bool needs_flipping) { strm.read(reinterpret_cast(sizes.data()), vec_size * sizeof(uint16_t)); - if (needs_flipping) + if (needs_flipping) { + SwapBytes swaper { }; + for (auto &s : sizes) - s = SwapBytes { }(s); + s = swaper(s); + } // Now read the strings // str_vec.reserve(vec_size); for (const auto s : sizes) { - std::string str(std::size_t(s + 1), 0); + std::string str (std::size_t(s), 0); - strm.read(reinterpret_cast(str.data()), s); + strm.read(str.data(), s * sizeof(char)); str_vec.emplace_back(std::move(str)); } @@ -1040,8 +1043,8 @@ _read_binary_data_(STRM &strm, V &vec, bool needs_flipping) { vec.resize(vec_size); strm.read(reinterpret_cast(vec.data()), vec_size * sizeof(ValueType)); + if (needs_flipping) flip_endianness(vec); } - if (needs_flipping) flip_endianness(vec); return (strm); } diff --git a/include/DataFrame/Internals/DataFrame_write.tcc b/include/DataFrame/Internals/DataFrame_write.tcc index ac86fa59..da429500 100644 --- a/include/DataFrame/Internals/DataFrame_write.tcc +++ b/include/DataFrame/Internals/DataFrame_write.tcc @@ -197,10 +197,12 @@ write(S &o, } } else if (iof == io_format::binary) { - const auto endianness = get_system_endian(); + const auto ed = get_system_endian(); + + o.write(reinterpret_cast(&ed), sizeof(ed)); + const uint16_t col_num = static_cast(column_list_.size()); - o.write(reinterpret_cast(&endianness), sizeof(endians)); o.write(reinterpret_cast(&col_num), sizeof(col_num)); print_binary_functor_ idx_functor (DF_INDEX_COL_NAME, @@ -224,8 +226,8 @@ write(S &o, if (iof == io_format::json) o << "\n}"; - if (iof != io_format::binary) - o << std::endl; + // if (iof != io_format::binary) + // o << std::endl; return (true); } diff --git a/src/CommonMakefile.mk b/src/CommonMakefile.mk index dca3883d..77bd60ba 100644 --- a/src/CommonMakefile.mk +++ b/src/CommonMakefile.mk @@ -73,7 +73,8 @@ HEADERS = $(LOCAL_INCLUDE_DIR)/DataFrame/Vectors/HeteroVector.h \ $(LOCAL_INCLUDE_DIR)/DataFrame/Utils/Concepts.h \ $(LOCAL_INCLUDE_DIR)/DataFrame/Utils/FixedSizeString.h \ $(LOCAL_INCLUDE_DIR)/DataFrame/Utils/FixedSizePriorityQueue.h \ - $(LOCAL_INCLUDE_DIR)/DataFrame/Utils/AlignedAllocator.h + $(LOCAL_INCLUDE_DIR)/DataFrame/Utils/AlignedAllocator.h \ + $(LOCAL_INCLUDE_DIR)/DataFrame/Utils/Endianness.h LIB_NAME = DataFrame TARGET_LIB = $(LOCAL_LIB_DIR)/lib$(LIB_NAME).a diff --git a/test/dataframe_tester_3.cc b/test/dataframe_tester_3.cc index 05d642a6..2fb7c083 100644 --- a/test/dataframe_tester_3.cc +++ b/test/dataframe_tester_3.cc @@ -3778,21 +3778,139 @@ static void test_writing_binary() { typedef StdDataFrame64 StrDataFrame; - StrDataFrame df; + StrDataFrame ibm; + StrDataFrame ibm_csv; + StrDataFrame ibm_csv2; + StrDataFrame ibm_dat; + StrDataFrame ibm_vw_dat; + StrDataFrame ibm_vw_json; try { - df.read("SHORT_IBM.csv", io_format::csv2); - df.write("./SHORT_IBM_dup.csv", io_format::csv); - df.write("./SHORT_IBM_dup.csv2", io_format::csv2); - df.write("./SHORT_IBM_dup.dat", io_format::binary); + ibm.read("SHORT_IBM.csv", io_format::csv2); + + ibm.write("./SHORT_IBM_dup.csv", io_format::csv); + ibm.write("./SHORT_IBM_dup.csv2", io_format::csv2); + ibm.write("./SHORT_IBM_dup.dat", io_format::binary); auto vw = - df.get_view( + ibm.get_view( { "IBM_Open", "IBM_High", "IBM_Close", "IBM_Volume" }); vw.write("./FROM_VW_SHORT_IBM.csv", io_format::csv); vw.write("./FROM_VW_SHORT_IBM.csv2", io_format::csv2); vw.write("./FROM_VW_SHORT_IBM.dat", io_format::binary); + vw.write("./FROM_VW_SHORT_IBM.json", io_format::json); + + ibm_csv.read("./SHORT_IBM_dup.csv", io_format::csv); + ibm_csv2.read("./SHORT_IBM_dup.csv2", io_format::csv2); + ibm_dat.read("./SHORT_IBM_dup.dat", io_format::binary); + ibm_vw_dat.read("./FROM_VW_SHORT_IBM.dat", io_format::binary); + ibm_vw_json.read("./FROM_VW_SHORT_IBM.json", io_format::json); + + assert((ibm.get_index().size() == ibm_dat.get_index().size())); + assert((ibm.get_index().size() == ibm_csv.get_index().size())); + assert((ibm.get_index().size() == ibm_csv2.get_index().size())); + assert((ibm.get_index().size() == ibm_vw_dat.get_index().size())); + assert((ibm.get_index().size() == ibm_vw_json.get_index().size())); + + assert((ibm.get_column("IBM_Open").front() == + ibm_dat.get_column("IBM_Open").front())); + assert((ibm.get_column("IBM_Open").front() == + ibm_csv.get_column("IBM_Open").front())); + assert((ibm.get_column("IBM_Open").front() == + ibm_csv2.get_column("IBM_Open").front())); + assert((ibm.get_column("IBM_Open").front() == + ibm_vw_dat.get_column("IBM_Open").front())); + assert((ibm.get_column("IBM_Open").front() == + ibm_vw_json.get_column("IBM_Open").front())); + + assert((ibm.get_column("IBM_Open").back() == + ibm_dat.get_column("IBM_Open").back())); + assert((ibm.get_column("IBM_Open").back() == + ibm_csv.get_column("IBM_Open").back())); + assert((ibm.get_column("IBM_Open").back() == + ibm_csv2.get_column("IBM_Open").back())); + assert((ibm.get_column("IBM_Open").back() == + ibm_vw_dat.get_column("IBM_Open").back())); + assert((ibm.get_column("IBM_Open").back() == + ibm_vw_json.get_column("IBM_Open").back())); + + assert((ibm.get_column("IBM_Open")[850] == + ibm_dat.get_column("IBM_Open")[850])); + assert((ibm.get_column("IBM_Open")[850] == + ibm_csv.get_column("IBM_Open")[850])); + assert((ibm.get_column("IBM_Open")[850] == + ibm_csv2.get_column("IBM_Open")[850])); + assert((ibm.get_column("IBM_Open")[850] == + ibm_vw_dat.get_column("IBM_Open")[850])); + assert((ibm.get_column("IBM_Open")[850] == + ibm_vw_json.get_column("IBM_Open")[850])); + + assert((ibm.get_column("IBM_High").front() == + ibm_dat.get_column("IBM_High").front())); + assert((ibm.get_column("IBM_High").front() == + ibm_csv.get_column("IBM_High").front())); + assert((ibm.get_column("IBM_High").front() == + ibm_csv2.get_column("IBM_High").front())); + assert((ibm.get_column("IBM_High").front() == + ibm_vw_dat.get_column("IBM_High").front())); + assert((ibm.get_column("IBM_High").front() == + ibm_vw_json.get_column("IBM_High").front())); + + assert((ibm.get_column("IBM_High").back() == + ibm_dat.get_column("IBM_High").back())); + assert((ibm.get_column("IBM_High").back() == + ibm_csv.get_column("IBM_High").back())); + assert((ibm.get_column("IBM_High").back() == + ibm_csv2.get_column("IBM_High").back())); + assert((ibm.get_column("IBM_High").back() == + ibm_vw_dat.get_column("IBM_High").back())); + assert((ibm.get_column("IBM_High").back() == + ibm_vw_json.get_column("IBM_High").back())); + + assert((ibm.get_column("IBM_High")[850] == + ibm_dat.get_column("IBM_High")[850])); + assert((ibm.get_column("IBM_High")[850] == + ibm_csv.get_column("IBM_High")[850])); + assert((ibm.get_column("IBM_High")[850] == + ibm_csv2.get_column("IBM_High")[850])); + assert((ibm.get_column("IBM_High")[850] == + ibm_vw_dat.get_column("IBM_High")[850])); + assert((ibm.get_column("IBM_High")[850] == + ibm_vw_json.get_column("IBM_High")[850])); + + assert((ibm.get_column("IBM_Volume").front() == + ibm_dat.get_column("IBM_Volume").front())); + assert((ibm.get_column("IBM_Volume").front() == + ibm_csv.get_column("IBM_Volume").front())); + assert((ibm.get_column("IBM_Volume").front() == + ibm_csv2.get_column("IBM_Volume").front())); + assert((ibm.get_column("IBM_Volume").front() == + ibm_vw_dat.get_column("IBM_Volume").front())); + assert((ibm.get_column("IBM_Volume").front() == + ibm_vw_json.get_column("IBM_Volume").front())); + + assert((ibm.get_column("IBM_Volume").back() == + ibm_dat.get_column("IBM_Volume").back())); + assert((ibm.get_column("IBM_Volume").back() == + ibm_csv.get_column("IBM_Volume").back())); + assert((ibm.get_column("IBM_Volume").back() == + ibm_csv2.get_column("IBM_Volume").back())); + assert((ibm.get_column("IBM_Volume").back() == + ibm_vw_dat.get_column("IBM_Volume").back())); + assert((ibm.get_column("IBM_Volume").back() == + ibm_vw_json.get_column("IBM_Volume").back())); + + assert((ibm.get_column("IBM_Volume")[850] == + ibm_dat.get_column("IBM_Volume")[850])); + assert((ibm.get_column("IBM_Volume")[850] == + ibm_csv.get_column("IBM_Volume")[850])); + assert((ibm.get_column("IBM_Volume")[850] == + ibm_csv2.get_column("IBM_Volume")[850])); + assert((ibm.get_column("IBM_Volume")[850] == + ibm_vw_dat.get_column("IBM_Volume")[850])); + assert((ibm.get_column("IBM_Volume")[850] == + ibm_vw_json.get_column("IBM_Volume")[850])); std::remove("./SHORT_IBM_dup.csv"); std::remove("./SHORT_IBM_dup.csv2"); @@ -3800,6 +3918,7 @@ static void test_writing_binary() { std::remove("./FROM_VW_SHORT_IBM.csv"); std::remove("./FROM_VW_SHORT_IBM.csv2"); std::remove("./FROM_VW_SHORT_IBM.dat"); + std::remove("./FROM_VW_SHORT_IBM.json"); } catch (const DataFrameError &ex) { std::cout << ex.what() << std::endl; From 023b5d46854b1bd21284e55b9ac9023de1508844 Mon Sep 17 00:00:00 2001 From: Hossein Moein Date: Mon, 20 May 2024 11:15:02 -0400 Subject: [PATCH 6/7] Added more tests for binary read/write --- docs/HTML/DataFrame.html | 2 +- .../Internals/DataFrame_private_decl.h | 21 +++-- test/dataframe_tester_3.cc | 89 +++++++++++++++++++ 3 files changed, 103 insertions(+), 9 deletions(-) diff --git a/docs/HTML/DataFrame.html b/docs/HTML/DataFrame.html index 5f6a1ca6..4002d67e 100644 --- a/docs/HTML/DataFrame.html +++ b/docs/HTML/DataFrame.html @@ -101,7 +101,7 @@

Summary

    class DataFrame;

I specifies the index column type. Index column in a DataFrame is unlike an index in a SQL database. SQL database index makes access efficient. It doesn't give you any more information. The index column in a DataFrame is metadata about the data in the DataFrame. Each entry in the index describes the given row. It could be time, frequency, …, or a set of descriptors in a struct (like temperature, altitude, …).
- H specifies a heterogenous vector type to contain DataFrame columns — don't get hang up on this too much, instead use the convenient typedef's in DataFrame Library Types.
+ H specifies a heterogenous vector type to contain DataFrame columns — don't get hang up on this too much, instead use the convenient typedef's in DataFrame Library Types. H is a relatively complex construct. You do not need to fully understand H to use the DataFrame library.
H can only be:
  • HeteroVector<std::size_t A = 0>: This is an actual heterogenous vector that would contain data. This will result in a "standard" data frame
  • diff --git a/include/DataFrame/Internals/DataFrame_private_decl.h b/include/DataFrame/Internals/DataFrame_private_decl.h index 4bab13b4..637770c0 100644 --- a/include/DataFrame/Internals/DataFrame_private_decl.h +++ b/include/DataFrame/Internals/DataFrame_private_decl.h @@ -464,7 +464,7 @@ void remove_data_by_sel_common_(const StlVecType &col_indices) { std::is_base_of, H>::value) this->indices_.erase(this->indices_.begin() + (col_indices[i] - del_count++)); - else + else this->indices_.erase(col_indices[i] - del_count++); } }; @@ -491,7 +491,7 @@ void remove_data_by_sel_common_(const StlVecType &col_indices) { if constexpr (std::is_base_of, H>::value) indices_.erase(indices_.begin() + (col_indices[i] - del_count++)); - else + else indices_.erase(col_indices[i] - del_count++); } } @@ -991,6 +991,12 @@ struct ColVectorPushBack_, Dummy> { value.clear(); _get_token_from_file_(file, ',', value, file_type == io_format::json ? ']' : '\0'); + + if (file_type == io_format::json) { // Get rid of "'s + value.pop_back(); + value.erase(value.begin()); + } + vec.push_back(value); } } @@ -1040,7 +1046,7 @@ inline static void json_str_col_vector_push_back_(StlVecType &vec, std::istream &file) { - char value[1024]; + char value[2048]; char c = 0; while (file.get(c)) @@ -1061,11 +1067,10 @@ json_str_col_vector_push_back_(StlVecType &vec, throw DataFrameError( "json_str_col_vector_push_back_(): ERROR: Expected '\"' (0)"); - while (file.get(c)) - if (c == '"') - break; - else - value[count++] = c; + while (file.get(c)) { + if (c == '"') break; + else value[count++] = c; + } if (c != '"') throw DataFrameError( "DataFrame::read_json_(): ERROR: Expected '\"' (1)"); diff --git a/test/dataframe_tester_3.cc b/test/dataframe_tester_3.cc index 2fb7c083..f793228a 100644 --- a/test/dataframe_tester_3.cc +++ b/test/dataframe_tester_3.cc @@ -3813,6 +3813,24 @@ static void test_writing_binary() { assert((ibm.get_index().size() == ibm_vw_dat.get_index().size())); assert((ibm.get_index().size() == ibm_vw_json.get_index().size())); + assert((ibm.get_index().front() == ibm_dat.get_index().front())); + assert((ibm.get_index().front() == ibm_csv.get_index().front())); + assert((ibm.get_index().front() == ibm_csv2.get_index().front())); + assert((ibm.get_index().front() == ibm_vw_dat.get_index().front())); + assert((ibm.get_index().front() == ibm_vw_json.get_index().front())); + + assert((ibm.get_index().back() == ibm_dat.get_index().back())); + assert((ibm.get_index().back() == ibm_csv.get_index().back())); + assert((ibm.get_index().back() == ibm_csv2.get_index().back())); + assert((ibm.get_index().back() == ibm_vw_dat.get_index().back())); + assert((ibm.get_index().back() == ibm_vw_json.get_index().back())); + + assert((ibm.get_index()[1000] == ibm_dat.get_index()[1000])); + assert((ibm.get_index()[1000] == ibm_csv.get_index()[1000])); + assert((ibm.get_index()[1000] == ibm_csv2.get_index()[1000])); + assert((ibm.get_index()[1000] == ibm_vw_dat.get_index()[1000])); + assert((ibm.get_index()[1000] == ibm_vw_json.get_index()[1000])); + assert((ibm.get_column("IBM_Open").front() == ibm_dat.get_column("IBM_Open").front())); assert((ibm.get_column("IBM_Open").front() == @@ -3927,6 +3945,76 @@ static void test_writing_binary() { // ---------------------------------------------------------------------------- +static void test_writing_binary_2() { + + std::cout << "\nTesting test_writing_binary_2{ } ..." << std::endl; + + using DTDataFrame = StdDataFrame64; + + DTDataFrame aapl; + DTDataFrame aapl_dat; + DTDataFrame aapl_vw_dat; + + try { + aapl.read("DT_AAPL.csv", io_format::csv2); + + aapl.write("./DT_AAPL_dup.dat", io_format::binary); + + auto vw = + aapl.get_view( + { "AAPL_Close", "AAPL_Volume", "AAPL_Open" }); + + vw.write("./FROM_VW_DT_AAPL.dat", io_format::binary); + + aapl_dat.read("./DT_AAPL_dup.dat", io_format::binary); + aapl_vw_dat.read("./FROM_VW_DT_AAPL.dat", io_format::binary); + + assert((aapl.get_index().size() == aapl_dat.get_index().size())); + assert((aapl.get_index().size() == aapl_vw_dat.get_index().size())); + + assert((aapl.get_index().front() == aapl_dat.get_index().front())); + assert((aapl.get_index().front() == aapl_vw_dat.get_index().front())); + assert((aapl.get_index().back() == aapl_dat.get_index().back())); + assert((aapl.get_index().back() == aapl_vw_dat.get_index().back())); + assert((aapl.get_index()[1200] == aapl_dat.get_index()[1200])); + assert((aapl.get_index()[1200] == aapl_vw_dat.get_index()[1200])); + + assert((aapl.get_column("AAPL_Open").front() == + aapl_dat.get_column("AAPL_Open").front())); + assert((aapl.get_column("AAPL_Open").front() == + aapl_vw_dat.get_column("AAPL_Open").front())); + assert((aapl.get_column("AAPL_Open").back() == + aapl_dat.get_column("AAPL_Open").back())); + assert((aapl.get_column("AAPL_Open").back() == + aapl_vw_dat.get_column("AAPL_Open").back())); + assert((aapl.get_column("AAPL_Open")[830] == + aapl_dat.get_column("AAPL_Open")[830])); + assert((aapl.get_column("AAPL_Open")[830] == + aapl_vw_dat.get_column("AAPL_Open")[830])); + + assert((aapl.get_column("AAPL_Volume").front() == + aapl_dat.get_column("AAPL_Volume").front())); + assert((aapl.get_column("AAPL_Volume").front() == + aapl_vw_dat.get_column("AAPL_Volume").front())); + assert((aapl.get_column("AAPL_Volume").back() == + aapl_dat.get_column("AAPL_Volume").back())); + assert((aapl.get_column("AAPL_Volume").back() == + aapl_vw_dat.get_column("AAPL_Volume").back())); + assert((aapl.get_column("AAPL_Volume")[830] == + aapl_dat.get_column("AAPL_Volume")[830])); + assert((aapl.get_column("AAPL_Volume")[830] == + aapl_vw_dat.get_column("AAPL_Volume")[830])); + + std::remove("./DT_AAPL_dup.dat"); + std::remove("./FROM_VW_DT_AAPL.dat"); + } + catch (const DataFrameError &ex) { + std::cout << ex.what() << std::endl; + } +} + +// ---------------------------------------------------------------------------- + int main(int, char *[]) { MyDataFrame::set_optimum_thread_level(); @@ -4007,6 +4095,7 @@ int main(int, char *[]) { test_EhlersHighPassFilterVisitor(); test_EhlersBandPassFilterVisitor(); test_writing_binary(); + test_writing_binary_2(); return (0); } From f7f635862b1419b1019caf554591665d03f71354 Mon Sep 17 00:00:00 2001 From: Hossein Moein Date: Mon, 20 May 2024 13:23:40 -0400 Subject: [PATCH 7/7] Added docs for binrary read/write --- docs/HTML/read.html | 8 ++- docs/HTML/write.html | 155 +++++++++++++++++++++++-------------------- 2 files changed, 88 insertions(+), 75 deletions(-) diff --git a/docs/HTML/read.html b/docs/HTML/read.html index e7511a04..b56e1534 100644 --- a/docs/HTML/read.html +++ b/docs/HTML/read.html @@ -95,7 +95,9 @@
  • Column "INDEX" must be the first column, if it exists
  • Fields in column dictionaries must be in N (name), T (type), D (data) order
  • -
    + -----------------------------------------------
    + Binary format is a proprietary format, that is optimized for compressing algorithms. It also takes care of different endianness. The file is always written with the same endianness as the writing host. But it will be adjusted accordingly when reading it from a different host with a different endianness.

    + -----------------------------------------------
    In all formats the following data types are supported:
    @@ -117,8 +119,8 @@
     string     -- char *
     bool       -- bool
     DateTime   -- DateTime data in format of
    -                        <Epoch seconds>.<nanoseconds>
    -                        (1516179600.874123908)
    +    <Epoch seconds>.<nanoseconds>
    +    (1516179600.874123908)
             
    In case of io_format::csv2 and io_format::csv the following additional types are also supported:
    diff --git a/docs/HTML/write.html b/docs/HTML/write.html
    index 0820eb15..b0b00c4e 100644
    --- a/docs/HTML/write.html
    +++ b/docs/HTML/write.html
    @@ -41,7 +41,7 @@
         
     
         
    -       
    +       
             
    
     template<typename S, typename ... Ts>
     bool
    @@ -52,39 +52,41 @@
           long max_recs = std::numeric_limits::max()) const; 
             
    - - It outputs the content of DataFrame into the stream o. Currently 3 formats (i.e. csv, csv2, json) are supported specified by the iof parameter.


    + + It outputs the content of DataFrame into the stream o. Currently 4 formats (i.e. csv, csv2, json, binary) are supported specified by the iof parameter.


    The CSV file format is written:
    -  INDEX:<Number of data points>:<Comma delimited list of values>
    -  <Column1 name>:<Number of data points>:<Column1 type>:<Comma delimited list of values>
    -  <Column2 name>:<Number of data points>:<Column2 type>:<Comma delimited list of values>
    -      .
    -      .
    -      .
    +INDEX:<Number of data points>:<Comma delimited list of values>
    +<Col1 name>:<Number of data points>:<Col1 type>:<Comma delimited list of values>
    +<Col2 name>:<Number of data points>:<Col2 type>:<Comma delimited list of values>
    +    .
    +    .
    +    .
             
    All empty lines or lines starting with # will be skipped. For examples, see files in test directory

    The CSV2 file format must be (this is similar to Pandas csv format):
    -  INDEX:<Number of data points>:<Index type>:,<Column1 name>:<Number of data points>:<Column1 type>,<Column2 name>:<Number of data points>:<Column2 type>, . . .
    -  Comma delimited rows of values
    -      .
    -      .
    -      .
    +INDEX:<Number of data points>:<Index type>:,<Column1 name>:
    +<Number of data points>:<Column1 type>,<Column2 name>:
    +<Number of data points>:<Column2 type>, . . .
    +Comma delimited rows of values
    +    .
    +    .
    +    .
             
    All empty lines or lines starting with # will be skipped. For examples, see IBM and FORD files in test directory

    The JSON file format looks like this:
    -  {
    -    "INDEX":{"N":3,"T":"ulong","D":[123450,123451,123452]},
    -    "col_3":{"N":3,"T":"double","D":[15.2,16.34,17.764]},
    -    "col_4":{"N":3,"T":"int","D":[22,23,24]},
    -    "col_str":{"N":3,"T":"string","D":["11","22","33"]},
    -    "col_2":{"N":3,"T":"double","D":[8,9.001,10]},
    -    "col_1":{"N":3,"T":"double","D":[1,2,3.456]}
    -  }
    +{
    +  "INDEX":{"N":3,"T":"ulong","D":[123450,123451,123452]},
    +  "col_3":{"N":3,"T":"double","D":[15.2,16.34,17.764]},
    +  "col_4":{"N":3,"T":"int","D":[22,23,24]},
    +  "col_str":{"N":3,"T":"string","D":["11","22","33"]},
    +  "col_2":{"N":3,"T":"double","D":[8,9.001,10]},
    +  "col_1":{"N":3,"T":"double","D":[1,2,3.456]}
    +}
             
    Please note DataFrame json does not follow json spec 100%. In json, there is no particular order in dictionary fields. But in DataFrame json:
      @@ -93,66 +95,75 @@


    + + Binary format is a proprietary format, that is optimized for compressing algorithms. It also takes care of different endianness. The file is always written with the same endianness as the writing host. But it will be adjusted accordingly when reading it from a different host with a different endianness.


    + In all formats the following data types are supported:
    -          float      -- float
    -          double     -- double
    -          longdouble -- long double
    -          short      -- short int
    -          ushort     -- unsigned short int
    -          int        -- int
    -          uint       -- unsigned int
    -          long       -- long int
    -          longlong   -- long long int
    -          ulong      -- unsigned long int
    -          ulonglong  -- unsigned long long int
    -          char       -- char
    -          uchar      -- unsigned char
    -          string     -- std::string
    -          string     -- const char *
    -          string     -- char *
    -          bool       -- bool
    -          DateTime   -- DateTime data in format of <Epoch seconds>.<nanoseconds> (1516179600.874123908)
    +float      -- float
    +double     -- double
    +longdouble -- long double
    +short      -- short int
    +ushort     -- unsigned short int
    +int        -- int
    +uint       -- unsigned int
    +long       -- long int
    +longlong   -- long long int
    +ulong      -- unsigned long int
    +ulonglong  -- unsigned long long int
    +char       -- char
    +uchar      -- unsigned char
    +string     -- std::string
    +string     -- const char *
    +string     -- char *
    +bool       -- bool
    +DateTime   -- DateTime data in format of
    +    <Epoch seconds>.<nanoseconds>
    +    (1516179600.874123908)
             
    In case of io_format::csv2 and io_format::csv the following additional types are also supported:
    -          dbl_vec        -- A vector of double precision values, The vector is printed as "s[d1|d2|...]"
    -                            where s is the size of the vector and d's are the double values.
    -          str_vec        -- A vector of std::string values, The vector is printed as "s[str1|str2|...]"
    -                            where s is the size of the vector and str's are the strings.
    -          dbl_set        -- A set of double precision values, The set is printed as "s[d1|d2|...]"
    -                            where s is the size of the set and d's are the double values.
    -          str_set        -- A set of std::string values, The set is printed as "s[str1|str2|...]"
    -                            where s is the size of the set and str's are the strings.
    -          str_dbl_map    -- A map of string keys to double precision values, The map is printed as "s{k1:v1|k2:v2|...}"
    -                            where s is the size of the map and k's and v's are keys and values.
    -          str_dbl_unomap -- An unordered map of string keys to double precision values, The map is printed as "s{k1:v1|k2:v2|...}"
    -                            where s is the size of the map and k's and v's are keys and values.
    +dbl_vec        -- A vector of double precision values, The vector is printed
    +                  as "s[d1|d2|...]" where s is the size of the vector and d's
    +                  are the double values.
    +str_vec        -- A vector of std::string values, The vector is printed as
    +                  "s[str1|str2|...]" where s is the size of the vector and
    +                  str's are the strings.
    +dbl_set        -- A set of double precision values, The set is printed as
    +                  "s[d1|d2|...]" where s is the size of the set and d's
    +                  are the double values.
    +str_set        -- A set of std::string values, The set is printed as
    +                  "s[str1|str2|...]" where s is the size of the set and
    +                  str's are the strings.
    +str_dbl_map    -- A map of string keys to double precision values, The map is
    +                  printed as "s{k1:v1|k2:v2|...}" where s is the size of
    +                  the map and k's and v's are keys and values.
    +str_dbl_unomap -- An unordered map of string keys to double precision values,
    +                  The map is printed as "s{k1:v1|k2:v2|...}" where s is the
    +                  size of the map and k's and v's are keys and values.
             
    In case of io_format::csv2 the following additional types are also supported:
    -          DateTimeAME -- DateTime string printed in American style (MM/DD/YYYY HH:MM:SS.mmm)
    -          DateTimeEUR -- DateTime string printed in European style (YYYY/MM/DD HH:MM:SS.mmm)
    -          DateTimeISO -- DateTime string printed in ISO style (YYYY-MM-DD HH:MM:SS.mmm)
    +DateTimeAME -- American style (MM/DD/YYYY HH:MM:SS.mmm)
    +DateTimeEUR -- European style (YYYY/MM/DD HH:MM:SS.mmm)
    +DateTimeISO -- ISO style (YYYY-MM-DD HH:MM:SS.mmm)
             
    - -
    - S: Output stream type
    - Ts: The list of types for all columns. A type should be specified only once
    - o: Reference to an streamable object (e.g. cout, file, ...)
    - iof: Specifies the I/O format. The default is CSV
    - precision: Specifies the precision for floating point numbers
    - columns_only: If true, the index columns is not written into the stream
    - max_recs: Max number of rows to write. If it is positive, it will write max_recs from the beginning of DataFrame. If it is negative, it will write max_recs from the end of DataFrame
    -        
    + + S: Output stream type
    + Ts: The list of types for all columns. A type should be specified only once
    + o: Reference to an streamable object (e.g. cout, file, ...)
    + iof: Specifies the I/O format. The default is CSV
    + precision: Specifies the precision for floating point numbers
    + columns_only: If true, the index columns is not written into the stream
    + max_recs: Max number of rows to write. If it is positive, it will write max_recs from the beginning of DataFrame. If it is negative, it will write max_recs from the end of DataFrame
    - +
    
     template<typename ... Ts>
     std::future<bool>
    @@ -163,7 +174,7 @@
           long max_recs = std::numeric_limits::max()) const; 
             
    - + Same as write() above, but it takes a file name

    NOTE:: This version of write() can be substantially faster, especially for larger files, than if you open the file yourself and use the write() version above. @@ -172,7 +183,7 @@ - +
    
     template<typename S, typename ... Ts>
     std::future<bool>
    @@ -191,7 +202,7 @@
         
     
         
    -       
    +       
             
    
     template<typename ... Ts>
     std::future<bool>
    @@ -210,14 +221,14 @@
         
     
         
    -       
    +       
             
    
     template<typename ... Ts>
     std::string
     to_string(std::streamsize precision = 12) const; 
             
    - + This is a convenient function (simple implementation) to convert a DataFrame into a string that could be restored later by calling from_string(). It utilizes the write() member function of DataFrame.
    These functions could be used to transmit a DataFrame from one place to another or store a DataFrame in databases, caches, ...

    @@ -229,7 +240,7 @@ - + Ts: The list of types for all columns. A type should be specified only once
    precision: Specifies the precision for floating point numbers