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

Optimize is_permutation for reversed sequences #2043

Merged
merged 12 commits into from
Nov 13, 2021
56 changes: 52 additions & 4 deletions stl/inc/algorithm
Original file line number Diff line number Diff line change
Expand Up @@ -985,8 +985,6 @@ namespace ranges {
--_Final1;
--_Final2;
if (!_STD invoke(_Pred, _STD invoke(_Proj1, *_Final1), _STD invoke(_Proj2, *_Final2))) { // mismatch
++_Final1;
++_Final2;
break;
}

Expand All @@ -996,6 +994,32 @@ namespace ranges {
}
// If we get here, _Count > 1, initial elements do not match, and final elements do not match.

// We've trimmed matching prefixes and matching suffixes.
// Now we need to compare each range's prefix to the other range's suffix.

const auto _ProjectedPred = [&]<class _Ty1, class _Ty2>(_Ty1&& _Left, _Ty2&& _Right) {
return _STD invoke(_Pred, _STD invoke(_Proj1, _STD forward<_Ty1>(_Left)),
_STD invoke(_Proj2, _STD forward<_Ty2>(_Right)));
};

const _TrimResult _Res = _Trim_completely(_First1, _Final1, _First2, _Final2, _ProjectedPred);

if (_Res != _TrimResult::_HaveWorkAfterTrimming) {
return _Res == _TrimResult::_ReturnTrue;
}

++_Final1;
++_Final2;
// If we get here, initial elements do not match, final elements do not match, and ranges have length
// at least 2 and at most _Count.

// We've trimmed matching prefixes, matching suffixes,
// and each range's prefix matching the other range's suffix. That is, given:
// Range 1: [A, ..., B]
// Range 2: [X, ..., Y]
// we know that A != X, A != Y, B != X, and B != Y.
// (A == B and X == Y are possible but irrelevant.)

return _Match_counts(_STD move(_First1), _STD move(_Final1), _STD move(_First2), _STD move(_Final2),
_Pred, _Proj1, _Proj2);
} else {
Expand Down Expand Up @@ -1053,13 +1077,37 @@ namespace ranges {
--_Final2; // since ranges have equal lengths, _Final2 cannot equal _First2

if (!_STD invoke(_Pred, _STD invoke(_Proj1, *_Final1), _STD invoke(_Proj2, *_Final2))) { // mismatch
++_Final1;
++_Final2;
break;
}
}
// If we get here, initial elements do not match, final elements do not match, and ranges have length
// at least 2.

// We've trimmed matching prefixes and matching suffixes.
// Now we need to compare each range's prefix to the other range's suffix.

const auto _ProjectedPred = [&]<class _Ty1, class _Ty2>(_Ty1&& _Left, _Ty2&& _Right) {
return _STD invoke(_Pred, _STD invoke(_Proj1, _STD forward<_Ty1>(_Left)),
_STD invoke(_Proj2, _STD forward<_Ty2>(_Right)));
};

const _TrimResult _Res = _Trim_completely(_First1, _Final1, _First2, _Final2, _ProjectedPred);

if (_Res != _TrimResult::_HaveWorkAfterTrimming) {
return _Res == _TrimResult::_ReturnTrue;
}

++_Final1;
++_Final2;
// If we get here, initial elements do not match, final elements do not match, and ranges have length
// at least 2.

// We've trimmed matching prefixes, matching suffixes,
// and each range's prefix matching the other range's suffix. That is, given:
// Range 1: [A, ..., B]
// Range 2: [X, ..., Y]
// we know that A != X, A != Y, B != X, and B != Y.
// (A == B and X == Y are possible but irrelevant.)
}

return _Match_counts(
Expand Down
110 changes: 109 additions & 1 deletion stl/inc/xutility
Original file line number Diff line number Diff line change
Expand Up @@ -5348,9 +5348,106 @@ _NODISCARD constexpr _Iter_diff_t<_InIt> _Count_pr(_InIt _First, const _InIt _La
return _Count;
}

enum class _TrimResult : unsigned char { _KeepTrimming, _HaveWorkAfterTrimming, _ReturnFalse, _ReturnTrue };

template <class _BidIt1, class _BidIt2, class _Pr>
_NODISCARD _CONSTEXPR20 _TrimResult _Trim_equal(
_BidIt1& _First1, _BidIt1& _Back1, _BidIt2& _First2, _BidIt2& _Back2, _Pr _Pred) {
// advances the iterators, trimming matching prefixes then matching suffixes
// from [_First1, _Back1] and [_First2, _Back2]
_STL_INTERNAL_CHECK(_First1 != _Back1);
_STL_INTERNAL_CHECK(_STD distance(_First1, _Back1) == _STD distance(_First2, _Back2));
if (_Pred(*_First1, *_First2)) {
do {
++_First1;
++_First2;
if (_First1 == _Back1) {
// only one element is left
return _Pred(*_First1, *_First2) ? _TrimResult::_ReturnTrue : _TrimResult::_ReturnFalse;
}
} while (_Pred(*_First1, *_First2));
} else {
if (!_Pred(*_Back1, *_Back2)) {
// nothing to trim
return _TrimResult::_HaveWorkAfterTrimming;
}
--_Back1;
--_Back2;
}

for (;;) {
if (_First1 == _Back1) {
// only one element is left, it can't match because it wasn't trimmed by the first loop
return _TrimResult::_ReturnFalse;
}

if (!_Pred(*_Back1, *_Back2)) {
return _TrimResult::_KeepTrimming;
}
--_Back1;
--_Back2;
}
}

template <class _BidIt1, class _BidIt2, class _Pr>
_NODISCARD _CONSTEXPR20 _TrimResult _Trim_reversed(
_BidIt1& _First1, _BidIt1& _Back1, _BidIt2& _First2, _BidIt2& _Back2, _Pr _Pred) {
// advances the iterators, trimming each range's prefix that matches the other range's suffix
// from [_First1, _Back1] and [_First2, _Back2]
_STL_INTERNAL_CHECK(_First1 != _Back1);
_STL_INTERNAL_CHECK(_STD distance(_First1, _Back1) == _STD distance(_First2, _Back2));
if (_Pred(*_First1, *_Back2)) {
do {
++_First1;
--_Back2;
if (_First1 == _Back1) {
// only one element is left
return _Pred(*_First1, *_First2) ? _TrimResult::_ReturnTrue : _TrimResult::_ReturnFalse;
}
} while (_Pred(*_First1, *_Back2));
} else {
if (!_Pred(*_Back1, *_First2)) {
// nothing to trim
return _TrimResult::_HaveWorkAfterTrimming;
}
--_Back1;
++_First2;
}

for (;;) {
if (_First1 == _Back1) {
// only one element is left, it can't match because it wasn't trimmed by the first loop
return _TrimResult::_ReturnFalse;
}

if (!_Pred(*_Back1, *_First2)) {
return _TrimResult::_KeepTrimming;
}
--_Back1;
++_First2;
}
}

template <class _BidIt1, class _BidIt2, class _Pr>
_NODISCARD _CONSTEXPR20 _TrimResult _Trim_completely(
_BidIt1& _First1, _BidIt1& _Back1, _BidIt2& _First2, _BidIt2& _Back2, _Pr _Pred) {
// alternates between calling _Trim_reversed and _Trim_equal until no more trimming is possible
_TrimResult _Res = _TrimResult::_KeepTrimming;

for (bool _Check_reversed = true; _Res == _TrimResult::_KeepTrimming; _Check_reversed = !_Check_reversed) {
if (_Check_reversed) {
_Res = _Trim_reversed(_First1, _Back1, _First2, _Back2, _Pred);
} else {
_Res = _Trim_equal(_First1, _Back1, _First2, _Back2, _Pred);
}
}

return _Res;
}

template <class _FwdIt1, class _FwdIt2, class _Pr>
_NODISCARD _CONSTEXPR20 bool _Check_match_counts(
const _FwdIt1 _First1, _FwdIt1 _Last1, const _FwdIt2 _First2, _FwdIt2 _Last2, _Pr _Pred) {
_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _FwdIt2 _Last2, _Pr _Pred) {
// test if [_First1, _Last1) == permuted [_First2, _Last2), after matching prefix removal
_STL_INTERNAL_CHECK(!_Pred(*_First1, *_First2));
_STL_INTERNAL_CHECK(_STD distance(_First1, _Last1) == _STD distance(_First2, _Last2));
Expand All @@ -5359,6 +5456,17 @@ _NODISCARD _CONSTEXPR20 bool _Check_match_counts(
--_Last1;
--_Last2;
} while (_Pred(*_Last1, *_Last2));

if (_First1 == _Last1) {
return false;
}

const _TrimResult _Res = _Trim_completely(_First1, _Last1, _First2, _Last2, _Pred);

if (_Res != _TrimResult::_HaveWorkAfterTrimming) {
return _Res == _TrimResult::_ReturnTrue;
}

++_Last1;
++_Last2;
}
Expand Down
26 changes: 26 additions & 0 deletions tests/std/tests/Dev11_0000000_dual_range_algorithms/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,32 @@ int main() {
assert(!is_permutation(a.begin(), a.end(), b.begin(), b.end(), equal_to<int>()));
}

{
int arr1[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int arr2[] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
assert(is_permutation(begin(arr1), end(arr1), begin(arr2), end(arr2)));
}
{
int arr1[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int arr2[] = {9, 8, 7, 3, 4, 5, 6, 2, 1, 0};
assert(is_permutation(begin(arr1), end(arr1), begin(arr2), end(arr2)));
}
{
int arr1[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int arr2[] = {9, 1, 7, 3, 5, 4, 6, 2, 8, 0};
assert(is_permutation(begin(arr1), end(arr1), begin(arr2), end(arr2)));
}
{
int arr1[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int arr2[] = {9, 1, 7, 5, 6, 3, 4, 2, 8, 0};
assert(is_permutation(begin(arr1), end(arr1), begin(arr2), end(arr2)));
}
{
int arr1[] = {0, 1, 2, 3, 4, 10, 5, 6, 7, 8, 9};
int arr2[] = {9, 1, 7, 3, 5, 11, 4, 6, 2, 8, 0};
assert(!is_permutation(begin(arr1), end(arr1), begin(arr2), end(arr2)));
}

{ // Test that _ITERATOR_DEBUG_ARRAY_OVERLOADS is not needed anymore
int arr[8] = {};
assert(mismatch(arr, arr, arr, arr) == make_pair(begin(arr), begin(arr)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,10 @@ constexpr void test_permutations() {
{40, 30, 20, 10},
};

for (size_t i = 0; i < size(expected); ++i) {
assert(is_permutation(begin(buff), end(buff), begin(expected[i]), end(expected[i])));
}

size_t cursor = 0;
do {
assert(equal(begin(buff), end(buff), begin(expected[cursor]), end(expected[cursor])));
Expand All @@ -518,6 +522,32 @@ constexpr void test_permutations() {

assert(cursor == 0);
assert(equal(begin(buff), end(buff), begin(expected[23]), end(expected[23])));

{
int arr1[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int arr2[] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
assert(is_permutation(begin(arr1), end(arr1), begin(arr2), end(arr2)));
}
{
int arr1[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int arr2[] = {9, 8, 7, 3, 4, 5, 6, 2, 1, 0};
assert(is_permutation(begin(arr1), end(arr1), begin(arr2), end(arr2)));
}
{
int arr1[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int arr2[] = {9, 1, 7, 3, 5, 4, 6, 2, 8, 0};
assert(is_permutation(begin(arr1), end(arr1), begin(arr2), end(arr2)));
}
{
int arr1[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int arr2[] = {9, 1, 7, 5, 6, 3, 4, 2, 8, 0};
assert(is_permutation(begin(arr1), end(arr1), begin(arr2), end(arr2)));
}
{
int arr1[] = {0, 1, 2, 3, 4, 10, 5, 6, 7, 8, 9};
int arr2[] = {9, 1, 7, 3, 5, 11, 4, 6, 2, 8, 0};
assert(!is_permutation(begin(arr1), end(arr1), begin(arr2), end(arr2)));
}
}

constexpr bool test() {
Expand Down
46 changes: 46 additions & 0 deletions tests/std/tests/P0896R4_ranges_alg_is_permutation/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,52 @@ struct instantiator {
assert(!ranges::is_permutation(ranges::begin(r1), ranges::end(r1), ranges::begin(r2), ranges::end(r2),
ranges::equal_to{}, get_first, identity{}));
}
{
int arr1[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int arr2[] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
const Fwd2 r1{arr1};
const Fwd2 r2{arr2};
assert(ranges::is_permutation(r1, r2));
}
{
int arr1[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int arr2[] = {9, 8, 7, 3, 4, 5, 6, 2, 1, 0};
const Fwd2 r1{arr1};
const Fwd2 r2{arr2};
assert(ranges::is_permutation(r1, r2));
}
{
int arr1[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int arr2[] = {9, 1, 7, 3, 5, 4, 6, 2, 8, 0};
const Fwd2 r1{arr1};
const Fwd2 r2{arr2};
assert(ranges::is_permutation(r1, r2));
}
{
int arr1[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int arr2[] = {9, 1, 7, 5, 6, 3, 4, 2, 8, 0};
const Fwd2 r1{arr1};
const Fwd2 r2{arr2};
assert(ranges::is_permutation(r1, r2));
}
{
int arr1[] = {0, 1, 2, 3, 4, 10, 5, 6, 7, 8, 9};
int arr2[] = {9, 1, 7, 3, 5, 11, 4, 6, 2, 8, 0};
const Fwd2 r1{arr1};
const Fwd2 r2{arr2};
assert(!ranges::is_permutation(r1, r2));
}
{
int arr1[] = {-1, 0, 1, 2, 3, 4, 5, 6, 7, 8};
int arr2[] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
const Fwd2 r1{arr1};
const Fwd2 r2{arr2};
assert(!ranges::is_permutation(r1, r2));
assert(!ranges::is_permutation(r1, r2, {}, {}, [](int n) { return n - 1; }));
assert(ranges::is_permutation(r1, r2, {}, {}, [](int n) { return n - 2; }));
assert(ranges::is_permutation(
r1, r2, {}, [](int n) { return n + 1; }, [](int n) { return n - 1; }));
}
}
};

Expand Down