A Solution for Collection by Unique Elements with Custom Tolerance
Problem Description
Suppose there were such a scenario - we need to put mutiple points into a collection which contains unique points. The form of point is its coordinates (x, y). The type of coordinate is double.
Note that we treat two points whose coordinates are close to each other as the same point. Coordinate close means within a certain tolerance. In different cases, we need to support custom tolerance which means that it’s a run-time value rather than compile-time value.
For example, with a tolerance of 1e-7, we consider a point (1.0000000000, 2.0000000000) and another point (1.0000000001, 2.0000000001) to be the same point. If they were added separately to the collection, the size of the collection would be 1.
Solution
std::unordered_set?
Imagine we use std::unordered_set<Point> to handle this problem. How to implement the hash function for the data structure Point? No matter how small the difference between double values, their hash values can be very different. It’s not easy to ensure that close double values have consistent hash values.
std::set?
Imagine we use std::set<Point> to handle this problem. How to write a comparator for Point in std::set?
Don’t forget that we also need to support custom tolerance.
Template with double can be considered, but it’s a C++20 feature and more importantly it only supports compile-time values.
There is an elegant solution - that is lambda as comparator with capturing tolerance as follows:
1 | |
std::set/map requires that comparators should be with weak strict ordering. For two value x and y, if !(x < y) && !(y < x) == true, we can consider them equal.[1]
If their difference are within tolerance, the comparator should return false directly.
Advanced
However, how to transfer such sets of such type to a function?
1 | |
After all, we can’t spell the real type of a lambda function.
Of course, we can use auto to run:
1 | |
But its readability is bad and auto has no constraint. We can’t clarify what we actually is a std::set<Point, ...>. If we use concept, it seems that we take lots of efforts away from our work.
The solution to fix it is very simple. We all know lambda is just a anonymous functor that overloads the operator() function. And we have no idea to spell such anonymous functor. Therefore, the direction is clear - that is to make it a named functor. :)
1 | |
Actually, it also supports to change the tolerance for the same set within the the process of use. It not only implements the functionality of comparator but also supports variable custom tolerance.
1 | |