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
60 changes: 56 additions & 4 deletions stl/inc/algorithm
Original file line number Diff line number Diff line change
Expand Up @@ -978,8 +978,6 @@ namespace ranges {
--_Final1;
--_Final2;
if (!_STD invoke(_Pred, _STD invoke(_Proj1, *_Final1), _STD invoke(_Proj2, *_Final2))) { // mismatch
++_Final1;
++_Final2;
break;
}

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

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)));
};

for (;;) {
_TrimResult _Res = _Trim_reversed(_First1, _Final1, _First2, _Final2, _ProjectedPred);
if (_Res != _TrimResult::_KeepTrimming) {
if (_Res == _TrimResult::_Break) {
break;
}
return _Res == _TrimResult::_ReturnTrue;
}

_Res = _Trim_equal(_First1, _Final1, _First2, _Final2, _ProjectedPred);
if (_Res != _TrimResult::_KeepTrimming) {
if (_Res == _TrimResult::_Break) {
break;
}
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

return _Match_counts(_STD move(_First1), _STD move(_Final1), _STD move(_First2), _STD move(_Final2),
_Pred, _Proj1, _Proj2);
} else {
Expand Down Expand Up @@ -1046,13 +1072,39 @@ 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.

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)));
};

for (;;) {
_TrimResult _Res = _Trim_reversed(_First1, _Final1, _First2, _Final2, _ProjectedPred);
if (_Res != _TrimResult::_KeepTrimming) {
if (_Res == _TrimResult::_Break) {
break;
}
return _Res == _TrimResult::_ReturnTrue;
}

_Res = _Trim_equal(_First1, _Final1, _First2, _Final2, _ProjectedPred);
if (_Res != _TrimResult::_KeepTrimming) {
if (_Res == _TrimResult::_Break) {
break;
}
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.
}

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

enum class _TrimResult : unsigned char { _KeepTrimming, _Break, _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 and 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, break
return _TrimResult::_Break;
}
--_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 prefix matching 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, break
return _TrimResult::_Break;
}
--_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 _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 @@ -5295,6 +5373,29 @@ _NODISCARD _CONSTEXPR20 bool _Check_match_counts(
--_Last1;
--_Last2;
} while (_Pred(*_Last1, *_Last2));

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

for (;;) {
_TrimResult _Res = _Trim_reversed(_First1, _Last1, _First2, _Last2, _Pred);
if (_Res != _TrimResult::_KeepTrimming) {
if (_Res == _TrimResult::_Break) {
break;
}
return _Res == _TrimResult::_ReturnTrue;
}

_Res = _Trim_equal(_First1, _Last1, _First2, _Last2, _Pred);
if (_Res != _TrimResult::_KeepTrimming) {
if (_Res == _TrimResult::_Break) {
break;
}
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 @@ -496,6 +496,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 @@ -514,6 +518,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