std::set::equal_range for a container of std::pair - c++

I'm trying to find all the ranges [a,b] enclosing int values i, where a <= i <= b. I'm using set<std:pair<int,int>> for the set of ranges.
In the following, using equal range on a vector<int> yields the start and one past the end of the range.
When I do the same for a set<pair<int,int>>, the result starts and ends at one past the end of the range and therefore doesn't include the range enclosing the value.
#include <set>
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
int ia[] = {1,2,3,4,5,6,7,8,9,10};
set<int> s1(begin(ia),end(ia));
auto range1 = s1.equal_range(5);
cout << *range1.first << " " << *range1.second << endl; //prints 5 6
pair<int,int> p[] = {make_pair(1,10),
make_pair(11,20),
make_pair(21,30),
make_pair(31,40)};
set<pair<int,int>> s(begin(p), end(p));
auto range = s.equal_range(make_pair(12,12));
cout << range.first->first << " " << range.first->second << endl; //prints 21 30, why?
cout << range.second->first << " " << range.second->second << endl; //prints 21 30
}
prints 5 6
21 30
21 30
Why does equal_range on the set<pair<int,int>> not include the range that encloses the value (12), namely [11.20]

equal_range is behaving completely correctly:
assert( std::make_pair(11, 20) < std::make_pair(12, 12) );
assert( std::make_pair(12, 12) < std::make_pair(21, 30) );
[11,20] is not a range, it's a pair. The pair [12,12] is not "within" another pair, that makes no sense to even say.
[12,12] is not "within" [11,20], it's greater than it. The less-than operator for std::pair compares the first elements first, and only if they're equal does it look at the second elements, so make_pair(11,x) is less than make_pair(12, y) for any x and y
So equal_range tells you that [12,12] would be inserted after [11,20] and before [21,30], which is correct.
If you want to treat pairs as ranges of values you need to write code to do that, not assume the built-in comparisons for pairs does that. You're actually trying to find an int 12 in a range of pairs of ints, but have written code to find a pair [12,12] in a range of pairs of ints. That's not the same thing.

It does not include [11, 20] in the range, because it doesn't include anything in the range. There are no equal elements to [12, 12], so it returns an empty range (represented by the half-open interval [x, x)).
BTW dereferencing the upper bound of the range may invoke undefined behavior, since that may be equal to s.end().

The pair [12, 12] is sorted after [11, 20] and before [21, 30].
std::set::equal_range() includes a range of equal elements. There is no equal element in your set (especially not [11, 20]), so equal_range() returns [21, 30], [21, 30].

equal_range is implemented as to call lower_bound first then call upper_bound to search the rest data set.
template <class ForwardIterator, class T>
pair<ForwardIterator,ForwardIterator>
equal_range ( ForwardIterator first, ForwardIterator last, const T& value )
{
ForwardIterator it = lower_bound (first,last,value);
return make_pair ( it, upper_bound(it,last,value) );
}
Look at your sample:
It calls lower_bound to locate the lower bound of value(which is pair(12,12), which arrives at
pair<int,int> p[] = {make_pair(1,10),
make_pair(11,20),
make_pair(21,30), // <--- lower_bound() points to here
make_pair(31,40)};
Then it calls upper_bound() to search on (21,30),(31,40) and it cound't find it, it returns (21,30)
http://www.cplusplus.com/reference/algorithm/upper_bound/

I don't think your std::set<std::pair<int, int> > won't help you much in intersecting it with your integer: You can find the s.lower_bound(std::make_pair(i + 1, i + 1) to cut off the search but all ranges starting at an index lower than i + 1 can potentially include the value i if the second boundary is large enough. What might help is if you know the maximum size of the ranges in which case you can bound the search towards the front by s.lower_bound(std::make_pair(i - max_range_size, i - max_range_size)). You'll need to inspect each of the ranges in turn to identify if your i falls into them:
auto it = s.lower_bound(std::make_pair(i - max_range_size, i - max_range_size));
auto upper = s.lower_bound(std::make_pair(i + 1, i + 1));
for (; it != upper; ++it) {
if (i < it->second) {
std::cout << "range includes " << i << ": "
<< [" << it.first << ", " << it->second << "]\n";
}
(or something like this...)

Related

Range transformations with stateful lambdas and std::views::drop

it's my first time digging into the new <ranges> library and I tried a little experiment combining std::views::transform with a stateful lambda and 'piping' the resulting range to std::views::drop:
#include <iostream>
#include <ranges>
#include <vector>
using namespace std;
int main() {
auto aggregator = [sum = 0](int val) mutable
{
return sum += val;
};
vector<int> data{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
cout << "Expected:\n";
int sum = 0;
for (int val : data) {
cout << (sum += val) << ' ';
}
cout << '\n';
cout << "Transformation:\n- - - ";
for (int val : data | views::transform(aggregator) | views::drop(3)) {
cout << val << ' ';
}
cout << '\n';
}
The output was:
Expected:
1 3 6 10 15 21 28 36 45 55
Transformation:
- - - 4 9 15 22 30 39 49
Now, the difference between each expected and actual output is 1 + 2 + 3 = 6. I am guessing it is a result of the lazy evaluation of ranges that causes std::views::drop to disregard the first three transformations.
Is there a way for me to force the evaluation of the aggregator functor for the three elements I drop? Or are stateful lambdas and ranges considered incompatible?
transform_view is required to be a pure function. This is codified in the regular_invocable concept:
The invoke function call expression shall be equality-preserving and shall not modify the function object or the arguments.
This is important to allow transform_view to not lie about its iterator status. Forward iterators, for example, are supposed to allow multipass iteration. This means that the value at each iterator position within the range must be independent of any other iterator position. That's not possible if the transformation functor is not pure.
Note that all predicate functors are also regular_invocable. So this also applies to things like filter_view and take_while_view.
Note that the algorithm transform does not have this requirement.

Using std::find() With Reverse Iterators

I'm having some trouble understanding how to use reverse iterators with the std::find() function. I believe that if I could see an example that completed the following task, I would be able to understand it perfectly.
So, suppose I have a std::vector I want to search through; however, I do not want to search the typical way. I want to find the first occurrence of a value starting at a certain index and heading towards the start of the vector. To illustrate:
3 | 4 | 7| 4| 2| 6| 3|
^ ^
|<------------|
start_point
Search: find first occurrence, given the above search layout, of 4
Expected Result: index 3
I'm rather sure that one would have to work with reverse iterators in this situation, but I can't figure out how to do it.
If you're using a std::vector, or any other container that provides Random Access Iterators, you can advance an iterator just using arithmetic, like you would with a pointer. Your example vector has 7 elements, and you want to start at index 4, so you could get a normal iterator to that element just with:
auto i = v.begin() + 4;
For a reverse iterator, you're starting from the back of the vector, not the front, so to get the right offset you have to subtract the desired index+1 from the size, like so:
auto i = v.rbegin() + (v.size() - 5);
This'll be, in your example, 2, so the reverse iterator will start pointing to the last element, then move two spaces toward the beginning, reaching your desired start point.
Then, you can use std::find in the normal way:
auto found = std::find(v.rbegin() + (v.size() - 5), v.rend(), 4);
if(found == v.rend()) {
std::cout << "No element found." << std::endl;
} else {
std::cout << "Index " << (v.rend() - found) << std::endl;
}
Remember that, when testing the result of std::find to see if it found anything, you need to use rend(), not end(). When you compare reverse iterators to normal iterators, you're comparing the actual positions, not the offsets from the start, so v.rend() != v.end().
If you don't have Random Access Iterators (for example, in a std::list) you can't use pointer-style arithmetic, so you can instead use std::advance to advance iterators to a specific position and std::distance to get the distance between two iterators.
First you set the start position:
auto it = v.rbegin() + 2; // two from the end
Then search:
auto kt = std::find(it, v.rend(), 4);
If kt == v.rend(), no element is found; otherwise we can compute the index from the front with a simple distance computation:
if (kt == v.rend()) {
std::cerr << "Element 4 not found.\n";
std::abort();
} else {
auto n = std::distance(kt, v.rend()) - 1;
std::cout << "Element 4 found at position v[" << n << "].\n";
}
Try something like the following
#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>
int main()
{
std::vector<int> v = { 3, 4, 7, 4, 2, 6, 3 };
std::vector<int>::size_type pos = 4;
auto it = std::find(std::next(v.rbegin(), v.size() - pos), v.rend(), 4);
if (it != v.rend())
{
std::cout << std::distance(v.begin(), it.base() - 1) << std::endl;
}
}

Find the last non-zero element in a std::vector

I'm trying to find the index of the last non-zero element in a std::vector<double>. If the last element in the vector is non-zero then it should return the index of that last element.
I believe I can use std::find_if_not, reverse iterators and std::distance, based on this:
std::find_if_not(amounts.rbegin(), amounts.rend(), 0.0)
where amounts is a std::vector<double>, but I'm having difficulty in combining this with std::distance and a forward iterator amounts.begin().
Also, is there a way I can introduce a predicate to compare on, say a tolerance of 1e-8?
I'm using C++11.
Example:
std::vector<double> v{1.32, 1.423, 2.543, 3.534, 4.2, 0};
auto result1 = std::find_if(std::rbegin(v), std::rend(v), [](auto& v) { return std::fabs(v - 0) > std::numeric_limits<double>::epsilon(); } );
if (result1 != std::rend(v)) {
std::cout << *result1 << "\n";
std::cout << std::distance(std::begin(v), (result1 + 1).base());
}
outputs:
4.2
4
[edit]
more explanation on:
std::fabs(v - 0) > std::numeric_limits<double>::epsilon(); }
in OP question there was:
Also, is there a way I can introduce a predicate to compare on, say a tolerance of 1e-8?
so this is such tolerance check, you can replace epsilon use with some other value.
A simple for loop can also do the trick, see live sample: http://ideone.com/dVNOKk
#include <iostream>
#include <vector>
int main() {
std::vector<int> v{1, 2, 3, 4, 1, 2, 3, 0, 4, 1, 2, 3, 4, 0, 0, 0};
for (int i = static_cast<int>(v.size()) - 1; i >= 0; --i) {
if (v.at(i) != 0) {
std::cout << "Last non-zero at: " << i << '\n';
break;
}
}
return 0;
}
Output: Last non-zero at: 12
but I'm having difficulty in combining this with std::distance and a forward iterator amounts.begin()
Reverse iterators have member function base that returns a non-reverse iterator with the relation &*(rit.base() - 1) == &*rit. So, you can use the following:
std::distance(amounts.begin(), found.base()) - 1;
Another option:
amounts.size() - std::distance(amounts.rbegin(), found) - 1
Also, is there a way I can introduce a predicate to compare on, say a tolerance of 1e-8?
Yes. In fact, you must use a predicate, even for the exact comparison, since that's what std::find_if_not expects as the third parameter (instead of value of an element).

Finding number of pairs having sum as 0 in two different arrays

If I have two seperate sorted arrays, containing equal number of entries, and I need to find the number of pairs(both numbers should be from seperate arrays) having sum = 0 in linear time, how can I do that?
I can easily do it in O(n^2) but how to do it in linear time?
OR should I merge the two arrays and then proceed?
Thanks!
You don't need the arrays to be sorted.
Stick the numbers from one of the arrays into a hash table. Then iterate over the other array. For each number n, see if -n is in the hash table.
(If either array can contain duplicates, you need to take some care around handling them.)
P.S. You can exploit the fact that the arrays are sorted. Just iterate over them from the opposite ends once, looking for items that have the same value but the opposite signs. I leave figuring out the details as an exercise (hint: think of the merge step of merge sort).
Try this:
for(i=0;j=0;i<n&&j<n;)
{
if(arr1[i]+arr2[j]==0)
{
count++;
i++;
j++;
}
else if(arr[i]>arr[j])
{
j++;
}
else
{
i++;
}
}
Following may help:
std::size_t count_zero_pair(const std::vector<int>& v1, const std::vector<int>& v2)
{
assert(is_sorted(v1.begin(), v1.end()));
assert(is_sorted(v2.begin(), v2.end()));
std::size_t res = 0;
auto it1 = v1.begin();
auto it2 = v2.rbegin();
while (it1 != v1.end() && it2 != v2.rend()) {
const int sum = *it1 + *it2;
if (sum < 0) {
++it1;
} else if (0 < sum) {
++it2;
} else { // sum == 0
// may be more complicated depending
// how you want to manage duplicated pairs
++it1;
++it2;
++res;
}
}
return res;
}
If they are already sorted, you can traverse them, one frome left to right, one from right to left:
Take two pointers, and put one at the very left of one array, the other at the very right of the other array. Look at both values you currently point on. If the absolute value of one of these values is greater than the other, advance the greater one. If the absolute values are equal, report both values, and advance both pointers. Stop, as soon as the pointer coming from the left reaches a positive value, or the pointer from the right reaches a negative value. After that, do the same with the pointers starting at the resp. other ends of the arrays.
This is essentially the solution proposed by #Matthias with an added pointer to catch duplicates. If there is a string of duplicate values in arr2, searchStart will always point to the one with the highest index so that we can check the entire string against the next value in arr1. All values in arr1 are explicitly checked, so no extra duplicate handling is required.
int pairCount = 0;
for (int base=0, searchStart=arr2Size-1; base<arr1Size; base++) {
int searchCurrent = searchStart;
while (arr1[base]+arr2[searchCurrent] > 0) {
searchCurrent--;
if (searchCurrent < 0) break;
}
searchStart=searchCurrent;
if (searchStart < 0) break;
while (arr1[base]+arr2[searchCurrent] == 0) {
std::cout << "arr1[" << base << "] + arr2[" << searchCurrent << "] = ";
std::cout << "[" << arr1[base] << "," << arr2[searchCurrent] << "]\n";
pairCount++;
searchCurrent--;
}
}
std::cout << "pairCount = " << pairCount << "\n";
Given the arrays:
arr1[] = {-5, -3, -3, -2, -1, 0, 2, 4, 4, 5, 8};
arr2[] = {-7, -5, -5, -4, -3, -2, 1, 3, 4, 5, 6, 7, 8};
we get:
arr1[0] + arr2[9] = [-5,5]
arr1[1] + arr2[7] = [-3,3]
arr1[2] + arr2[7] = [-3,3]
arr1[4] + arr2[6] = [-1,1]
arr1[6] + arr2[5] = [2,-2]
arr1[7] + arr2[3] = [4,-4]
arr1[8] + arr2[3] = [4,-4]
arr1[9] + arr2[2] = [5,-5]
arr1[9] + arr2[1] = [5,-5]
pairCount = 9
Now we come to the question of time complexity. The construction of searchStart is such that for each value in arr1 can have an extra compare with one value in arr2 (but no more than 1). Otherwise, for arrays with no duplicates this checks each value in arr2 exactly once, so this algorithm runs in O(n).
If duplicate values are present, however, it complicates things a bit. Consider the arrays:
arr1 = {-3, -3, -3}
arr2 = { 3, 3, 3}
Clearly, since all O(n²) pairs equal zero, we have to count all O(n²) pairs. This means that in the worst case, the algorithm is O(n²) and this is the best we can do. It is possibly more constructive to say that the complexity is O(n + p) where p is the number of matching pairs.
Note that if you only want to count the number of matches rather than printing them all, you can do this in linear time as well. Just change when searchStart is updated to when the last match is found and keep a counter that equals the number of matches found for the current searchStart. Then if the next arr1[base] matches arr2[searchStart], add the counter to the number of pairs.

How to find the first smaller element than an integer X in a vector ? (c++)

If I have the following vector {10 10 10 20 20 20 30 30}
and I want a function to return the position of the integer that = X or directly the smaller element after X , like for example if I am searching for 11 I want the function to return 2 since the 2nd element(10) is the first smaller element than 11 in the vector. I tried using lower_bound but that doesn't work.
int myints[] = {10,20,30,30,20,10,10,20};
vector<int> v(myints,myints+8); // 10 20 30 30 20 10 10 20
vector<int>::iterator low,up;
sort (v.begin(), v.end()); // 10 10 10 20 20 20 30 30
low=lower_bound (v.begin(), v.end(), 11); //
up= upper_bound (v.begin(), v.end(), 11); //
cout << "lower_bound at position " << int(low- v.begin()) << endl;
cout << "upper_bound at position " << int(up - v.begin()) << endl;
return 0;
this code outputs:
lower_bound at position 3
upper_bound at position 3
cppreference informs me that std::lower_bound
Returns an iterator pointing to the first element in the range [first, last) that is not less than value
and std::upper_bound
Returns an iterator pointing to the first element in the range [first, last) that is greater than value
In this case, given a vector containing 10 10 10 20 20 20 30 30 I would expect both functions to point at the first 20, which sits at position 3 in the vector and is indeed the result you got both times. If you had instead asked for 20, std::lower_bound would return an iterator pointing to the first 20 in the vector (position 3)... the first number not less than 20 and the same result you'd get when asking for 11. In this case though, std::upper_bound would return an iterator pointing at the first 30 (position 6), which is the first value greater than 20.
Just move the iterator back one to get the last value less than your target number, std::prev is one way to do that.
Well, upper_bound returns the first item that is greater than the test item, so the one before that (if it exists) will be the one you want?
you could do this...it might be better to return an iterator in case if the vector is empty...
auto find_next_smaller(vector<int> vec, const int x) {
std::sort(vec.begin(), vec.end());
auto it = std::lower_bound(vec.begin(), vec.end(), x);
if (it == vec.end()) {
it = (vec.rbegin()+1).base();
}
else if (it != vec.begin() && *it > x) {
--it;
}
return it;
}
If one has to find an element less than or equal to some x then multiset can be used to do so.
#include <iostream>
#include <set>
#include <iterator>
using namespace std;
int main()
{
multiset <int, greater <int> > iammultiset;
iammultiset.insert(10);
iammultiset.insert(10);
iammultiset.insert(14);
iammultiset.insert(20);
iammultiset.insert(20);
iammultiset.insert(30);
iammultiset.insert(40);
iammultiset.insert(50);
//{10,10,14,20,20,30,40,50}
cout<<*iammultiset.lower_bound(17) << endl;
//The Output here will be 14.
cout<<*iammultiset.lower_bound(20) << endl;
//The Output here will be 20.
}
#include <bits/stdc++.h>
using namespace std;
template <typename F, typename T>
F first_less_than(F f, F l, T val)
{
auto it = lower_bound(f, l, val);
return it == f ? l : --it;
}
int main()
{
vector<int> s{10, 20, 25, 40};
auto j = first_less_than(s.begin(), s.end(), 35);
cout << *j;
//output : 25
return 0;
}