Using Custom Std::Set Comparator

Using custom std::set comparator

1. Modern C++20 solution

auto cmp = [](int a, int b) { return ... };
std::set<int, decltype(cmp)> s;

We use lambda function as comparator. As usual, comparator should return boolean value, indicating whether the element passed as first argument is considered to go before the second in the specific strict weak ordering it defines.

Online demo

2. Modern C++11 solution

auto cmp = [](int a, int b) { return ... };
std::set<int, decltype(cmp)> s(cmp);

Before C++20 we need to pass lambda as argument to set constructor

Online demo

3. Similar to first solution, but with function instead of lambda

Make comparator as usual boolean function

bool cmp(int a, int b) {
return ...;
}

Then use it, either this way:

std::set<int, decltype(cmp)*> s(cmp);

Online demo

or this way:

std::set<int, decltype(&cmp)> s(&cmp);

Online demo

4. Old solution using struct with () operator

struct cmp {
bool operator() (int a, int b) const {
return ...
}
};

// ...
// later
std::set<int, cmp> s;

Online demo

5. Alternative solution: create struct from boolean function

Take boolean function

bool cmp(int a, int b) {
return ...;
}

And make struct from it using std::integral_constant

#include <type_traits>
using Cmp = std::integral_constant<decltype(&cmp), &cmp>;

Finally, use the struct as comparator

std::set<X, Cmp> set;

Online demo

std::set operations with custom comparator

Try changing

struct comp {
bool operator()(int const & a, int const & b) const {
return sz(a) >= sz(b);
}
};

with

struct comp {
bool operator()(int const & a, int const & b) const {
return sz(a) > sz(b);
} // ---------^
};

The (first) problem is that a comparator must impose a strict weak ordering.

So, in particular, must be comp(a, a) == false for every a in the std::set.

With your comparator you have comp(a, a) == true for every a.

Anyway: this works only if a != b imply s(a) != s(b); if this isn't the case... well... I suppose you can try with

struct comp {
bool operator()(int const & a, int const & b) const {
return (sz(a) > sz(b)) || ((sz(a) == sz(b)) && (a > b));
}
};

or something similar.

Comparator for member variable of type std::set that requires access to other member variables

CenterComparator isn't related to ShapeDisplay, it isn't aware of its members and it isn't derived from ShapeDisplay. You need to provide CenterComparator with its own reference Point. You then need to provide an instance of CenterComparator whose reference point is set.

Note that if you change that comparator's reference point in any way you will break std::set's sorting resulting in Undefined Behavior if you try to use it. So whenever setReference is called, you need to create a new set with a new comparator and copy over the old set.

Here is your code, adapted with these changes. I assumed you meant setReference and insertShape to be part of the public interface.

#include <cmath>
#include <set>

struct Point
{
int x;
int y;
};

struct Rectangle
{
int x;
int y;
int width;
int height;
};

class ShapeDisplay
{
public:
void insertShape(Rectangle rect)
{
m_shapes.insert(rect);
}

void setReference(Point reference)
{
m_reference = reference;

// Create a comparator using this new reference
auto comparator = CenterComparator{};
comparator.reference = m_reference;

// Create a new set
auto new_shapes = std::set<Rectangle, CenterComparator>(
std::begin(m_shapes), std::end(m_shapes), // Copy these shapes
comparator); // Use this comparator

m_shapes = std::move(new_shapes);
}

private:
struct CenterComparator
{
bool operator() (const Rectangle & a, const Rectangle & b) const
{

double distA = std::sqrt(std::pow(a.x - reference.x, 2)
+ std::pow(a.y - reference.y, 2));

double distB = std::sqrt(std::pow(b.x - reference.x, 2)
+ std::pow(b.y - reference.y, 2));

return distA < distB;
}

Point reference;
};

std::set<Rectangle, CenterComparator> m_shapes;
Point m_reference;
};

How to create a std::set with custom comparator in C++?

Method 1: use functor

Write a class that overloads the operator()so it can be called like a function:

struct compare {
bool operator() (const pair<int,int> &lhs, const pair<int,int> &rhs) const{
return (lhs.second-lhs.first > rhs.second-rhs.first);
}
};

Then, you can use the class name as the type parameter

set<pair<int,int>, compare> myset;

Method 2: use function pointer

Assuming compare is the function you want to use:

set<pair<int,int>, bool(*)(const pair<int,int> &lhs, 
const pair<int,int> &rhs)
> myset(&compare);

C++ set with customized comparator crashes on insert

You have to pass pointer to compare function to set constructor, otherwise it is null and it is why the code fails.

std::set<std::string, decltype(&Foo::Compare)> S{&Foo::Compare};

By decltype(&Foo::Compare) you only specify the type of comparator, it has to be provided because its default value is null.

Using custom comparator for std::set with custom constructor

Making the set

You need to provide the comparer when you construct the set:

using std::unordered_map;
using std::set;
using std::string;

set<int, Custom> s(Custom(10, 20));
// Or:
auto s = set<int, Custom>(Custom(10, 20));

This is because it needs to have a comparer as soon as you start assigning elements to the set, and because your comparer has parameters, it needs to know what those are

Using the set in a map

The comparer has to be default-constructible because calling map["key"] will default-construct the element if it doesn't exist:

class Custom
{
public:
// Add a default constructor
Custom() : x(0), y(0) {}
Custom (int x, int y) : x (x), y (x) {}
bool operator() (int a, int b)
{
return a < x && y < b;
}
private:
int x, y;
};

In this case, it's OK to provide a default constructor to the comparer because we can reassign it:

unordered_map<string, set<int, Custom>> map; 
map["some key"] = set<int, Custom>(Custom(10, 20));

What if I don't or can't have a default constructor?

We can still use unordered_map, but we have to use map.at("key") and map.emplace("key", value) instead of map["key"]:

unordered_map<string, set<int, Custom>> map; 
set<int, Custom> s(Custom(10, 20));
set<int, Custom> s2(Cunstom(30, 40));
s2.insert(1);
s2.insert(2);
s2.insert(3); // put some stuff in the set

map.emplace("Hello", s); //Inserts the set

map.insert({"Hello", s2}); //Inserts the set as a key-value pair

We can get values using map.at:

// This line gets the set at "Hello", and inserts 10 into the set:
map.at("Hello").insert(10);
// If "Hello" isn't in the map, there's an error

And we can check if something's in the map by using map.find("key"):

// Get an iterator to the key value pair held by "Hello" in the map:
// An iterator acts like a pointer
auto iterator = map.find("Hello");

if(iterator == map.end()) // Check if we found the thing
{
std::cout << "Couldn't find 'Hello' in the map";
}
else
{
// Get the key from the iterator
string key = iterator->first;
// Get a reference to the value from the iterator
set<int, Custom>& value = iterator->second;

}

C++ Finding minimum in vector of structs using custom comparator

It is evident that you need to define the comparison function correctly.

Here you are.

#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>

int main()
{
struct element_t {
int val;
bool visited;
};

std::vector<element_t> vct_priority =
{
{ 1, true }, { 0, true }, { 1, false }, { 0, true },
{ 2, false }, { 2147483647, false }, { 2147483647, false },
{ 0, true }, {1, false }, { 0, true }, { 2, false },
{ 2147483647, false }, { 1, false }
};

auto less_not_visited = []( const auto &e1, const auto &e2 )
{
return ( not e1.visited ) && ( e2.visited or e1.val < e2.val );
};

auto min = std::min_element( std::begin( vct_priority ),
std::end( vct_priority ),
less_not_visited );

std::cout << std::distance( std::begin( vct_priority ), min ) << '\n';
}

The program output is

2

If you want to define a separate function instead of the lambda expression then it looks like

bool less_not_visited( const element_t &e1, const element_t &e2 )
{
return ( not e1.visited ) && ( e2.visited or e1.val < e2.val );
};


Related Topics



Leave a reply



Submit