Skip to content

Commit

Permalink
Optimize the is_permutation family and _Hash::operator== for multicon…
Browse files Browse the repository at this point in the history
…tainers (microsoft#423)

* Optimize the is_permutation family and _Hash::operator== for multicontaniers slightly.

<xutility>
4660: _Find_pr is a helper for is_permutation, so move it down to that area.
4684: The SHOUTY banners were attached to functions which were implmentation details of is_permutation, so I fixed them up to say is_permutation and removed the banners for helper functions.
4711: Use if constexpr to avoid a tag dispatch call for _Trim_matching_suffixes. Optimizers will like this because they generally hate reference-to-pointer, and it also serves to workaround DevCom-883631 when this algorithm is constexprized.
4766: Indicate that we are trimming matching prefixes in this loop body, and break apart comment block that was incorrectly merged by clang-format.
4817: In the dual range forward version of the algorithm, calculate the distances concurrently to avoid wasting lots of time when the distances vary by a lot. For example, is_permutation( a forward range of length 1, a forward range of length 1'000'000 ) used to do the million increments, now it stops at 1 increment.
4862: In the dual range random-access version, avoid recalculating _Last2 when it has already been supplied to us.

<xhash>
1404: Move down construction of _Bucket_hi in _Equal_range to before the first loop body using it.
1918: Added a new function to calculate equality for unordered multicontainers. We loop over the elements in the left container, find corresponding ranges in the right container, trim prefixes, then dispatch to is_permutation's helper _Check_match_counts.
Improvements over the old implementation:
* For standard containers, we no longer need to hash any elements from the left container; we know that we've found the "run" of equivalent elements because we *started* with an element in that container. We also never go "backwards" or multiply enumerate _Left (even for !_Standard), which improves cache use when the container becomes large.
* Just like the dual range is_permutation improvement above, when the equal_ranges of the containers are of wildly varying lengths, this will stop on the shorter of the lengths.
* We avoid the 3-arg is_permutation doing a linear time operation to discover _Last2 that we already had calculated in determining _Right's equal_range.
The function _Multi_equal_check_equal_range tests one equal_range from the left container against the corresponding equal_range from the right container, while _Multi_equal invokes _Multi_equal_check_equal_range for each equal_range.

Performance results:

```
Benchmark	Before (ns)	After (ns)	Percent Better
HashRandomUnequal<unordered_multimap>/1	18.7	11.7	59.83%
HashRandomUnequal<unordered_multimap>/10	137	97	41.24%
HashRandomUnequal<unordered_multimap>/100	1677	1141	46.98%
HashRandomUnequal<unordered_multimap>/512	10386	7036	47.61%
HashRandomUnequal<unordered_multimap>/4096	173807	119391	45.58%
HashRandomUnequal<unordered_multimap>/32768	2898405	1529710	89.47%
HashRandomUnequal<unordered_multimap>/100000	27441112	18602792	47.51%
HashRandomUnequal<hash_multimap>/1	18.9	11.8	60.17%
HashRandomUnequal<hash_multimap>/10	138	101	36.63%
HashRandomUnequal<hash_multimap>/100	1613	1154	39.77%
HashRandomUnequal<hash_multimap>/512	10385	7178	44.68%
HashRandomUnequal<hash_multimap>/4096	171718	120115	42.96%
HashRandomUnequal<hash_multimap>/32768	3352231	1510245	121.97%
HashRandomUnequal<hash_multimap>/100000	26532471	19209741	38.12%
HashRandomEqual<unordered_multimap>/1	16	9.4	70.21%
HashRandomEqual<unordered_multimap>/10	126	89.2	41.26%
HashRandomEqual<unordered_multimap>/100	1644	1133	45.10%
HashRandomEqual<unordered_multimap>/512	10532	7183	46.62%
HashRandomEqual<unordered_multimap>/4096	174580	120029	45.45%
HashRandomEqual<unordered_multimap>/32768	3031653	1455416	108.30%
HashRandomEqual<unordered_multimap>/100000	26100504	19240571	35.65%
HashRandomEqual<hash_multimap>/1	15.9	9.38	69.51%
HashRandomEqual<hash_multimap>/10	123	94.1	30.71%
HashRandomEqual<hash_multimap>/100	1645	1151	42.92%
HashRandomEqual<hash_multimap>/512	10177	7144	42.46%
HashRandomEqual<hash_multimap>/4096	172994	121381	42.52%
HashRandomEqual<hash_multimap>/32768	3045242	1966513	54.85%
HashRandomEqual<hash_multimap>/100000	26013781	22025482	18.11%
HashUnequalDifferingBuckets<unordered_multimap>/2	5.87	3.41	72.14%
HashUnequalDifferingBuckets<unordered_multimap>/10	12	3.39	253.98%
HashUnequalDifferingBuckets<unordered_multimap>/100	106	3.41	3008.50%
HashUnequalDifferingBuckets<unordered_multimap>/512	691	3.46	19871.10%
HashUnequalDifferingBuckets<unordered_multimap>/4096	6965	3.47	200620.46%
HashUnequalDifferingBuckets<unordered_multimap>/32768	91451	3.46	2642992.49%
HashUnequalDifferingBuckets<unordered_multimap>/100000	290430	3.52	8250752.27%
HashUnequalDifferingBuckets<hash_multimap>/2	5.97	3.4	75.59%
HashUnequalDifferingBuckets<hash_multimap>/10	11.8	3.54	233.33%
HashUnequalDifferingBuckets<hash_multimap>/100	105	3.54	2866.10%
HashUnequalDifferingBuckets<hash_multimap>/512	763	3.46	21952.02%
HashUnequalDifferingBuckets<hash_multimap>/4096	6862	3.4	201723.53%
HashUnequalDifferingBuckets<hash_multimap>/32768	94583	3.4	2781752.94%
HashUnequalDifferingBuckets<hash_multimap>/100000	287996	3.43	8396284.84%
```

Benchmark code:
```
#undef NDEBUG
#define _SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS
#include <assert.h>
#include <benchmark/benchmark.h>
#include <hash_map>
#include <random>
#include <stddef.h>
#include <unordered_map>
#include <utility>
#include <vector>

using namespace std;

template <template <class...> class MapType> void HashRandomUnequal(benchmark::State &state) {
    std::minstd_rand rng(std::random_device{}());
    const auto range0 = static_cast<ptrdiff_t>(state.range(0));
    vector<pair<unsigned, unsigned>> testData;
    testData.resize(range0 * 5);
    const auto dataEnd = testData.begin() + range0;
    std::generate(testData.begin(), dataEnd, [&]() { return pair<unsigned, unsigned>{rng(), 0u}; });
    std::copy(testData.begin(), dataEnd,
              std::copy(testData.begin(), dataEnd,
                        std::copy(testData.begin(), dataEnd, std::copy(testData.begin(), dataEnd, dataEnd))));
    std::unordered_multimap<unsigned, unsigned> a(testData.begin(), testData.end());
    testData.clear();
    std::unordered_multimap<unsigned, unsigned> b = a;
    next(b.begin(), b.size() - 1)->second = 1u;
    for (auto &&_ : state) {
        (void)_;
        assert(a != b);
    }
}

BENCHMARK_TEMPLATE1(HashRandomUnequal, unordered_multimap)->Arg(1)->Arg(10)->Range(100, 100'000);
BENCHMARK_TEMPLATE1(HashRandomUnequal, hash_multimap)->Arg(1)->Arg(10)->Range(100, 100'000);

template <template <class...> class MapType> void HashRandomEqual(benchmark::State &state) {
    std::minstd_rand rng(std::random_device{}());
    const auto range0 = static_cast<ptrdiff_t>(state.range(0));
    vector<pair<unsigned, unsigned>> testData;
    testData.resize(range0 * 5);
    const auto dataEnd = testData.begin() + range0;
    std::generate(testData.begin(), dataEnd, [&]() { return pair<unsigned, unsigned>{rng(), 0}; });
    std::copy(testData.begin(), dataEnd,
              std::copy(testData.begin(), dataEnd,
                        std::copy(testData.begin(), dataEnd, std::copy(testData.begin(), dataEnd, dataEnd))));
    std::unordered_multimap<unsigned, unsigned> a(testData.begin(), testData.end());
    testData.clear();
    std::unordered_multimap<unsigned, unsigned> b = a;
    for (auto &&_ : state) {
        (void)_;
        assert(a == b);
    }
}

BENCHMARK_TEMPLATE1(HashRandomEqual, unordered_multimap)->Arg(1)->Arg(10)->Range(100, 100'000);
BENCHMARK_TEMPLATE1(HashRandomEqual, hash_multimap)->Arg(1)->Arg(10)->Range(100, 100'000);

template <template <class...> class MapType> void HashUnequalDifferingBuckets(benchmark::State &state) {
    std::unordered_multimap<unsigned, unsigned> a;
    std::unordered_multimap<unsigned, unsigned> b;
    const auto range0 = static_cast<ptrdiff_t>(state.range(0));
    for (ptrdiff_t idx = 0; idx < range0; ++idx) {
        a.emplace(0, 1);
        b.emplace(1, 0);
    }

    a.emplace(1, 0);
    b.emplace(0, 1);
    for (auto &&_ : state) {
        (void)_;
        assert(a != b);
    }
}

BENCHMARK_TEMPLATE1(HashUnequalDifferingBuckets, unordered_multimap)->Arg(2)->Arg(10)->Range(100, 100'000);
BENCHMARK_TEMPLATE1(HashUnequalDifferingBuckets, hash_multimap)->Arg(2)->Arg(10)->Range(100, 100'000);

BENCHMARK_MAIN();

* Apply a bunch of code review comments from Casey.

* clang-format

* Apply @miscco's code deduplication idea for <xhash>.

* Fix code review comments from Stephan: comments and add DMIs.
  • Loading branch information
BillyONeal authored Jan 19, 2020
1 parent b3598a4 commit 2989323
Show file tree
Hide file tree
Showing 2 changed files with 194 additions and 71 deletions.
153 changes: 117 additions & 36 deletions stl/inc/xhash
Original file line number Diff line number Diff line change
Expand Up @@ -1401,14 +1401,14 @@ private:
template <class _Keyty>
_NODISCARD _Equal_range_result _Equal_range(const _Keyty& _Keyval, const size_t _Hashval) const
noexcept(_Nothrow_compare<_Traits, key_type, _Keyty>&& _Nothrow_compare<_Traits, _Keyty, key_type>) {
const size_type _Bucket = _Hashval & _Mask;
_Unchecked_const_iterator _Where = _Vec._Mypair._Myval2._Myfirst[_Bucket << 1];
const _Unchecked_const_iterator _Bucket_hi = _Vec._Mypair._Myval2._Myfirst[(_Bucket << 1) + 1];
const _Unchecked_const_iterator _End = _Unchecked_end();
const size_type _Bucket = _Hashval & _Mask;
_Unchecked_const_iterator _Where = _Vec._Mypair._Myval2._Myfirst[_Bucket << 1];
const _Unchecked_const_iterator _End = _Unchecked_end();
if (_Where == _End) {
return {_End, _End, 0};
}

const _Unchecked_const_iterator _Bucket_hi = _Vec._Mypair._Myval2._Myfirst[(_Bucket << 1) + 1];
for (; _Traitsobj(_Traits::_Kfn(*_Where), _Keyval); ++_Where) {
if (_Where == _Bucket_hi) {
return {_End, _End, 0};
Expand Down Expand Up @@ -1915,6 +1915,117 @@ protected:
return _List._Getal();
}

struct _Multi_equal_check_result {
bool _Equal_possible = false;
_Unchecked_const_iterator _Subsequent_first{}; // only useful if _Equal_possible
};

_NODISCARD _Multi_equal_check_result _Multi_equal_check_equal_range(
const _Hash& _Right, _Unchecked_const_iterator _First1) const {
// check that an equal_range of elements starting with *_First1 are a permutation of the corresponding
// equal_range of elements in _Right
auto& _Keyval = _Traits::_Kfn(*_First1);
// find the start of the matching run in the other container
const size_t _Hashval = _Right._Traitsobj(_Keyval);
const size_type _Bucket = _Hashval & _Right._Mask;
auto _First2 = _Right._Vec._Mypair._Myval2._Myfirst[_Bucket << 1];
if (_First2 == _Right._Unchecked_end()) {
// no matching bucket, therefore no matching run
return {};
}

const auto _Bucket_hi = _Right._Vec._Mypair._Myval2._Myfirst[(_Bucket << 1) + 1];
for (; _Right._Traitsobj(_Traits::_Kfn(*_First2), _Keyval); ++_First2) {
// find first matching element in _Right
if (_First2 == _Bucket_hi) {
return {};
}
}

_Unchecked_const_iterator _Left_stop_at;
if
_CONSTEXPR_IF(_Traits::_Standard) {
_Left_stop_at = _Unchecked_end();
}
else {
// check the first elements for equivalence when !_Standard
if (_Right._Traitsobj(_Keyval, _Traits::_Kfn(*_First2))) {
return {};
}

const size_t _LHashval = _Traitsobj(_Keyval);
const size_type _LBucket = _LHashval & _Mask;
const auto _LBucket_hi = _Vec._Mypair._Myval2._Myfirst[(_LBucket << 1) + 1];
_Left_stop_at = _LBucket_hi;
++_Left_stop_at;
}

// trim matching prefixes
while (*_First1 == *_First2) {
// the right equal_range ends at the end of the bucket or on the first nonequal element
bool _Right_range_end = _First2 == _Bucket_hi;
++_First2;
if (!_Right_range_end) {
_Right_range_end = _Right._Traitsobj(_Keyval, _Traits::_Kfn(*_First2));
}

// the left equal_range ends at the end of the container or on the first nonequal element
++_First1;
const bool _Left_range_end = _First1 == _Left_stop_at || _Traitsobj(_Keyval, _Traits::_Kfn(*_First1));

if (_Left_range_end && _Right_range_end) {
// the equal_ranges were completely equal
return {true, _First1};
}

if (_Left_range_end || _Right_range_end) {
// one equal_range is a prefix of the other; not equal
return {};
}
}

// found a mismatched element, find the end of the equal_ranges and dispatch to _Check_match_counts
auto _Last1 = _First1;
auto _Last2 = _First2;
for (;;) {
bool _Right_range_end = _Last2 == _Bucket_hi;
++_Last2;
if (!_Right_range_end) {
_Right_range_end = _Right._Traitsobj(_Keyval, _Traits::_Kfn(*_Last2));
}

++_Last1;
const bool _Left_range_end = _Last1 == _Left_stop_at || _Traitsobj(_Keyval, _Traits::_Kfn(*_Last1));

if (_Left_range_end && _Right_range_end) {
// equal_ranges had the same length, check for permutation
return {_Check_match_counts(_First1, _Last1, _First2, _Last2, equal_to<>{}), _Last1};
}

if (_Left_range_end || _Right_range_end) {
// different number of elements in the range, not a permutation
return {};
}
}
}

_NODISCARD bool _Multi_equal(const _Hash& _Right) const {
static_assert(_Traits::_Multi, "This function only works with multi containers");
_STL_INTERNAL_CHECK(this->size() == _Right.size());
const auto _Last1 = _Unchecked_end();
auto _First1 = _Unchecked_begin();
while (_First1 != _Last1) {
const auto _Result = _Multi_equal_check_equal_range(_Right, _First1);
if (!_Result._Equal_possible) {
return false;
}

_First1 = _Result._Subsequent_first;
}

return true;
}

#ifdef _ENABLE_STL_INTERNAL_CHECK
public:
void _Stl_internal_check_container_invariants() const noexcept {
Expand Down Expand Up @@ -2018,23 +2129,7 @@ _NODISCARD bool _Hash_equal_elements(const _Hash<_Traits>& _Left, const _Hash<_T

template <class _Traits>
_NODISCARD bool _Hash_equal_elements(const _Hash<_Traits>& _Left, const _Hash<_Traits>& _Right, true_type) {
const auto _Left_end = _Left._Unchecked_end();
for (auto _Next1 = _Left._Unchecked_begin(); _Next1 != _Left_end;) { // look for elements with equivalent keys
const auto& _Keyval = _Traits::_Kfn(*_Next1);
const size_t _Lhashval = _Left._Traitsobj(_Keyval);
const size_t _Rhashval = _Right._Traitsobj(_Keyval); // P0809 prohibits reusing _Lhashval
const auto _Lrange = _Left._Equal_range(_Keyval, _Lhashval);
const auto _Rrange = _Right._Equal_range(_Keyval, _Rhashval);

if (_Lrange._Distance != _Rrange._Distance
|| !_Is_permutation_unchecked(_Lrange._First, _Lrange._Last, _Rrange._First, equal_to<>{})) {
return false;
}

_Next1 = _Lrange._Last; // continue just past range
}

return true;
return _Left._Multi_equal(_Right);
}
#endif // !_HAS_IF_CONSTEXPR

Expand All @@ -2046,21 +2141,7 @@ _NODISCARD bool _Hash_equal(const _Hash<_Traits>& _Left, const _Hash<_Traits>& _

#if _HAS_IF_CONSTEXPR
if constexpr (_Traits::_Multi) {
const auto _Left_end = _Left._Unchecked_end();
for (auto _Next1 = _Left._Unchecked_begin(); _Next1 != _Left_end;) { // look for elements with equivalent keys
const auto& _Keyval = _Traits::_Kfn(*_Next1);
const size_t _Lhashval = _Left._Traitsobj(_Keyval);
const size_t _Rhashval = _Right._Traitsobj(_Keyval); // P0809 prohibits reusing _Lhashval
const auto _Lrange = _Left._Equal_range(_Keyval, _Lhashval);
const auto _Rrange = _Right._Equal_range(_Keyval, _Rhashval);

if (_Lrange._Distance != _Rrange._Distance
|| !_Is_permutation_unchecked(_Lrange._First, _Lrange._Last, _Rrange._First, equal_to<>{})) {
return false;
}

_Next1 = _Lrange._Last; // continue just past range
}
return _Left._Multi_equal(_Right);
} else {
for (const auto& _LVal : _Left) {
// look for element with equivalent key
Expand Down
112 changes: 77 additions & 35 deletions stl/inc/xutility
Original file line number Diff line number Diff line change
Expand Up @@ -4658,18 +4658,6 @@ template <class _ExPo, class _FwdIt, class _Ty, _Enable_if_execution_policy_t<_E
_NODISCARD _FwdIt find(_ExPo&& _Exec, _FwdIt _First, const _FwdIt _Last, const _Ty& _Val) noexcept; // terminates
#endif // _HAS_CXX17

// FUNCTION TEMPLATE _Find_pr
template <class _InIt, class _Ty, class _Pr>
_InIt _Find_pr(_InIt _First, _InIt _Last, const _Ty& _Val, _Pr _Pred) { // find first matching _Val, using _Pred
for (; _First != _Last; ++_First) {
if (_Pred(*_First, _Val)) {
break;
}
}

return _First;
}

// FUNCTION TEMPLATE count
template <class _InIt, class _Ty>
_NODISCARD _Iter_diff_t<_InIt> count(const _InIt _First, const _InIt _Last, const _Ty& _Val) {
Expand All @@ -4694,10 +4682,20 @@ _NODISCARD _Iter_diff_t<_FwdIt> count(
_ExPo&& _Exec, const _FwdIt _First, const _FwdIt _Last, const _Ty& _Val) noexcept; // terminates
#endif // _HAS_CXX17

// FUNCTION TEMPLATE _Count_pr
// FUNCTION TEMPLATE is_permutation
template <class _InIt, class _Ty, class _Pr>
_Iter_diff_t<_InIt> _Count_pr(_InIt _First, _InIt _Last, const _Ty& _Val, _Pr _Pred) {
// count elements that match _Val, using _Pred
_NODISCARD _InIt _Find_pr(_InIt _First, const _InIt _Last, const _Ty& _Val, _Pr _Pred) {
for (; _First != _Last; ++_First) {
if (_Pred(*_First, _Val)) {
break;
}
}

return _First;
}

template <class _InIt, class _Ty, class _Pr>
_NODISCARD _Iter_diff_t<_InIt> _Count_pr(_InIt _First, const _InIt _Last, const _Ty& _Val, _Pr _Pred) {
_Iter_diff_t<_InIt> _Count = 0;

for (; _First != _Last; ++_First) {
Expand All @@ -4709,7 +4707,7 @@ _Iter_diff_t<_InIt> _Count_pr(_InIt _First, _InIt _Last, const _Ty& _Val, _Pr _P
return _Count;
}

// FUNCTION TEMPLATE _Trim_matching_suffixes
#if !_HAS_IF_CONSTEXPR
template <class _FwdIt1, class _FwdIt2, class _Pr>
void _Trim_matching_suffixes(_FwdIt1&, _FwdIt2&, _Pr, forward_iterator_tag, forward_iterator_tag) {
// trim matching suffixes, forward iterators (do nothing)
Expand All @@ -4727,39 +4725,53 @@ void _Trim_matching_suffixes(
++_Last1;
++_Last2;
}
#endif // !_HAS_IF_CONSTEXPR

// FUNCTION TEMPLATE _Check_match_counts
template <class _FwdIt1, class _FwdIt2, class _Pr>
bool _Check_match_counts(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _FwdIt2 _Last2, _Pr _Pred) {
// test if [_First1, _Last1) == permuted [_First2, _Last2), using _Pred, same lengths
_NODISCARD bool _Check_match_counts(
const _FwdIt1 _First1, _FwdIt1 _Last1, const _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));
#if _HAS_IF_CONSTEXPR
if constexpr (_Is_bidi_iter_v<_FwdIt1> && _Is_bidi_iter_v<_FwdIt2>) {
do { // find last inequality
--_Last1;
--_Last2;
} while (_Pred(*_Last1, *_Last2));
++_Last1;
++_Last2;
}
#else // ^^^ _HAS_IF_CONSTEXPR // !_HAS_IF_CONSTEXPR vvv
_Trim_matching_suffixes(_Last1, _Last2, _Pred, _Iter_cat_t<_FwdIt1>(), _Iter_cat_t<_FwdIt2>());
#endif // _HAS_IF_CONSTEXPR
for (_FwdIt1 _Next1 = _First1; _Next1 != _Last1; ++_Next1) {
if (_Next1 == _Find_pr(_First1, _Next1, *_Next1, _Pred)) { // new value, compare match counts
_Iter_diff_t<_FwdIt2> _Count2 = _Count_pr(_First2, _Last2, *_Next1, _Pred);
if (_Count2 == 0) {
return false; // second range lacks value, fail
return false; // second range lacks value, not a permutation
}

_FwdIt1 _Skip1 = _Next_iter(_Next1);
_Iter_diff_t<_FwdIt1> _Count1 = _Count_pr(_Skip1, _Last1, *_Next1, _Pred) + 1;
if (_Count2 != _Count1) {
return false; // match counts differ, fail
return false; // match counts differ, not a permutation
}
}
}

return true;
}

// FUNCTION TEMPLATE is_permutation
template <class _FwdIt1, class _FwdIt2, class _Pr>
bool _Is_permutation_unchecked(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _Pr _Pred) {
_NODISCARD bool _Is_permutation_unchecked(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _Pr _Pred) {
// test if [_First1, _Last1) == permuted [_First2, ...), using _Pred
for (; _First1 != _Last1; ++_First1, (void) ++_First2) {
for (; _First1 != _Last1; ++_First1, (void) ++_First2) { // trim matching prefix
if (!_Pred(*_First1, *_First2)) {
// found first inequality, check match counts in suffix narrowing _Iter_diff_t<_FwdIt1> to
// _Iter_diff_t<_FwdIt2> is OK because if the 2nd range is shorter than the 1st, the user already
// triggered UB
// found first inequality, check match counts in suffix
//
// narrowing _Iter_diff_t<_FwdIt1> to _Iter_diff_t<_FwdIt2> is OK because if the second range is shorter
// than the first, the user already triggered UB
auto _Last2 = _STD next(_First2, static_cast<_Iter_diff_t<_FwdIt2>>(_STD distance(_First1, _Last1)));
return _Check_match_counts(_First1, _Last1, _First2, _Last2, _Pred);
}
Expand Down Expand Up @@ -4805,17 +4817,41 @@ template <class _FwdIt1, class _FwdIt2, class _Pr>
bool _Is_permutation_unchecked(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _FwdIt2 _Last2, _Pr _Pred,
forward_iterator_tag, forward_iterator_tag) {
// test if [_First1, _Last1) == permuted [_First2, _Last2), using _Pred, arbitrary iterators
for (; _First1 != _Last1 && _First2 != _Last2; ++_First1, (void) ++_First2) {
for (;;) { // trim matching prefix
if (_First1 == _Last1) {
return _First2 == _Last2;
}

if (_First2 == _Last2) {
return false;
}

if (!_Pred(*_First1, *_First2)) { // found first inequality, check match counts in suffix
if (_STD distance(_First1, _Last1) == _STD distance(_First2, _Last2)) {
break;
}

++_First1;
++_First2;
}

auto _Next1 = _First1;
auto _Next2 = _First2;
for (;;) { // check for same lengths
if (_Next1 == _Last1) {
if (_Next2 == _Last2) {
return _Check_match_counts(_First1, _Last1, _First2, _Last2, _Pred);
} else {
return false; // lengths differ, fail
}

return false; // sequence 1 is shorter than sequence 2, not a permutation
}
}

return _First1 == _Last1 && _First2 == _Last2;
if (_Next2 == _Last2) {
return false; // sequence 1 is longer than sequence 2, not a permutation
}

++_Next1;
++_Next2;
}
}

template <class _FwdIt1, class _FwdIt2, class _Pr>
Expand All @@ -4826,7 +4862,14 @@ bool _Is_permutation_unchecked(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2,
return false;
}

return _Is_permutation_unchecked(_First1, _Last1, _First2, _Pred);
for (; _First1 != _Last1; ++_First1, (void) ++_First2) { // trim matching prefix
if (!_Pred(*_First1, *_First2)) {
// found first inequality, check match counts in suffix
return _Check_match_counts(_First1, _Last1, _First2, _Last2, _Pred);
}
}

return true;
}

template <class _FwdIt1, class _FwdIt2, class _Pr>
Expand All @@ -4838,7 +4881,6 @@ _NODISCARD bool is_permutation(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2,
_Get_unwrapped(_Last2), _Pass_fn(_Pred), _Iter_cat_t<_FwdIt1>(), _Iter_cat_t<_FwdIt2>());
}

// FUNCTION TEMPLATE is_permutation WITH TWO RANGES
template <class _FwdIt1, class _FwdIt2>
_NODISCARD bool is_permutation(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _FwdIt2 _Last2) {
// test if [_First1, _Last1) == permuted [_First2, _Last2)
Expand Down

0 comments on commit 2989323

Please sign in to comment.