diff --git a/tiledb/common/alt_var_length_view.h b/tiledb/common/alt_var_length_view.h index 1c4bab8a966..44c2194e805 100644 --- a/tiledb/common/alt_var_length_view.h +++ b/tiledb/common/alt_var_length_view.h @@ -34,10 +34,10 @@ * The difference between `alt_var_length_view` and `var_length_view` is that * `alt_var_length_view` maintains a materialized range of subranges, whereas * `var_length_view` creates subrange views on the fly as proxy objects. As a - * result - * * An `alt_var_length_view` does not need to refer to the offsets array after - * it is constructed - * * An `alt_var_length_view` can be sorted + * result: + * - An `alt_var_length_view` does not need to refer to the offsets array + * after it is constructed + * - An `alt_var_length_view` can be sorted * * * Usage example: @@ -69,12 +69,15 @@ * @tparam I Type of the index range, assumed to be a random access range. * * @todo R could be a view rather than a range. + * @todo Would using `std::ranges::view_interface` be better tha `view_base`? */ template < std::ranges::random_access_range R, std::ranges::random_access_range I> class alt_var_length_view : public std::ranges::view_base { - // Forward reference of the iterator over the range of variable length data + /** + * Forward reference of the iterator over the range of variable length data + */ template struct private_iterator; @@ -115,30 +118,167 @@ class alt_var_length_view : public std::ranges::view_base { /** Move assignment */ alt_var_length_view& operator=(alt_var_length_view&&) = default; - /** Primary constructor. All offsets are contained in the input (notably, the - * index to the end of the data range). */ + /** + * Constructor taking iterator pairs for the data and index ranges, arrow + * format + */ + alt_var_length_view( + std::ranges::iterator_t data_begin, + [[maybe_unused]] std::ranges::iterator_t data_end, + std::ranges::iterator_t begin_index, + std::ranges::iterator_t index_end) { + auto num_subranges = index_end - begin_index - 1; + + subranges_.reserve(num_subranges); + for (long i = 0; i < num_subranges; ++i) { + subranges_.emplace_back( + data_begin + begin_index[i], data_begin + begin_index[i + 1]); + } + } + + /** + * Constructor taking iterator pairs for the data and index ranges, tiledb + * format + */ + alt_var_length_view( + std::ranges::iterator_t data_begin, + [[maybe_unused]] std::ranges::iterator_t data_end, + std::ranges::iterator_t begin_index, + std::ranges::iterator_t index_end, + data_index_type missing_index) { + auto num_subranges = index_end - begin_index; + + subranges_.reserve(num_subranges); + for (long i = 0; i < num_subranges - 1; ++i) { + subranges_.emplace_back( + data_begin + begin_index[i], data_begin + begin_index[i + 1]); + } + subranges_.emplace_back( + data_begin + begin_index[num_subranges - 1], + data_begin + missing_index); + } + + /** + * Constructor taking iterator pairs for the data and index ranges, with + * sizes, arrow format + */ + alt_var_length_view( + std::ranges::iterator_t data_begin, + [[maybe_unused]] std::ranges::iterator_t data_end, + [[maybe_unused]] std::ranges::range_difference_t n_data, + std::ranges::iterator_t begin_index, + [[maybe_unused]] std::ranges::iterator_t index_end, + std::ranges::range_difference_t n_index) { + auto num_subranges = n_index - 1; + + subranges_.reserve(num_subranges); + for (long i = 0; i < num_subranges; ++i) { + subranges_.emplace_back( + data_begin + begin_index[i], data_begin + begin_index[i + 1]); + } + } + + /** + * Constructor taking iterator pairs for the data and index ranges, with + * sizes, tiledb format + */ + alt_var_length_view( + std::ranges::iterator_t data_begin, + [[maybe_unused]] std::ranges::iterator_t data_end, + [[maybe_unused]] std::ranges::range_difference_t n_data, + std::ranges::iterator_t begin_index, + [[maybe_unused]] std::ranges::iterator_t index_end, + std::ranges::range_difference_t n_index, + data_index_type missing_index) { + auto num_subranges = n_index; + + subranges_.reserve(num_subranges); + for (long i = 0; i < num_subranges - 1; ++i) { + subranges_.emplace_back( + data_begin + begin_index[i], data_begin + begin_index[i + 1]); + } + subranges_.emplace_back( + data_begin + begin_index[num_subranges - 1], + data_begin + missing_index); + } + + /** + * Constructor taking ranges for the data and index ranges arrow + * format + */ alt_var_length_view(R& data, const I& index) { + auto num_subranges = std::ranges::size(index) - 1; + auto data_begin(std::ranges::begin(data)); + auto index_begin(std::ranges::begin(index)); + + subranges_.reserve(num_subranges); + + for (size_t i = 0; i < num_subranges; ++i) { + subranges_.emplace_back( + data_begin + index_begin[i], data_begin + index_begin[i + 1]); + } + } + + /** + * Constructor taking ranges for the data and index ranges + * tiledb format + */ + alt_var_length_view(R& data, const I& index, data_index_type missing_index) { + auto num_subranges = std::ranges::size(index); auto data_begin(std::ranges::begin(data)); + [[maybe_unused]] auto index_begin(std::ranges::begin(index)); + + subranges_.reserve(num_subranges); - subranges_.reserve(std::ranges::size(index) - 1); - for (size_t i = 0; i < std::ranges::size(index) - 1; ++i) { + for (size_t i = 0; i < num_subranges - 1; ++i) { subranges_.emplace_back(data_begin + index[i], data_begin + index[i + 1]); } + subranges_.emplace_back( + data_begin + index.back(), data_begin + missing_index); } - /** Constructor. The offsets do not contain the final index value (which would - * be the end of the data range), so the final index is passed in as a - * separate argument. + /** + * Constructor taking ranges for the data and index ranges, with sizes, arrow + * format */ - alt_var_length_view(R& data, const I& index, data_index_type end_index) { + alt_var_length_view( + R& data, + [[maybe_unused]] std::ranges::range_difference_t n_data, + const I& index, + std::ranges::range_difference_t n_index) { + auto num_subranges = n_index - 1; auto data_begin(std::ranges::begin(data)); + auto index_begin(std::ranges::begin(index)); - subranges_.reserve(std::ranges::size(index) - 1); + subranges_.reserve(num_subranges); - for (size_t i = 0; i < std::ranges::size(index) - 1; ++i) { + for (long i = 0; i < num_subranges; ++i) { + subranges_.emplace_back( + data_begin + index_begin[i], data_begin + index_begin[i + 1]); + } + } + + /** + * Constructor taking ranges for the data and index ranges, with sizes, + * tiledb format + */ + alt_var_length_view( + R& data, + [[maybe_unused]] std::ranges::range_difference_t n_data, + const I& index, + std::ranges::range_difference_t n_index, + data_index_type missing_index) { + auto num_subranges = n_index; + auto data_begin(std::ranges::begin(data)); + [[maybe_unused]] auto index_begin(std::ranges::begin(index)); + + subranges_.reserve(num_subranges); + + for (long i = 0; i < num_subranges - 1; ++i) { subranges_.emplace_back(data_begin + index[i], data_begin + index[i + 1]); } - subranges_.emplace_back(data_begin + index.back(), data_begin + end_index); + subranges_.emplace_back( + data_begin + index[num_subranges - 1], data_begin + missing_index); } /** Return iterator to the beginning of the var length view */ @@ -180,4 +320,23 @@ class alt_var_length_view : public std::ranges::view_base { std::vector subranges_; }; +/** Deduction guide for alt_var_length_view */ +template +alt_var_length_view(R, R, I, I) + -> alt_var_length_view, std::ranges::subrange>; + +/** Deduction guide for alt_var_length_view */ +template +alt_var_length_view(R, R, I, I, J) + -> alt_var_length_view, std::ranges::subrange>; + +/** Deduction guide for alt_var_length_view */ +template +alt_var_length_view(R, R, J, I, I, K) + -> alt_var_length_view, std::ranges::subrange>; + +/** Deduction guide for alt_var_length_view */ +template +alt_var_length_view(R, R, J, I, I, K, L) + -> alt_var_length_view, std::ranges::subrange>; #endif // TILEDB_ALT_VAR_LENGTH_VIEW_H diff --git a/tiledb/common/test/CMakeLists.txt b/tiledb/common/test/CMakeLists.txt index c5af2d47d80..e6f7df42ed5 100644 --- a/tiledb/common/test/CMakeLists.txt +++ b/tiledb/common/test/CMakeLists.txt @@ -42,7 +42,7 @@ commence(unit_test memory_tracker_types) conclude(unit_test) commence(unit_test common_utils) - this_target_sources(main.cc unit_alt_var_length_view.cc unit_iterator_facade.cc unit_permutation_view.cc unit_proxy_sort.cc unit_var_length_view.cc) + this_target_sources(main.cc unit_alt_var_length_view.cc unit_iterator_facade.cc unit_permutation_view.cc unit_proxy_sort.cc unit_var_length_view.cc unit_zip_view.cc) this_target_object_libraries(baseline) conclude(unit_test) diff --git a/tiledb/common/test/unit_alt_var_length_view.cc b/tiledb/common/test/unit_alt_var_length_view.cc index fd56d10aec0..9f0ee32ad0b 100644 --- a/tiledb/common/test/unit_alt_var_length_view.cc +++ b/tiledb/common/test/unit_alt_var_length_view.cc @@ -116,9 +116,44 @@ TEST_CASE( // Simple test that the alt_var_length_view can be constructed TEST_CASE("alt_var_length_view: Basic constructor", "[alt_var_length_view]") { std::vector r = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0}; + std::vector o = {0, 3, 6, 10}; + std::vector> expected = { + {1.0, 2.0, 3.0}, + {4.0, 5.0, 6.0}, + {7.0, 8.0, 9.0, 10.0}, + }; + + SECTION("iterator pair") { + auto u = alt_var_length_view(r.begin(), r.end(), o.begin(), o.end()); + auto v = alt_var_length_view{r.begin(), r.end(), o.begin(), o.end()}; + alt_var_length_view w(r.begin(), r.end(), o.begin(), o.end()); + alt_var_length_view x{r.begin(), r.end(), o.begin(), o.end()}; - SECTION("Arrow format") { - std::vector o = {0, 3, 6, 10}; + CHECK(size(u) == 3); + CHECK(size(v) == 3); + CHECK(size(w) == 3); + CHECK(size(x) == 3); + + for (auto&& i : v) { + CHECK(std::ranges::equal(i, expected[&i - &*v.begin()])); + } + } + SECTION("iterator pair with size") { + auto u = alt_var_length_view(r.begin(), r.end(), 6, o.begin(), o.end(), 3); + auto v = alt_var_length_view{r.begin(), r.end(), 6, o.begin(), o.end(), 3}; + alt_var_length_view w(r.begin(), r.end(), 6, o.begin(), o.end(), 3); + alt_var_length_view x{r.begin(), r.end(), 6, o.begin(), o.end(), 3}; + + CHECK(size(u) == 2); + CHECK(size(v) == 2); + CHECK(size(w) == 2); + CHECK(size(x) == 2); + + for (auto&& i : v) { + CHECK(std::ranges::equal(i, expected[&i - &*v.begin()])); + } + } + SECTION("range") { auto u = alt_var_length_view(r, o); auto v = alt_var_length_view{r, o}; alt_var_length_view w(r, o); @@ -128,18 +163,94 @@ TEST_CASE("alt_var_length_view: Basic constructor", "[alt_var_length_view]") { CHECK(size(v) == 3); CHECK(size(w) == 3); CHECK(size(x) == 3); + + for (auto&& i : v) { + CHECK(std::ranges::equal(i, expected[&i - &*v.begin()])); + } + } + + SECTION("range with size") { + auto u = alt_var_length_view(r, 6, o, 3); + auto v = alt_var_length_view{r, 6, o, 3}; + alt_var_length_view w(r, 6, o, 3); + alt_var_length_view x{r, 6, o, 3}; + + CHECK(size(u) == 2); + CHECK(size(v) == 2); + CHECK(size(w) == 2); + CHECK(size(x) == 2); + + for (auto&& i : v) { + CHECK(std::ranges::equal(i, expected[&i - &*v.begin()])); + } } - SECTION("TileDB format") { - std::vector o = {0, 3, 6}; - auto u = alt_var_length_view(r, o, 10); - auto v = alt_var_length_view{r, o, 10}; - alt_var_length_view w(r, o, 10); - alt_var_length_view x{r, o, 10}; + + SECTION("iterator pair, tiledb format") { + auto u = + alt_var_length_view(r.begin(), r.end(), o.begin(), o.end() - 1, 10); + auto v = + alt_var_length_view{r.begin(), r.end(), o.begin(), o.end() - 1, 10}; + alt_var_length_view w(r.begin(), r.end(), o.begin(), o.end() - 1, 10); + alt_var_length_view x{r.begin(), r.end(), o.begin(), o.end() - 1, 10}; + + CHECK(size(u) == 3); + CHECK(size(v) == 3); + CHECK(size(w) == 3); + CHECK(size(x) == 3); + + for (auto&& i : v) { + CHECK(std::ranges::equal(i, expected[&i - &*v.begin()])); + } + } + + SECTION("iterator pair with size, tiledb format") { + auto u = + alt_var_length_view(r.begin(), r.end(), 6, o.begin(), o.end(), 2, 6); + auto v = + alt_var_length_view{r.begin(), r.end(), 6, o.begin(), o.end(), 2, 6}; + alt_var_length_view w(r.begin(), r.end(), 6, o.begin(), o.end(), 2, 6); + alt_var_length_view x{r.begin(), r.end(), 6, o.begin(), o.end(), 2, 6}; + + CHECK(size(u) == 2); + CHECK(size(v) == 2); + CHECK(size(w) == 2); + CHECK(size(x) == 2); + + for (auto&& i : v) { + CHECK(std::ranges::equal(i, expected[&i - &*v.begin()])); + } + } + + SECTION("range, tiledb format") { + auto u = alt_var_length_view(r, std::ranges::views::take(o, 3), 10); + auto v = alt_var_length_view{r, std::ranges::views::take(o, 3), 10}; + alt_var_length_view w(r, std::ranges::views::take(o, 3), 10); + alt_var_length_view x{r, std::ranges::views::take(o, 3), 10}; CHECK(size(u) == 3); CHECK(size(v) == 3); CHECK(size(w) == 3); CHECK(size(x) == 3); + + for (auto&& i : v) { + CHECK(std::ranges::equal(i, expected[&i - &*v.begin()])); + } + } + + SECTION("range with size, tiledb format") { + auto u = alt_var_length_view(r, 6, o, 2, 6); + auto v = alt_var_length_view{r, 6, o, 2, 6}; + alt_var_length_view w(r, 6, o, 2, 6); + alt_var_length_view x{r, 6, o, 2, 6}; + + CHECK(size(u) == 2); + CHECK(size(v) == 2); + CHECK(size(w) == 2); + CHECK(size(x) == 2); + + for (auto&& i : v) { + CHECK(std::ranges::equal(i, expected[&i - &*v.begin()])); + } } } diff --git a/tiledb/common/test/unit_var_length_view.cc b/tiledb/common/test/unit_var_length_view.cc index 45f7988f302..9540a824a8f 100644 --- a/tiledb/common/test/unit_var_length_view.cc +++ b/tiledb/common/test/unit_var_length_view.cc @@ -103,19 +103,54 @@ TEST_CASE( } // Simple test that the var_length_view can be constructed -TEST_CASE("var_length_view: Basic constructor", "[var_length_view]") { +TEST_CASE("var_length_view: Constructors", "[var_length_view]") { std::vector r = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0}; std::vector o = {0, 3, 6, 10}; - auto u = var_length_view(r, o); - auto v = var_length_view{r, o}; - var_length_view w(r, o); - var_length_view x{r, o}; + SECTION("iterator pair") { + auto u = var_length_view(r.begin(), r.end(), o.begin(), o.end()); + auto v = var_length_view{r.begin(), r.end(), o.begin(), o.end()}; + var_length_view w(r.begin(), r.end(), o.begin(), o.end()); + var_length_view x{r.begin(), r.end(), o.begin(), o.end()}; - CHECK(size(u) == 3); - CHECK(size(v) == 3); - CHECK(size(w) == 3); - CHECK(size(x) == 3); + CHECK(size(u) == 3); + CHECK(size(v) == 3); + CHECK(size(w) == 3); + CHECK(size(x) == 3); + } + SECTION("iterator pair with size") { + auto u = var_length_view(r.begin(), r.end(), 6, o.begin(), o.end(), 3); + auto v = var_length_view{r.begin(), r.end(), 6, o.begin(), o.end(), 3}; + var_length_view w(r.begin(), r.end(), 6, o.begin(), o.end(), 3); + var_length_view x{r.begin(), r.end(), 6, o.begin(), o.end(), 3}; + + CHECK(size(u) == 2); + CHECK(size(v) == 2); + CHECK(size(w) == 2); + CHECK(size(x) == 2); + } + SECTION("range") { + auto u = var_length_view(r, o); + auto v = var_length_view{r, o}; + var_length_view w(r, o); + var_length_view x{r, o}; + + CHECK(size(u) == 3); + CHECK(size(v) == 3); + CHECK(size(w) == 3); + CHECK(size(x) == 3); + } + SECTION("range with size") { + auto u = var_length_view(r, 6, o, 3); + auto v = var_length_view{r, 6, o, 3}; + var_length_view w(r, 6, o, 3); + var_length_view x{r, 6, o, 3}; + + CHECK(size(u) == 2); + CHECK(size(v) == 2); + CHECK(size(w) == 2); + CHECK(size(x) == 2); + } } // Check that the sizes of the var_length_view are correct diff --git a/tiledb/common/test/unit_zip_view.cc b/tiledb/common/test/unit_zip_view.cc new file mode 100644 index 00000000000..0ca003509b5 --- /dev/null +++ b/tiledb/common/test/unit_zip_view.cc @@ -0,0 +1,292 @@ +/** + * @file unit_zip_view.cc + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2024 TileDB, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * 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. + * + * @section DESCRIPTION + * + * This file implements unit tests for the zip_view class. + */ + +#include +#include +#include +#include "../zip_view.h" + +#include "../alt_var_length_view.h" + +TEST_CASE("zip_view: Null test", "[zip_view][null_test]") { + REQUIRE(true); +} + +TEST_CASE("zip_view: Range concepts", "[zip_view][concepts]") { + using test_type = zip_view, std::vector>; + + CHECK(std::ranges::range); + CHECK(!std::ranges::borrowed_range); + CHECK(std::ranges::sized_range); + CHECK(std::ranges::input_range); + CHECK(!std::ranges:: + output_range>); + CHECK(std::ranges::forward_range); + CHECK(std::ranges::bidirectional_range); + CHECK(std::ranges::random_access_range); + CHECK(!std::ranges::contiguous_range); + CHECK(std::ranges::common_range); + + // @todo Fix so that it passes on ubuntu. + // CHECK(std::ranges::viewable_range); + + // @todo: Should this be a view? + CHECK(!std::ranges::view); +} + +TEST_CASE("zip_view: Iterator concepts", "[zip_view][concepts]") { + using test_type = zip_view, std::vector>; + using test_type_iterator = std::ranges::iterator_t; + using test_type_const_iterator = std::ranges::iterator_t; + + CHECK(std::input_or_output_iterator); + CHECK(std::input_or_output_iterator); + CHECK(std::input_iterator); + CHECK(std::input_iterator); + CHECK(!std::output_iterator< + test_type_iterator, + std::ranges::range_value_t>); + CHECK(!std::output_iterator< + test_type_const_iterator, + std::ranges::range_value_t>); + CHECK(std::forward_iterator); + CHECK(std::forward_iterator); + CHECK(std::bidirectional_iterator); + CHECK(std::bidirectional_iterator); + CHECK(std::random_access_iterator); + CHECK(std::random_access_iterator); +} + +// Test that the zip_view value_type satisfies the expected concepts +TEST_CASE("zip_view: value_type concepts", "[zip_view][concepts]") { + using test_type = zip_view, std::vector>; + CHECK(std::ranges::range); + + using test_iterator_type = std::ranges::iterator_t; + using test_iterator_value_type = std::iter_value_t; + using test_iterator_reference_type = + std::iter_reference_t; + + using range_value_type = std::ranges::range_value_t; + using range_reference_type = std::ranges::range_reference_t; + + CHECK(std::is_same_v); + CHECK(std::is_same_v); +} + +TEST_CASE("zip_view: constructor", "[zip_view]") { + std::vector a{1, 2, 3}; + std::vector b{4, 5, 6}; + std::vector c{7, 8, 9}; + + SECTION("Zip one range") { + auto z = zip(a); + auto it = z.begin(); + CHECK(*it == std::tuple{1}); + ++it; + CHECK(*it == std::tuple{2}); + ++it; + CHECK(*it == std::tuple{3}); + it = z.begin(); + std::get<0>(*it) = 99; + CHECK(a[0] == 99); + } + + SECTION("Zip three ranges") { + auto z = zip(a, b, c); + auto it = z.begin(); + CHECK(*it == std::tuple{1, 4, 7}); + ++it; + CHECK(*it == std::tuple{2, 5, 8}); + ++it; + CHECK(*it == std::tuple{3, 6, 9}); + it = z.begin(); + std::get<0>(*it) = 41; + std::get<1>(*it) = 42; + std::get<2>(*it) = 43; + CHECK(a[0] == 41); + CHECK(b[0] == 42); + CHECK(c[0] == 43); + } +} + +TEST_CASE("zip_view: size()", "[zip_view]") { + std::vector a{1, 2, 3}; + std::vector b{4, 5, 6, 7, 8, 9}; + std::vector c{10, 11, 12, 13}; + + CHECK(zip(a).size() == 3); + CHECK(zip(b).size() == 6); + CHECK(zip(c).size() == 4); + CHECK(zip(a, b).size() == 3); + CHECK(zip(a, c).size() == 3); + CHECK(zip(b, c).size() == 4); + CHECK(zip(a, b, c).size() == 3); +} + +TEST_CASE("zip_view: end()", "[zip_view]") { + std::vector a{1, 2, 3}; + std::vector b{4, 5, 6, 7, 8, 9}; + std::vector c{10, 11, 12, 13}; + + [[maybe_unused]] auto x = zip(a).begin(); + [[maybe_unused]] auto y = zip(a).end(); + + CHECK(zip(a).end() == zip(a).begin() + 3); + CHECK(zip(b).end() == zip(b).begin() + 6); + CHECK(zip(c).end() == zip(c).begin() + 4); + CHECK(zip(a, b).end() == zip(a, b).begin() + 3); + CHECK(zip(a, c).end() == zip(a, c).begin() + 3); + CHECK(zip(b, c).end() == zip(b, c).begin() + 4); + CHECK(zip(a, b, c).end() == zip(a, b, c).begin() + 3); + + CHECK(zip(a).end() - zip(a).begin() == 3); + CHECK(zip(b).end() - zip(b).begin() == 6); + CHECK(zip(c).end() - zip(c).begin() == 4); + CHECK(zip(a, b).end() - zip(a, b).begin() == 3); + CHECK(zip(a, c).end() - zip(a, c).begin() == 3); + CHECK(zip(b, c).end() - zip(b, c).begin() == 4); + CHECK(zip(a, b, c).end() - zip(a, b, c).begin() == 3); +} + +TEST_CASE("zip_view: basic iterator properties", "[zip_view]") { + std::vector a{1, 2, 3}; + std::vector b{4, 5, 6, 7, 8, 9}; + std::vector c{10, 11, 12, 13}; + + auto z = zip(a, b, c); + auto it = z.begin(); + auto it2 = z.begin(); + CHECK(it == it2); + CHECK(*it == *it2); + it++; + CHECK(it != it2); + it2++; + CHECK(it == it2); + CHECK(*it == *it2); + auto jt = z.end(); + CHECK(it != jt); + CHECK(it < jt); + CHECK(it <= jt); + CHECK(jt > it); + CHECK(jt >= it); + CHECK(jt == jt); + CHECK(jt >= jt); + CHECK(jt <= jt); + + it = z.begin(); + auto x = *it++; + CHECK(x == std::tuple{1, 4, 10}); + CHECK(it == z.begin() + 1); + + it = z.begin(); + auto y = *++it; + CHECK(y == std::tuple{2, 5, 11}); + CHECK(it == z.begin() + 1); + + CHECK(it[0] == *it); + CHECK(it[1] == *(it + 1)); + CHECK(it[2] == *(it + 2)); + CHECK(it[0] == std::tuple{2, 5, 11}); +} + +TEST_CASE("zip_view: alt_var_length_view", "[zip_view]") { + std::vector r = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0}; + std::vector o = {0, 3, 6, 10}; + auto v = alt_var_length_view{r, o}; + std::vector a{1, 2, 3}; + std::vector b{4, 5, 6, 7, 8, 9}; + std::vector c{10, 11, 12, 13}; + + auto z = zip(a, b, c, v); + auto it = z.begin(); + CHECK(std::get<0>(*it) == 1); + CHECK(std::get<1>(*it) == 4); + CHECK(std::get<2>(*it) == 10); + CHECK(std::ranges::equal( + std::get<3>(*it++), std::vector{1.0, 2.0, 3.0})); + CHECK(std::ranges::equal( + std::get<3>(*it++), std::vector{4.0, 5.0, 6.0})); + CHECK(std::ranges::equal( + std::get<3>(*it++), std::vector{7.0, 8.0, 9.0, 10.0})); +} + +TEST_CASE( + "zip_view: for, std::for_each with alt_var_length_view", "[zip_view]") { + std::vector r = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0}; + std::vector o = {0, 3, 6, 10}; + auto v = alt_var_length_view{r, o}; + std::vector a{8, 6, 7}; + + auto z = zip(a, v); + + SECTION("for_each") { + size_t count = 0; + std::for_each(z.begin(), z.end(), [&a, &count](auto x) { + auto&& [i, j] = x; + CHECK(i == a[count]); + switch (count) { + case 0: + CHECK(std::ranges::equal(j, std::vector{1.0, 2.0, 3.0})); + break; + case 1: + CHECK(std::ranges::equal(j, std::vector{4.0, 5.0, 6.0})); + break; + case 2: + CHECK( + std::ranges::equal(j, std::vector{7.0, 8.0, 9.0, 10.0})); + break; + } + ++count; + }); + } + SECTION("for") { + size_t count = 0; + for (auto x : z) { + auto&& [i, j] = x; + CHECK(i == a[count]); + switch (count) { + case 0: + CHECK(std::ranges::equal(j, std::vector{1.0, 2.0, 3.0})); + break; + case 1: + CHECK(std::ranges::equal(j, std::vector{4.0, 5.0, 6.0})); + break; + case 2: + CHECK( + std::ranges::equal(j, std::vector{7.0, 8.0, 9.0, 10.0})); + break; + } + ++count; + } + } +} diff --git a/tiledb/common/var_length_view.h b/tiledb/common/var_length_view.h index 089ee78abd7..f5c020e8590 100644 --- a/tiledb/common/var_length_view.h +++ b/tiledb/common/var_length_view.h @@ -60,6 +60,7 @@ * @tparam I Type of the index range, assumed to be a random access range. * * @todo R could be a view rather than a range. + * @todo: Should we use `view_interface` instead of view_base? */ template < std::ranges::random_access_range R, @@ -73,10 +74,12 @@ class var_length_view : public std::ranges::view_base { using data_iterator_type = std::ranges::iterator_t; /** The type that can index into the var length data range */ - using data_index_type = std::iter_difference_t; + using data_index_type = std::ranges::range_difference_t; - /** The type of the iterator over the index range -- It should dereference to - * something that can index into the data range (e.g., the data_index_type) */ + /** + * The type of the iterator over the index range -- It should dereference to + * something that can index into the data range (e.g., the data_index_type) + */ using index_iterator_type = std::ranges::iterator_t; /** The type dereferenced by the iterator is a subrange */ @@ -89,12 +92,72 @@ class var_length_view : public std::ranges::view_base { using var_length_const_iterator = private_iterator; public: - /** Primary constructor */ + /***************************************************************************** + * Constructors + * The full litany of constructors (for each input range): + * var_l_view(data_begin, data_end, index_begin, index_end); + * var_l_view(data_begin, data_end, n_data, index_begin, index_end, n_index); + * var_l_view(data, index); + * var_l_view(data, n_data, index, n_index); + ****************************************************************************/ + + /** Constructor taking iterator pairs for the data and index ranges */ + var_length_view( + std::ranges::iterator_t data_begin, + std::ranges::iterator_t data_end, + std::ranges::iterator_t index_begin, + std::ranges::iterator_t index_end) + : data_begin_(data_begin) + , data_end_(data_end) + , index_begin_(index_begin) + , index_end_(index_end) + , num_subranges_(index_end - index_begin - 1) { + } + + /** + * Constructor taking iterator pairs for the data and index ranges, along + * with sizes + */ + var_length_view( + std::ranges::iterator_t data_begin, + std::ranges::iterator_t data_end, + std::ranges::range_difference_t n_data, + std::ranges::iterator_t index_begin, + std::ranges::iterator_t index_end, + std::ranges::range_difference_t n_index) + : data_begin_(data_begin) + , data_end_(data_begin + n_data) + , index_begin_(index_begin) + , index_end_(index_begin + n_index) + , num_subranges_(index_end_ - index_begin_ - 1) { + assert(data_end - data_begin >= n_data); + assert(index_end - index_begin >= n_index); + } + + /** Constructor taking ranges for the data and index ranges */ var_length_view(R& data, const I& index) : data_begin_(std::ranges::begin(data)) , data_end_(std::ranges::end(data)) , index_begin_(std::ranges::cbegin(index)) - , index_end_(std::ranges::cend(index) - 1) { + , index_end_(std::ranges::cend(index)) + , num_subranges_(std::ranges::size(index) - 1) { + } + + /** + * Constructor taking ranges for the data and index ranges, along with sizes + */ + var_length_view( + R& data, + std::ranges::range_difference_t n_data, + const I& index, + std::ranges::range_difference_t n_index) + : data_begin_(std::ranges::begin(data)) + , data_end_(std::ranges::begin(data) + n_data) + , index_begin_(std::ranges::cbegin(index)) + , index_end_(std::ranges::cbegin(index) + n_index) + , num_subranges_(n_index - 1) { + assert(data_end_ - data_begin_ >= n_data); + assert(index_end_ - index_begin_ >= n_index); } /** Return iterator to the beginning of the var length view */ @@ -104,8 +167,7 @@ class var_length_view : public std::ranges::view_base { /** Return iterator to the end of the var length view */ auto end() { - return var_length_iterator( - data_begin_, index_begin_, index_end_ - index_begin_); + return var_length_iterator(data_begin_, index_begin_, num_subranges_); } /** Return const iterator to the beginning of the var length view */ @@ -115,8 +177,7 @@ class var_length_view : public std::ranges::view_base { /** Return const iterator to the end of the var length view */ auto end() const { - return var_length_const_iterator( - data_begin_, index_begin_, index_end_ - index_begin_); + return var_length_const_iterator(data_begin_, index_begin_, num_subranges_); } /** Return const iterator to the beginning of the var length view */ @@ -126,13 +187,12 @@ class var_length_view : public std::ranges::view_base { /** Return const iterator to the end of the var length view */ auto cend() const { - return var_length_const_iterator( - data_begin_, index_begin_, index_end_ - index_begin_); + return var_length_const_iterator(data_begin_, index_begin_, num_subranges_); } /** Return the number of subranges in the var length view */ auto size() const { - return index_end_ - index_begin_; + return num_subranges_; } private: @@ -212,8 +272,27 @@ class var_length_view : public std::ranges::view_base { /** The beginning of the index range */ std::ranges::iterator_t index_begin_; - /** The end of the index range */ + /** + * The end of the index range. This the actual end, not the element that + * points to the end of the data. + */ std::ranges::iterator_t index_end_; + + /** + * Length of the active index range (the number of subranges). The number of + * subranges is one less than size of the (arrow format) index range. + */ + std::ranges::range_difference_t num_subranges_; }; +/** Deduction guide for var_length_view */ +template +var_length_view(R, R, I, I) + -> var_length_view, std::ranges::subrange>; + +/** Deduction guide for var_length_view */ +template +var_length_view(R, R, J, I, I, K) + -> var_length_view, std::ranges::subrange>; + #endif // TILEDB_VAR_LENGTH_VIEW_H diff --git a/tiledb/common/zip_view.h b/tiledb/common/zip_view.h new file mode 100644 index 00000000000..a502bda0ae7 --- /dev/null +++ b/tiledb/common/zip_view.h @@ -0,0 +1,261 @@ +/** + * @file zip_view.h + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2024 TileDB, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * 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. + * + * @section DESCRIPTION + * + * This file implements a zip view for zipping together a set of ranges. + * It is intended to implement the zip view as defined for C++23. From + * https://en.cppreference.com/w/cpp/ranges/zip_view: + * 1) A zip_view is a range adaptor that takes one or more views, and produces + * a view whose ith element is a tuple-like value consisting of the ith elements + * of all views. The size of produced view is the minimum of sizes of all + * adapted views. 2) zip is a customization point object that constructs a + * zip_view. + */ + +#ifndef TILEDB_ZIP_VIEW_H +#define TILEDB_ZIP_VIEW_H + +#include +#include "iterator_facade.h" + +// @todo Should this take viewable ranges? +// template +template +class zip_view { + /** + * Forward declaration to private (random access) iterator class + * @todo gneralize to non-random access ranges + */ + template + struct private_iterator; + + using iterator_type = private_iterator; + using const_iterator_type = private_iterator; + + public: + /**************************************************************************** + * Constructors + ****************************************************************************/ + + /** + * Construct a zip view from a set of ranges. The ranges are stored in a + * tuple. + * @param rngs The ranges to zip + * + * @tparam Ranges The types of the ranges to zip + * @param rngs The ranges to zip + */ + template + zip_view(Ranges&&... rngs) + : ranges_{std::forward(rngs)...} { + } + + /**************************************************************************** + * + * Iterator accessors. If all the ranges are random access ranges (which + * we assume is the case for now), an iterator is a tuple of begin iterators + * for each of + * the ranges being zipped, plus an index. + * + * @note The end iterator is only defined if all the ranges are sized ranges, + * in which case the size of the zipped view is the minimum of the sizes of + * the ranges being zipped, and the end iterator is the begin iterator plus + * the size of the zipped view. + * + ****************************************************************************/ + + /** Return an iterator to the beginning of the zipped view. */ + auto begin() { + return std::apply( + [](auto&&... rngs) { + return iterator_type(std::ranges::begin(rngs)...); + }, + ranges_); + } + + /** + * Return an iterator to the end of the zipped view. See above for what we + * mean by "end" + */ + auto end() + requires(std::ranges::sized_range && ...) + { + return std::apply( + [this](auto&&... rngs) { + return iterator_type(std::ranges::begin(rngs)..., this->size()); + }, + ranges_); + } + + /** Return an iterator to the beginning of a const zipped view. */ + auto begin() const { + return std::apply( + [](auto&&... rngs) { + return const_iterator_type(std::ranges::cbegin(rngs)...); + }, + ranges_); + } + + /** + * Return an iterator to the end of a const zipped view. See above for what + * we mean by "end" + */ + auto end() const + requires(std::ranges::sized_range && ...) + { + return std::apply( + [this](auto&&... rngs) { + return const_iterator_type( + std::ranges::cbegin(rngs)..., this->size()); + }, + ranges_); + } + /** Return an iterator to the beginning of a const zipped view. */ + auto cbegin() const { + return std::apply( + [](auto&&... rngs) { + return const_iterator_type(std::ranges::cbegin(rngs)...); + }, + ranges_); + } + + /** + * Return an iterator to the end of a const zipped view. See above for what + * we mean by "end" + */ + auto cend() const { + return std::apply( + [this](auto&&... rngs) { + return const_iterator_type( + std::ranges::cbegin(rngs)..., this->size()); + }, + ranges_); + } + + /** + * @brief The size of the zipped view is the size of the smallest range in the + * view. Requires that all the ranges are sized ranges. + * @return The size of the smallest range in the zip view + */ + auto size() const + requires(std::ranges::sized_range && ...) + { + return std::apply( + [](auto&&... rngs) { return std::min({std::ranges::size(rngs)...}); }, + ranges_); + } + + private: + /** + * This is a very straightforward iterator for a zip view over a set of + * random access ranges. It keeps an iterator for each of the ranges being + * zipped, along with an index into them. + * @tparam Rs + */ + template + struct private_iterator : public iterator_facade> { + using value_type_ = std::tuple...>; + using index_type = + std::common_type_t...>; + + /** Default constructor */ + private_iterator() = default; + + /** Construct an iterator from a set of begin iterators */ + private_iterator( + std::ranges::iterator_t... begins, index_type index = 0) + : index_(index) + , begins_(begins...) { + } + + /************************************************************************* + * Functions needed for iterator_facade + * Here we just supply the minimum needed to make the iterator work + *************************************************************************/ + + /** + * Dereference the iterator -- the critical function for defining the + * iterator sinc the facade bases many type aliases and other functions + * based on it and its signature + */ + value_type_ dereference() const { + return std::apply( + [this](auto&&... iters) { return value_type_(iters[index_]...); }, + begins_); + } + + /** Advance the iterator by n */ + auto advance(index_type n) { + index_ += n; + return *this; + } + + /** Return the distance to another iterator */ + auto distance_to(const private_iterator& other) const { + return other.index_ - index_; + } + + /** Compare two iterators for equality */ + bool operator==(const private_iterator& other) const { + return begins_ == other.begins_ && index_ == other.index_; + } + + /************************************************************************* + * Data members + *************************************************************************/ + + /** Current index of iterator */ + index_type index_; + + /** Begin iterators for each of the ranges being zipped */ + std::tuple...> begins_; + }; + + private: + /** The ranges being zipped */ + std::tuple ranges_; +}; + +/** + * Define "zip()" cpo for creating zip views + */ +namespace _zip { +struct _fn { + // @todo Should this take viewable ranges? + // template + template + auto constexpr operator()(T&&... t) const { + return zip_view{std::forward(t)...}; + } +}; +} // namespace _zip +inline namespace _cpo { +inline constexpr auto zip = _zip::_fn{}; +} // namespace _cpo + +#endif // TILEDB_ZIP_VIEW_H