Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement zip_view for external sort. #4930

Merged
merged 2 commits into from
May 2, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Implement zip_view for external sort.
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`.

---
TYPE: IMPROVEMENT
DESC: Implement zip_view for external sort.
lums658 authored and KiterLuc committed May 2, 2024
commit 9b03531bd1afba0db3eb3251ee956c087b29b7be
2 changes: 1 addition & 1 deletion tiledb/common/test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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