Skip to content

Commit

Permalink
Implement most ranges partition algorithms (#976)
Browse files Browse the repository at this point in the history
Includes `ranges::is_partitioned`, `ranges::partition`, `ranges::partition_copy`, and `ranges::partition_point`.
  • Loading branch information
CaseyCarter authored Jul 11, 2020
1 parent 672bcb2 commit 6d441a1
Show file tree
Hide file tree
Showing 9 changed files with 649 additions and 12 deletions.
330 changes: 318 additions & 12 deletions stl/inc/algorithm

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions tests/std/include/range_algorithm_support.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ namespace detail {
} // namespace detail
constexpr bool is_permissive = detail::Derived<int>::test();

template <class T>
inline constexpr T* nullptr_to = nullptr;

template <bool>
struct borrowed { // borrowed<true> is a borrowed_range; borrowed<false> is not
int* begin() const;
Expand Down
3 changes: 3 additions & 0 deletions tests/std/test.lst
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,9 @@ tests\P0896R4_ranges_alg_minmax
tests\P0896R4_ranges_alg_mismatch
tests\P0896R4_ranges_alg_move
tests\P0896R4_ranges_alg_none_of
tests\P0896R4_ranges_alg_partition
tests\P0896R4_ranges_alg_partition_copy
tests\P0896R4_ranges_alg_partition_point
tests\P0896R4_ranges_alg_replace_if
tests\P0896R4_ranges_alg_search
tests\P0896R4_ranges_alg_search_n
Expand Down
4 changes: 4 additions & 0 deletions tests/std/tests/P0896R4_ranges_alg_partition/env.lst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

RUNALL_INCLUDE ..\concepts_matrix.lst
115 changes: 115 additions & 0 deletions tests/std/tests/P0896R4_ranges_alg_partition/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

// Covers ranges::is_partitioned and ranges::partition (and bits of ranges::partition_point)

#include <algorithm>
#include <array>
#include <cassert>
#include <concepts>
#include <numeric>
#include <ranges>
#include <utility>

#include <range_algorithm_support.hpp>

using namespace std;

#define ASSERT(...) assert((__VA_ARGS__))

using P = pair<int, int>;

constexpr auto is_even = [](int i) { return i % 2 == 0; };

// Validate dangling story
STATIC_ASSERT(same_as<decltype(ranges::partition(borrowed<false>{}, is_even)), ranges::dangling>);
STATIC_ASSERT(same_as<decltype(ranges::partition(borrowed<true>{}, is_even)), ranges::subrange<int*>>);

struct empty_test {
template <ranges::input_range Range>
static constexpr void call() {
// Validate empty ranges
using ranges::is_partitioned, ranges::partition, ranges::partition_point;
{
Range range{};
ASSERT(is_partitioned(range, is_even, get_first));
}
{
Range range{};
ASSERT(is_partitioned(range.begin(), range.end(), is_even, get_first));
}

if constexpr (ranges::forward_range<Range>) {
const Range range{};
{
const auto result = partition(range, is_even, get_first);
ASSERT(result.begin() == range.end());
ASSERT(result.end() == range.end());
}
{
const auto result = partition(range.begin(), range.end(), is_even, get_first);
ASSERT(result.begin() == range.end());
ASSERT(result.end() == range.end());
}
}
}
};

struct partition_test {
static constexpr array elements = {P{0, 42}, P{1, 42}, P{2, 42}, P{3, 42}, P{4, 42}, P{5, 42}, P{6, 42}, P{7, 42}};

template <ranges::input_range Range>
static constexpr void call() {
using ranges::is_partitioned, ranges::partition, ranges::partition_point, ranges::is_permutation,
ranges::subrange;

auto pairs = elements;

{
Range range{pairs};
ASSERT(!is_partitioned(range, is_even, get_first));
}
{
Range range{pairs};
ASSERT(!is_partitioned(range.begin(), range.end(), is_even, get_first));
}

#if !defined(__clang__) && !defined(__EDG__) // TRANSITION, VSO-938163
if (!is_constant_evaluated())
#endif // TRANSITION, VSO-938163
{
if constexpr (ranges::forward_range<Range>) {
const Range range{pairs};
const auto mid = ranges::next(range.begin(), 4);

{
auto result = partition(range, is_even, get_first);
ASSERT(result.begin() == mid);
ASSERT(result.end() == range.end());
}
ASSERT(is_permutation(subrange{range.begin(), mid}, array{0, 2, 4, 6}, ranges::equal_to{}, get_first));
ASSERT(is_partitioned(range, is_even, get_first));
ASSERT(partition_point(range, is_even, get_first) == mid);

pairs = elements;

{
auto result = partition(range.begin(), range.end(), is_even, get_first);
ASSERT(result.begin() == mid);
ASSERT(result.end() == range.end());
}
ASSERT(is_permutation(subrange{range.begin(), mid}, array{0, 2, 4, 6}, ranges::equal_to{}, get_first));
ASSERT(is_partitioned(range.begin(), range.end(), is_even, get_first));
ASSERT(partition_point(range.begin(), range.end(), is_even, get_first) == mid);
}
}
}
};

int main() {
STATIC_ASSERT((test_in<empty_test, P>(), true));
test_in<empty_test, P>();

STATIC_ASSERT((test_in<partition_test, P>(), true));
test_in<partition_test, P>();
}
4 changes: 4 additions & 0 deletions tests/std/tests/P0896R4_ranges_alg_partition_copy/env.lst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

RUNALL_INCLUDE ..\concepts_matrix.lst
134 changes: 134 additions & 0 deletions tests/std/tests/P0896R4_ranges_alg_partition_copy/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include <algorithm>
#include <array>
#include <cassert>
#include <concepts>
#include <numeric>
#include <ranges>
#include <utility>

#include <range_algorithm_support.hpp>

using namespace std;

#define ASSERT(...) assert((__VA_ARGS__))

using P = pair<int, int>;

constexpr auto is_even = [](int i) { return i % 2 == 0; };

// Validate that partition_copy_result aliases in_out_out_result
STATIC_ASSERT(
same_as<ranges::partition_copy_result<int, double, void*>, ranges::in_out_out_result<int, double, void*>>);

// Validate dangling story
STATIC_ASSERT(same_as<decltype(ranges::partition_copy(borrowed<false>{}, nullptr_to<int>, nullptr_to<long>, is_even)),
ranges::partition_copy_result<ranges::dangling, int*, long*>>);
STATIC_ASSERT(same_as<decltype(ranges::partition_copy(borrowed<true>{}, nullptr_to<int>, nullptr_to<long>, is_even)),
ranges::partition_copy_result<int*, int*, long*>>);

struct empty_test {
template <ranges::input_range Range, indirectly_writable<ranges::range_reference_t<Range>> Out1,
indirectly_writable<ranges::range_reference_t<Range>> Out2>
static constexpr void call() {
// Validate empty ranges
using ranges::partition_copy, ranges::partition_copy_result, ranges::iterator_t;

{
Range range{};
auto result = partition_copy(range, Out1{}, Out2{}, is_even, get_first);
STATIC_ASSERT(same_as<decltype(partition_copy(range, Out1{}, Out2{}, is_even, get_first)),
partition_copy_result<iterator_t<Range>, Out1, Out2>>);
ASSERT(result.in == range.end());
ASSERT(result.out1.peek() == nullptr);
ASSERT(result.out2.peek() == nullptr);
}
{
Range range{};
auto result = partition_copy(range.begin(), range.end(), Out1{}, Out2{}, is_even, get_first);
STATIC_ASSERT(
same_as<decltype(partition_copy(range.begin(), range.end(), Out1{}, Out2{}, is_even, get_first)),
partition_copy_result<iterator_t<Range>, Out1, Out2>>);
ASSERT(result.in == range.end());
ASSERT(result.out1.peek() == nullptr);
ASSERT(result.out2.peek() == nullptr);
}
}
};

struct partition_copy_test {
static constexpr int N = 32;

template <ranges::input_range R, indirectly_writable<ranges::range_reference_t<R>> O1,
indirectly_writable<ranges::range_reference_t<R>> O2>
static constexpr void call() {
using ranges::partition_copy;

P source[N];
for (int i = 0; i < N; ++i) {
source[i] = {i, 42};
}

for (int i = 0; i < N; ++i) {
P dest[N];
ranges::fill(dest, P{-1, 13});

const R range{source};
auto result = partition_copy(
range, O1{dest}, O2{dest + i}, [i](int x) { return x < i; }, get_first);
assert(result.in == range.end());
assert(result.out1.peek() == dest + i);
assert(result.out2.peek() == dest + N);
assert(ranges::equal(source, dest));
}
}
};

template <class Instantiator, class Elem>
constexpr void run_tests() {
// Call Instantiator::template call</*...stuff...*/>() with a range whose element type is Elem, and two iterators to
// which Elem is writable, whose properties are "interesting" for ranges::partition_copy. What combinations of
// properties are "interesting"?

// For the input range, the algorithm simply unwraps iterators and chugs through looking for the end. It doesn't
// * take advantage of any capabilities provided by stronger-than-input categories,
// * care if the sentinel and iterator have the same type,
// * care if it can difference iterators with sentinels or each other, or
// * care about the size of the input range at all. (It can't even use size info to check the outputs, because we
// don't how many of the input elements will be written through each output.)
// TLDR: One input range with a proxy reference type and no other notable properties (the so-called "weakest" input
// range) suffices.

// For the outputs, both of which are treated equivalently, the algorithm is similarly oblivious to properties other
// than reference type and the ability to unwrap/rewrap. These could simply be the "weakest" writable iterator type
// in with_writable_iterators.

// Out of simple paranoia, let's permute ProxyRef; seven extra pointless tests won't hurt.

using test::range, test::iterator, test::input, test::output, test::CanCompare, test::CanDifference, test::Common,
test::ProxyRef, test::Sized;

using proxy_range = range<input, Elem, Sized::no, CanDifference::no, Common::no, CanCompare::no, ProxyRef::yes>;
using non_proxy_range = range<input, Elem, Sized::no, CanDifference::no, Common::no, CanCompare::no, ProxyRef::no>;
using proxy_iterator = iterator<output, remove_const_t<Elem>, CanDifference::no, CanCompare::no, ProxyRef::yes>;
using non_proxy_iterator = iterator<output, remove_const_t<Elem>, CanDifference::no, CanCompare::no, ProxyRef::no>;

Instantiator::template call<proxy_range, proxy_iterator, proxy_iterator>();
Instantiator::template call<proxy_range, proxy_iterator, non_proxy_iterator>();
Instantiator::template call<proxy_range, non_proxy_iterator, proxy_iterator>();
Instantiator::template call<proxy_range, non_proxy_iterator, non_proxy_iterator>();
Instantiator::template call<non_proxy_range, proxy_iterator, proxy_iterator>();
Instantiator::template call<non_proxy_range, proxy_iterator, non_proxy_iterator>();
Instantiator::template call<non_proxy_range, non_proxy_iterator, proxy_iterator>();
Instantiator::template call<non_proxy_range, non_proxy_iterator, non_proxy_iterator>();
}

int main() {
STATIC_ASSERT((run_tests<empty_test, const P>(), true));
run_tests<empty_test, const P>();

STATIC_ASSERT((run_tests<partition_copy_test, const P>(), true));
run_tests<partition_copy_test, const P>();
}
4 changes: 4 additions & 0 deletions tests/std/tests/P0896R4_ranges_alg_partition_point/env.lst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

RUNALL_INCLUDE ..\concepts_matrix.lst
64 changes: 64 additions & 0 deletions tests/std/tests/P0896R4_ranges_alg_partition_point/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include <algorithm>
#include <array>
#include <cassert>
#include <concepts>
#include <numeric>
#include <ranges>
#include <span>
#include <utility>

#include <range_algorithm_support.hpp>

using namespace std;

#define ASSERT(...) assert((__VA_ARGS__))

using P = pair<int, int>;

constexpr auto is_even = [](int i) { return i % 2 == 0; };

// Validate dangling story
STATIC_ASSERT(same_as<decltype(ranges::partition_point(borrowed<false>{}, is_even)), ranges::dangling>);
STATIC_ASSERT(same_as<decltype(ranges::partition_point(borrowed<true>{}, is_even)), int*>);

struct empty_test {
template <ranges::forward_range Range>
static constexpr void call() {
// Validate empty ranges
using ranges::partition_point;

const Range range{};
ASSERT(partition_point(range, is_even, get_first) == range.end());
ASSERT(partition_point(range.begin(), range.end(), is_even, get_first) == range.end());
}
};

struct partition_point_test {
template <ranges::forward_range R>
static constexpr void call() {
using ranges::partition_point;
int elements[200];
iota(ranges::begin(elements), ranges::end(elements), 0);

// to avoid constant expression step limits
const size_t bound = elements[0] + (is_constant_evaluated() ? 10 : ranges::size(elements));

for (size_t i = 0; i < bound; ++i) {
const R range{span{elements}.first(i)};
for (size_t j = 0; j < i; ++j) {
assert(partition_point(range, [j](int x) { return x < static_cast<int>(j); }).peek() == elements + j);
}
}
}
};

int main() {
STATIC_ASSERT((test_fwd<empty_test, P>(), true));
test_fwd<empty_test, P>();

STATIC_ASSERT((test_fwd<partition_point_test, const int>(), true));
test_fwd<partition_point_test, const int>();
}

0 comments on commit 6d441a1

Please sign in to comment.