Skip to content

Commit c23272c

Browse files
committed
[libc++] Introduce use of __lower_bound_onesided to improve average complexity of set_intersection.
1 parent 36bb63e commit c23272c

File tree

1 file changed

+150
-4
lines changed

1 file changed

+150
-4
lines changed

libcxx/include/__algorithm/set_intersection.h

Lines changed: 150 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,13 @@
1212
#include <__algorithm/comp.h>
1313
#include <__algorithm/comp_ref_type.h>
1414
#include <__algorithm/iterator_operations.h>
15+
#include <__algorithm/lower_bound.h>
1516
#include <__config>
17+
#include <__functional/identity.h>
1618
#include <__iterator/iterator_traits.h>
1719
#include <__iterator/next.h>
20+
#include <__type_traits/is_same.h>
21+
#include <__utility/exchange.h>
1822
#include <__utility/move.h>
1923

2024
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
@@ -36,9 +40,122 @@ struct __set_intersection_result {
3640
};
3741

3842
template <class _AlgPolicy, class _Compare, class _InIter1, class _Sent1, class _InIter2, class _Sent2, class _OutIter>
39-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 __set_intersection_result<_InIter1, _InIter2, _OutIter>
40-
__set_intersection(
41-
_InIter1 __first1, _Sent1 __last1, _InIter2 __first2, _Sent2 __last2, _OutIter __result, _Compare&& __comp) {
43+
struct _LIBCPP_NODISCARD_EXT __set_intersector {
44+
_InIter1& __first1_;
45+
const _Sent1& __last1_;
46+
_InIter2& __first2_;
47+
const _Sent2& __last2_;
48+
_OutIter& __result_;
49+
_Compare& __comp_;
50+
static constexpr auto __proj_ = std::__identity();
51+
bool __prev_advanced_ = true;
52+
53+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 __set_intersector(
54+
_InIter1& __first1, _Sent1& __last1, _InIter2& __first2, _Sent2& __last2, _OutIter& __result, _Compare& __comp)
55+
: __first1_(__first1),
56+
__last1_(__last1),
57+
__first2_(__first2),
58+
__last2_(__last2),
59+
__result_(__result),
60+
__comp_(__comp) {}
61+
62+
_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI
63+
_LIBCPP_CONSTEXPR_SINCE_CXX20 __set_intersection_result<_InIter1, _InIter2, _OutIter>
64+
operator()() && {
65+
while (__first2_ != __last2_) {
66+
__advance1_and_maybe_add_result();
67+
if (__first1_ == __last1_)
68+
break;
69+
__advance2_and_maybe_add_result();
70+
}
71+
return __set_intersection_result<_InIter1, _InIter2, _OutIter>(
72+
_IterOps<_AlgPolicy>::next(std::move(__first1_), std::move(__last1_)),
73+
_IterOps<_AlgPolicy>::next(std::move(__first2_), std::move(__last2_)),
74+
std::move(__result_));
75+
}
76+
77+
private:
78+
// advance __iter to the first element in the range where !__comp_(__iter, __value)
79+
// add result if this is the second consecutive call without advancing
80+
// this method only works if you alternate calls between __advance1_and_maybe_add_result() and
81+
// __advance2_and_maybe_add_result()
82+
template <class _Iter, class _Sent, class _Value>
83+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void
84+
__advance_and_maybe_add_result(_Iter& __iter, const _Sent& __sentinel, const _Value& __value) {
85+
// use one-sided lower bound for improved algorithmic complexity bounds
86+
const auto __tmp =
87+
std::exchange(__iter, std::__lower_bound_onesided<_AlgPolicy>(__iter, __sentinel, __value, __comp_, __proj_));
88+
__add_output_unless(__tmp != __iter);
89+
}
90+
91+
// advance __first1_ to the first element in the range where !__comp_(*__first1_, *__first2_)
92+
// add result if neither __first1_ nor __first2_ advanced in the last attempt (meaning they are equal)
93+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __advance1_and_maybe_add_result() {
94+
__advance_and_maybe_add_result(__first1_, __last1_, *__first2_);
95+
}
96+
97+
// advance __first2_ to the first element in the range where !__comp_(*__first2_, *__first1_)
98+
// add result if neither __first1_ nor __first2_ advanced in the last attempt (meaning they are equal)
99+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __advance2_and_maybe_add_result() {
100+
__advance_and_maybe_add_result(__first2_, __last2_, *__first1_);
101+
}
102+
103+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __add_output_unless(bool __advanced) {
104+
if (__advanced | __prev_advanced_) {
105+
__prev_advanced_ = __advanced;
106+
} else {
107+
*__result_ = *__first1_;
108+
++__result_;
109+
++__first1_;
110+
++__first2_;
111+
__prev_advanced_ = true;
112+
}
113+
}
114+
};
115+
116+
// with forward iterators we can use binary search to skip over entries
117+
template <class _AlgPolicy,
118+
class _Compare,
119+
class _InForwardIter1,
120+
class _Sent1,
121+
class _InForwardIter2,
122+
class _Sent2,
123+
class _OutIter>
124+
_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI
125+
_LIBCPP_CONSTEXPR_SINCE_CXX20 __set_intersection_result<_InForwardIter1, _InForwardIter2, _OutIter>
126+
__set_intersection(
127+
_InForwardIter1 __first1,
128+
_Sent1 __last1,
129+
_InForwardIter2 __first2,
130+
_Sent2 __last2,
131+
_OutIter __result,
132+
_Compare&& __comp,
133+
std::forward_iterator_tag,
134+
std::forward_iterator_tag) {
135+
std::__set_intersector<_AlgPolicy, _Compare, _InForwardIter1, _Sent1, _InForwardIter2, _Sent2, _OutIter>
136+
__intersector(__first1, __last1, __first2, __last2, __result, __comp);
137+
return std::move(__intersector)();
138+
}
139+
140+
// input iterators are not suitable for multipass algorithms, so we stick to the classic single-pass version
141+
template <class _AlgPolicy,
142+
class _Compare,
143+
class _InInputIter1,
144+
class _Sent1,
145+
class _InInputIter2,
146+
class _Sent2,
147+
class _OutIter>
148+
_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI
149+
_LIBCPP_CONSTEXPR_SINCE_CXX20 __set_intersection_result<_InInputIter1, _InInputIter2, _OutIter>
150+
__set_intersection(
151+
_InInputIter1 __first1,
152+
_Sent1 __last1,
153+
_InInputIter2 __first2,
154+
_Sent2 __last2,
155+
_OutIter __result,
156+
_Compare&& __comp,
157+
std::input_iterator_tag,
158+
std::input_iterator_tag) {
42159
while (__first1 != __last1 && __first2 != __last2) {
43160
if (__comp(*__first1, *__first2))
44161
++__first1;
@@ -52,12 +169,41 @@ __set_intersection(
52169
}
53170
}
54171

55-
return __set_intersection_result<_InIter1, _InIter2, _OutIter>(
172+
return std::__set_intersection_result<_InInputIter1, _InInputIter2, _OutIter>(
56173
_IterOps<_AlgPolicy>::next(std::move(__first1), std::move(__last1)),
57174
_IterOps<_AlgPolicy>::next(std::move(__first2), std::move(__last2)),
58175
std::move(__result));
59176
}
60177

178+
template <class _AlgPolicy, class _Iter>
179+
class __set_intersection_iter_category {
180+
template <class _It>
181+
using __cat = typename std::_IterOps<_AlgPolicy>::template __iterator_category<_It>;
182+
template <class _It>
183+
static auto test(__cat<_It>*) -> __cat<_It>;
184+
template <class>
185+
static std::input_iterator_tag test(...);
186+
187+
public:
188+
using __type = decltype(test<_Iter>(nullptr));
189+
};
190+
191+
template <class _AlgPolicy, class _Compare, class _InIter1, class _Sent1, class _InIter2, class _Sent2, class _OutIter>
192+
_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI
193+
_LIBCPP_CONSTEXPR_SINCE_CXX20 __set_intersection_result<_InIter1, _InIter2, _OutIter>
194+
__set_intersection(
195+
_InIter1 __first1, _Sent1 __last1, _InIter2 __first2, _Sent2 __last2, _OutIter __result, _Compare&& __comp) {
196+
return std::__set_intersection<_AlgPolicy>(
197+
std::move(__first1),
198+
std::move(__last1),
199+
std::move(__first2),
200+
std::move(__last2),
201+
std::move(__result),
202+
std::forward<_Compare>(__comp),
203+
typename std::__set_intersection_iter_category<_AlgPolicy, _InIter1>::__type(),
204+
typename std::__set_intersection_iter_category<_AlgPolicy, _InIter2>::__type());
205+
}
206+
61207
template <class _InputIterator1, class _InputIterator2, class _OutputIterator, class _Compare>
62208
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _OutputIterator set_intersection(
63209
_InputIterator1 __first1,

0 commit comments

Comments
 (0)