Difference between revisions of "cpp/algorithm/ranges/lower bound"
m (fix numbering) |
m (FTM.) |
||
(14 intermediate revisions by 9 users not shown) | |||
Line 2: | Line 2: | ||
{{cpp/algorithm/ranges/navbar}} | {{cpp/algorithm/ranges/navbar}} | ||
{{dcl begin}} | {{dcl begin}} | ||
− | {{dcl header | algorithm}} | + | {{dcl header|algorithm}} |
− | {{dcl h | Call signature}} | + | {{dcl h|Call signature}} |
− | {{dcl | num=1 | since=c++20 |1= | + | {{dcl rev begin|num=1}} |
+ | {{dcla|anchor=1|since=c++20|until=c++26|1= | ||
template< std::forward_iterator I, std::sentinel_for<I> S, | template< std::forward_iterator I, std::sentinel_for<I> S, | ||
class T, class Proj = std::identity, | class T, class Proj = std::identity, | ||
− | std::indirect_strict_weak_order | + | std::indirect_strict_weak_order |
− | const T*, | + | <const T*, std::projected<I, Proj>> Comp = ranges::less > |
− | + | constexpr I lower_bound( I first, S last, const T& value, | |
− | constexpr I | + | Comp comp = {}, Proj proj = {} ); |
− | lower_bound( I first, S last, const T& value, Comp comp = {}, Proj proj = {} ); | + | |
}} | }} | ||
− | {{dcl | num=2 | since=c++20 |1= | + | {{dcl|since=c++26|1= |
− | template< ranges::forward_range R, class T, class Proj = std::identity, | + | template< std::forward_iterator I, std::sentinel_for<I> S, |
− | std::indirect_strict_weak_order | + | class Proj = std::identity, |
− | const T*, | + | class T = std::projected_value_t<I, Proj>, |
− | std::projected<ranges::iterator_t<R>, Proj>> Comp = ranges::less > | + | std::indirect_strict_weak_order |
− | + | <const T*, std::projected<I, Proj>> Comp = ranges::less > | |
− | lower_bound( R&& r, const T& value, Comp comp = {}, Proj proj = {} ); | + | constexpr I lower_bound( I first, S last, const T& value, |
+ | Comp comp = {}, Proj proj = {} ); | ||
+ | }} | ||
+ | {{dcl rev end}} | ||
+ | {{dcl rev begin|num=2}} | ||
+ | {{dcl|since=c++20|until=c++26|1= | ||
+ | template< ranges::forward_range R, | ||
+ | class T, class Proj = std::identity, | ||
+ | std::indirect_strict_weak_order | ||
+ | <const T*, std::projected<ranges::iterator_t<R>, | ||
+ | Proj>> Comp = ranges::less > | ||
+ | constexpr ranges::borrowed_iterator_t<R> | ||
+ | lower_bound( R&& r, const T& value, Comp comp = {}, Proj proj = {} ); | ||
+ | }} | ||
+ | {{dcl|since=c++26|1= | ||
+ | template< ranges::forward_range R, | ||
+ | class Proj = std::identity, | ||
+ | class T = std::projected_value_t<ranges::iterator_t<R>, Proj> | ||
+ | std::indirect_strict_weak_order | ||
+ | <const T*, std::projected<ranges::iterator_t<R>, | ||
+ | Proj>> Comp = ranges::less > | ||
+ | constexpr ranges::borrowed_iterator_t<R> | ||
+ | lower_bound( R&& r, const T& value, Comp comp = {}, Proj proj = {} ); | ||
}} | }} | ||
+ | {{dcl rev end}} | ||
{{dcl end}} | {{dcl end}} | ||
− | @1@ Returns an iterator pointing to the first element in the range {{ | + | @1@ Returns an iterator pointing to the first element in the range {{range|first|last}} that is ''not less'' than (i.e. greater or equal to) {{c|value}}, or {{c|last}} if no such element is found. |
− | The range {{ | + | The range {{range|first|last}} must be partitioned with respect to the expression {{c|std::invoke(comp, std::invoke(proj, element), value)}}, i.e., all elements for which the expression is {{c|true}} must precede all elements for which the expression is {{c|false}}. A fully-sorted range meets this criterion. |
− | @2@ Same as {{v|1}}, but uses {{ | + | @2@ Same as {{v|1}}, but uses {{c|r}} as the source range, as if using {{c|ranges::begin(r)}} as {{c|first}} and {{c|ranges::end(r)}} as {{c|last}}. |
{{cpp/ranges/niebloid}} | {{cpp/ranges/niebloid}} | ||
Line 32: | Line 55: | ||
===Parameters=== | ===Parameters=== | ||
{{par begin}} | {{par begin}} | ||
− | {{par | first, last | iterator-sentinel pair defining the partially-ordered range to examine}} | + | {{par|first, last|iterator-sentinel pair defining the partially-ordered range to examine}} |
− | {{par | r | the partially-ordered range to examine}} | + | {{par|r|the partially-ordered range to examine}} |
− | {{par | value | value to compare the elements to}} | + | {{par|value|value to compare the projected elements to}} |
− | {{par | | + | {{par|comp|comparison predicate to apply to the projected elements}} |
− | {{par | proj | projection to apply to the elements}} | + | {{par|proj|projection to apply to the elements}} |
{{par end}} | {{par end}} | ||
===Return value=== | ===Return value=== | ||
− | Iterator pointing to the first element that is ''not less'' than {{ | + | Iterator pointing to the first element that is ''not less'' than {{c|value}}, or {{c|last}} if no such element is found. |
===Complexity=== | ===Complexity=== | ||
− | The number of comparisons and applications of the projection performed are logarithmic in the distance between {{ | + | The number of comparisons and applications of the projection performed are logarithmic in the distance between {{c|first}} and {{c|last}} (at most {{math|log{{su|b=2}}(last - first) + O(1)}} comparisons and applications of the projection). However, for an iterator that does not model {{lconcept|random_access_iterator}}, the number of iterator increments is linear. |
− | === | + | ===Notes=== |
− | {{ | + | On a range that's fully sorted (or more generally, partially ordered with respect to {{c|value}}) after projection, {{tt|std::ranges::lower_bound}} implements the binary search algorithm. Therefore, {{lc|ranges::binary_search|std::ranges::binary_search}} can be implemented in terms of it. |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | {{feature test macro|__cpp_lib_algorithm_default_value_type|value=202403|std=C++26|[[cpp/language/list initialization|List-initialization]] for algorithms {{vl|1,2}}}} | |
− | + | ||
− | + | ===Possible implementation=== | |
− | + | {{eq fun|1= | |
− | + | struct lower_bound_fn | |
− | + | { | |
− | + | template<std::forward_iterator I, std::sentinel_for<I> S, class Proj = std::identity, | |
− | + | class T = std::projected_value_t<I, Proj>, | |
− | + | std::indirect_strict_weak_order | |
− | + | <const T*, std::projected<I, Proj>> Comp = ranges::less> | |
+ | constexpr I operator()(I first, S last, const T& value, | ||
+ | Comp comp = {}, Proj proj = {}) const | ||
+ | { | ||
+ | I it; | ||
+ | std::iter_difference_t<I> count, step; | ||
+ | count = std::ranges::distance(first, last); | ||
+ | |||
+ | while (count > 0) | ||
+ | { | ||
+ | it = first; | ||
+ | step = count / 2; | ||
+ | ranges::advance(it, step, last); | ||
+ | if (comp(std::invoke(proj, *it), value)) | ||
+ | { | ||
+ | first = ++it; | ||
+ | count -= step + 1; | ||
+ | } | ||
+ | else | ||
+ | count = step; | ||
+ | } | ||
+ | return first; | ||
+ | } | ||
+ | |||
+ | template<ranges::forward_range R, class Proj = std::identity, | ||
+ | class T = std::projected_value_t<ranges::iterator_t<R>, Proj> | ||
+ | std::indirect_strict_weak_order | ||
+ | <const T*, std::projected<ranges::iterator_t<R>, | ||
+ | Proj>> Comp = ranges::less> | ||
+ | constexpr ranges::borrowed_iterator_t<R> | ||
+ | operator()(R&& r, const T& value, Comp comp = {}, Proj proj = {}) const | ||
+ | { | ||
+ | return (*this)(ranges::begin(r), ranges::end(r), value, | ||
+ | std::ref(comp), std::ref(proj)); | ||
+ | } | ||
}; | }; | ||
Line 92: | Line 122: | ||
===Example=== | ===Example=== | ||
{{example | {{example | ||
− | + | |code= | |
− | + | ||
#include <algorithm> | #include <algorithm> | ||
+ | #include <cassert> | ||
+ | #include <complex> | ||
#include <iostream> | #include <iostream> | ||
#include <iterator> | #include <iterator> | ||
Line 103: | Line 134: | ||
template<std::forward_iterator I, std::sentinel_for<I> S, class T, | template<std::forward_iterator I, std::sentinel_for<I> S, class T, | ||
class Proj = std::identity, | class Proj = std::identity, | ||
− | std::indirect_strict_weak_order< | + | std::indirect_strict_weak_order |
− | + | <const T*, std::projected<I, Proj>> Comp = ranges::less> | |
− | + | constexpr I binary_find(I first, S last, const T& value, Comp comp = {}, Proj proj = {}) | |
− | I binary_find(I first, S last, const T& value, Comp comp = {}, Proj proj = {}) | + | |
{ | { | ||
first = ranges::lower_bound(first, last, value, comp, proj); | first = ranges::lower_bound(first, last, value, comp, proj); | ||
− | return first != last && !comp(value, *first) ? first : last; | + | return first != last && !comp(value, proj(*first)) ? first : last; |
} | } | ||
int main() | int main() | ||
{ | { | ||
− | std::vector | + | std::vector data{1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5}; |
− | + | // ^^^^^^^^^^ | |
auto lower = ranges::lower_bound(data, 4); | auto lower = ranges::lower_bound(data, 4); | ||
auto upper = ranges::upper_bound(data, 4); | auto upper = ranges::upper_bound(data, 4); | ||
+ | std::cout << "found a range [" << ranges::distance(data.cbegin(), lower) | ||
+ | << ", " << ranges::distance(data.cbegin(), upper) << ") = { "; | ||
ranges::copy(lower, upper, std::ostream_iterator<int>(std::cout, " ")); | ranges::copy(lower, upper, std::ostream_iterator<int>(std::cout, " ")); | ||
− | + | std::cout << "}\n"; | |
− | std::cout << | + | |
// classic binary search, returning a value only if it is present | // classic binary search, returning a value only if it is present | ||
− | data = { 1, 2, 4, | + | data = {1, 2, 4, 8, 16}; |
+ | // ^ | ||
+ | auto it = binary_find(data.cbegin(), data.cend(), 8); // '5' would return end() | ||
− | + | if (it != data.cend()) | |
− | + | std::cout << *it << " found at index "<< ranges::distance(data.cbegin(), it); | |
− | if(it != data.cend()) | + | |
− | + | ||
− | return 0; | + | using CD = std::complex<double>; |
+ | std::vector<CD> nums{{1, 0}, {2, 2}, {2, 1}, {3, 0}}; | ||
+ | auto cmpz = [](CD x, CD y) { return x.real() < y.real(); }; | ||
+ | #ifdef __cpp_lib_algorithm_default_value_type | ||
+ | auto it2 = ranges::lower_bound(nums, {2, 0}, cmpz); | ||
+ | #else | ||
+ | auto it2 = ranges::lower_bound(nums, CD{2, 0}, cmpz); | ||
+ | #endif | ||
+ | assert((*it2 == CD{2, 2})); | ||
} | } | ||
− | + | |output= | |
− | 4 4 4 | + | found a range [6, 10) = { 4 4 4 4 } |
− | 4 found at index | + | 8 found at index 3 |
}} | }} | ||
===See also=== | ===See also=== | ||
{{dsc begin}} | {{dsc begin}} | ||
− | {{dsc inc | cpp/algorithm/ranges/dsc equal_range}} | + | {{dsc inc|cpp/algorithm/ranges/dsc equal_range}} |
− | {{dsc inc | cpp/algorithm/ranges/dsc partition}} | + | {{dsc inc|cpp/algorithm/ranges/dsc partition}} |
− | {{dsc inc | cpp/algorithm/ranges/dsc partition_point}} | + | {{dsc inc|cpp/algorithm/ranges/dsc partition_point}} |
− | {{dsc inc | cpp/algorithm/ranges/dsc upper_bound}} | + | {{dsc inc|cpp/algorithm/ranges/dsc upper_bound}} |
− | {{dsc inc | cpp/algorithm/dsc lower_bound}} | + | {{dsc inc|cpp/algorithm/dsc lower_bound}} |
{{dsc end}} | {{dsc end}} | ||
{{langlinks|es|ja|zh}} | {{langlinks|es|ja|zh}} |
Latest revision as of 21:32, 20 May 2024
Defined in header <algorithm>
|
||
Call signature |
||
(1) | ||
template< std::forward_iterator I, std::sentinel_for<I> S, class T, class Proj = std::identity, |
(since C++20) (until C++26) |
|
template< std::forward_iterator I, std::sentinel_for<I> S, class Proj = std::identity, |
(since C++26) | |
(2) | ||
template< ranges::forward_range R, class T, class Proj = std::identity, |
(since C++20) (until C++26) |
|
template< ranges::forward_range R, class Proj = std::identity, |
(since C++26) | |
[
first,
last)
that is not less than (i.e. greater or equal to) value, or last if no such element is found.
The range [
first,
last)
must be partitioned with respect to the expression std::invoke(comp, std::invoke(proj, element), value), i.e., all elements for which the expression is true must precede all elements for which the expression is false. A fully-sorted range meets this criterion.The function-like entities described on this page are niebloids, that is:
- Explicit template argument lists cannot be specified when calling any of them.
- None of them are visible to argument-dependent lookup.
- When any of them are found by normal unqualified lookup as the name to the left of the function-call operator, argument-dependent lookup is inhibited.
In practice, they may be implemented as function objects, or with special compiler extensions.
Contents |
[edit] Parameters
first, last | - | iterator-sentinel pair defining the partially-ordered range to examine |
r | - | the partially-ordered range to examine |
value | - | value to compare the projected elements to |
comp | - | comparison predicate to apply to the projected elements |
proj | - | projection to apply to the elements |
[edit] Return value
Iterator pointing to the first element that is not less than value, or last if no such element is found.
[edit] Complexity
The number of comparisons and applications of the projection performed are logarithmic in the distance between first and last (at most log2(last - first) + O(1) comparisons and applications of the projection). However, for an iterator that does not model random_access_iterator
, the number of iterator increments is linear.
[edit] Notes
On a range that's fully sorted (or more generally, partially ordered with respect to value) after projection, std::ranges::lower_bound
implements the binary search algorithm. Therefore, std::ranges::binary_search can be implemented in terms of it.
Feature-test macro | Value | Std | Feature |
---|---|---|---|
__cpp_lib_algorithm_default_value_type |
202403 | (C++26) | List-initialization for algorithms (1,2) |
[edit] Possible implementation
struct lower_bound_fn { template<std::forward_iterator I, std::sentinel_for<I> S, class Proj = std::identity, class T = std::projected_value_t<I, Proj>, std::indirect_strict_weak_order <const T*, std::projected<I, Proj>> Comp = ranges::less> constexpr I operator()(I first, S last, const T& value, Comp comp = {}, Proj proj = {}) const { I it; std::iter_difference_t<I> count, step; count = std::ranges::distance(first, last); while (count > 0) { it = first; step = count / 2; ranges::advance(it, step, last); if (comp(std::invoke(proj, *it), value)) { first = ++it; count -= step + 1; } else count = step; } return first; } template<ranges::forward_range R, class Proj = std::identity, class T = std::projected_value_t<ranges::iterator_t<R>, Proj> std::indirect_strict_weak_order <const T*, std::projected<ranges::iterator_t<R>, Proj>> Comp = ranges::less> constexpr ranges::borrowed_iterator_t<R> operator()(R&& r, const T& value, Comp comp = {}, Proj proj = {}) const { return (*this)(ranges::begin(r), ranges::end(r), value, std::ref(comp), std::ref(proj)); } }; inline constexpr lower_bound_fn lower_bound; |
[edit] Example
#include <algorithm> #include <cassert> #include <complex> #include <iostream> #include <iterator> #include <vector> namespace ranges = std::ranges; template<std::forward_iterator I, std::sentinel_for<I> S, class T, class Proj = std::identity, std::indirect_strict_weak_order <const T*, std::projected<I, Proj>> Comp = ranges::less> constexpr I binary_find(I first, S last, const T& value, Comp comp = {}, Proj proj = {}) { first = ranges::lower_bound(first, last, value, comp, proj); return first != last && !comp(value, proj(*first)) ? first : last; } int main() { std::vector data{1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5}; // ^^^^^^^^^^ auto lower = ranges::lower_bound(data, 4); auto upper = ranges::upper_bound(data, 4); std::cout << "found a range [" << ranges::distance(data.cbegin(), lower) << ", " << ranges::distance(data.cbegin(), upper) << ") = { "; ranges::copy(lower, upper, std::ostream_iterator<int>(std::cout, " ")); std::cout << "}\n"; // classic binary search, returning a value only if it is present data = {1, 2, 4, 8, 16}; // ^ auto it = binary_find(data.cbegin(), data.cend(), 8); // '5' would return end() if (it != data.cend()) std::cout << *it << " found at index "<< ranges::distance(data.cbegin(), it); using CD = std::complex<double>; std::vector<CD> nums{{1, 0}, {2, 2}, {2, 1}, {3, 0}}; auto cmpz = [](CD x, CD y) { return x.real() < y.real(); }; #ifdef __cpp_lib_algorithm_default_value_type auto it2 = ranges::lower_bound(nums, {2, 0}, cmpz); #else auto it2 = ranges::lower_bound(nums, CD{2, 0}, cmpz); #endif assert((*it2 == CD{2, 2})); }
Output:
found a range [6, 10) = { 4 4 4 4 } 8 found at index 3
[edit] See also
(C++20) |
returns range of elements matching a specific key (niebloid) |
(C++20) |
divides a range of elements into two groups (niebloid) |
(C++20) |
locates the partition point of a partitioned range (niebloid) |
(C++20) |
returns an iterator to the first element greater than a certain value (niebloid) |
returns an iterator to the first element not less than the given value (function template) |