How would I perform a find()
or lower_bound()
function on a std::set
using a comparator function that is independent of its key such that it still runs in O(log N) time?
Suppose I define a data type foo
with two variables x
and y
and have a std::set<foo>
that uses x
as the key value.
struct foo {
int x, y;
foo(int x, int y) : x(x), y(y) {}
};
struct xCompare {
bool operator() (const foo& i, const foo& j) const {
return i.x < j.x;
}
};
// Within main()
std::set<foo, xCompare> fooSetX;
Is it possible to perform a binary search using lower_bound()
or some other function that compares the values of y
?
For the sake of this argument, assume that x
and y
are unique and independent of each other, and that given two foo
variables foo1
and foo2
, if foo1.x < foo2.x
, then foo1.y < foo2.y
. This means that I cannot express y
as a function of x
, but is also ordered by y
within fooSetX
.
For example, given three foo(x,y)
values (2,5), (3,9) and (5,10) within fooSet
, a lower_bound()
that takes y = 7
as the search term would return an iterator pointing to (3,9).
Currently, the way I go about solving this is by having two std::set<foo>
s, ordered by x
and y
respectively. Whenever I need to search by y
, I use the second std::set
.
struct yCompare {
bool operator() (const foo& i, const foo& j) const {
return i.y < j.y;
}
};
// Within main()
std::set<foo, yCompare> fooSetY;
// Inserting elements
fooSetX.insert(foo(2,5)); fooSetY.insert(foo(2,5));
fooSetX.insert(foo(3,9)); fooSetY.insert(foo(3,9));
fooSetX.insert(foo(5,10)); fooSetY.insert(foo(5,10));
// lower_bound() with y = 7
std::set<foo>::iterator it = fooSetY.lower_bound(foo(0,7)); // points to (3,9)
You cannot directly pass a custom comparator to std::set::lower_bound
- you need to pass it to the class template itself, as it will be internally used to maintain the order of the objects (consequently making std::set::lower_bound
work).
Here's how the std::set
template is defined:
template<
class Key,
class Compare = std::less<Key>,
class Allocator = std::allocator<Key>
> class set;
Compare
is the only ordering customization point that allows you to provide a function object that will compare your objects as desired in place of std::less<Key>
.
There's no way of adding additional ordering predicates to an std::set
.
If you want an additional ordering on your objects that will allow you to achieve O(log N) lookups, you could use another ordered structure that is kept in sync with the original one. A std::set
of pointers to objects in the first set that uses a different comparator could work. Example:
class MySet
{
private:
std::set<Item, Comparator0> _set0;
std::set<decltype(_set0.begin()), Comparator1> _set1;
public:
void insert(Item x)
{
auto res = _set0.insert(x);
assert(res.second);
_set1.insert(res.first);
}
const auto& lookup0(Key0 x) { return _set0.lower_bound(x); }
const auto& lookup1(Key1 x) { return *(_set1.lower_bound(x)); }
};