Skip to content

Commit

Permalink
Implement zip_view for external sort. (#4930)
Browse files Browse the repository at this point in the history
This PR implements a `zip_view` for zipping together a set of ranges. It
is intended to implement the `std::ranges::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`

Currently, the `zip_view` only supports zipping together ranges that are
`random_access_range`s. In addition, the `size()` and `end()` functions
are only provided if all of the ranges are `sized_range`s

The iterator from a `zip_view` is essentially a tuple of pointers to the
beginning of each of the zipped ranges, plus an index that keeps track
of the iterator's location in the zipped ranges. The size of a
`zip_view` is the size of the smallest range. The end iterator of a zip
view is the begin iterator plus the size of the zip_view.

Unit tests have similar coverage to the tests for the var length views.
Tests also include zipping a var length view and iterating through with
`std::for_each` and `for`.

[sc-43639]

---
TYPE: IMPROVEMENT
DESC: Implement zip_view for external sort.
  • Loading branch information
lums658 authored May 2, 2024
1 parent af175e8 commit d861e95
Show file tree
Hide file tree
Showing 3 changed files with 554 additions and 1 deletion.
2 changes: 1 addition & 1 deletion tiledb/common/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

292 changes: 292 additions & 0 deletions tiledb/common/test/unit_zip_view.cc
Original file line number Diff line number Diff line change
@@ -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 <algorithm>
#include <catch2/catch_all.hpp>
#include <vector>
#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<double>, std::vector<int>>;

CHECK(std::ranges::range<test_type>);
CHECK(!std::ranges::borrowed_range<test_type>);
CHECK(std::ranges::sized_range<test_type>);
CHECK(std::ranges::input_range<test_type>);
CHECK(!std::ranges::
output_range<test_type, std::ranges::range_value_t<test_type>>);
CHECK(std::ranges::forward_range<test_type>);
CHECK(std::ranges::bidirectional_range<test_type>);
CHECK(std::ranges::random_access_range<test_type>);
CHECK(!std::ranges::contiguous_range<test_type>);
CHECK(std::ranges::common_range<test_type>);

// @todo Fix so that it passes on ubuntu.
// CHECK(std::ranges::viewable_range<test_type>);

// @todo: Should this be a view?
CHECK(!std::ranges::view<test_type>);
}

TEST_CASE("zip_view: Iterator concepts", "[zip_view][concepts]") {
using test_type = zip_view<std::vector<double>, std::vector<int>>;
using test_type_iterator = std::ranges::iterator_t<test_type>;
using test_type_const_iterator = std::ranges::iterator_t<const test_type>;

CHECK(std::input_or_output_iterator<test_type_iterator>);
CHECK(std::input_or_output_iterator<test_type_const_iterator>);
CHECK(std::input_iterator<test_type_iterator>);
CHECK(std::input_iterator<test_type_const_iterator>);
CHECK(!std::output_iterator<
test_type_iterator,
std::ranges::range_value_t<test_type>>);
CHECK(!std::output_iterator<
test_type_const_iterator,
std::ranges::range_value_t<test_type>>);
CHECK(std::forward_iterator<test_type_iterator>);
CHECK(std::forward_iterator<test_type_const_iterator>);
CHECK(std::bidirectional_iterator<test_type_iterator>);
CHECK(std::bidirectional_iterator<test_type_const_iterator>);
CHECK(std::random_access_iterator<test_type_iterator>);
CHECK(std::random_access_iterator<test_type_const_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<double>, std::vector<int>>;
CHECK(std::ranges::range<test_type>);

using test_iterator_type = std::ranges::iterator_t<test_type>;
using test_iterator_value_type = std::iter_value_t<test_iterator_type>;
using test_iterator_reference_type =
std::iter_reference_t<test_iterator_type>;

using range_value_type = std::ranges::range_value_t<test_type>;
using range_reference_type = std::ranges::range_reference_t<test_type>;

CHECK(std::is_same_v<test_iterator_value_type, range_value_type>);
CHECK(std::is_same_v<test_iterator_reference_type, range_reference_type>);
}

TEST_CASE("zip_view: constructor", "[zip_view]") {
std::vector<int> a{1, 2, 3};
std::vector<int> b{4, 5, 6};
std::vector<int> 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<int> a{1, 2, 3};
std::vector<int> b{4, 5, 6, 7, 8, 9};
std::vector<int> 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<int> a{1, 2, 3};
std::vector<int> b{4, 5, 6, 7, 8, 9};
std::vector<int> 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<int> a{1, 2, 3};
std::vector<int> b{4, 5, 6, 7, 8, 9};
std::vector<int> 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<double> r = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0};
std::vector<size_t> o = {0, 3, 6, 10};
auto v = alt_var_length_view{r, o};
std::vector<int> a{1, 2, 3};
std::vector<int> b{4, 5, 6, 7, 8, 9};
std::vector<int> 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<double>{1.0, 2.0, 3.0}));
CHECK(std::ranges::equal(
std::get<3>(*it++), std::vector<double>{4.0, 5.0, 6.0}));
CHECK(std::ranges::equal(
std::get<3>(*it++), std::vector<double>{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<double> r = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0};
std::vector<size_t> o = {0, 3, 6, 10};
auto v = alt_var_length_view{r, o};
std::vector<int> 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<double>{1.0, 2.0, 3.0}));
break;
case 1:
CHECK(std::ranges::equal(j, std::vector<double>{4.0, 5.0, 6.0}));
break;
case 2:
CHECK(
std::ranges::equal(j, std::vector<double>{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<double>{1.0, 2.0, 3.0}));
break;
case 1:
CHECK(std::ranges::equal(j, std::vector<double>{4.0, 5.0, 6.0}));
break;
case 2:
CHECK(
std::ranges::equal(j, std::vector<double>{7.0, 8.0, 9.0, 10.0}));
break;
}
++count;
}
}
}
Loading

0 comments on commit d861e95

Please sign in to comment.