I have a problem with the operator<() method which is required for a std::map. I'm using a struct as composite key that looks as follows:
struct MyKey {
std::string string1;
std::string string2;
std::string string3;
unsigned int uint1;
friend bool operator<(const MyKey& mk1, const MyKey& mk2)
{
return mk1.string1 < mk2.string1 && mk1.string2 < mk2.string2 &&
mk1.string3 < mk2.string3 && mk1.uint1 < mk2.uint1;
}
}
As introduced I want to use a composite key with 4 values, but I don't know how to achieve this for the operator< method. I observed that only 1 value is stored at a time!
Can anybody tell me how the right condition looks like?
Thanks in advance!
The Standard library's associative containers such as std::map, std::set, std::multiset, std::multimap, std::bitset require that the ordering of elements must follow Strict Weak Ordering, which means your implementation of operator< must follow strict weak ordering. So one implementation could be this:
friend bool operator<(const MyKey& mk1, const MyKey& mk2)
{
if (mk1.string1 != mk2.string1 )
return mk1.string1 < mk2.string1;
else if ( mk1.string2 != mk2.string2)
return mk1.string2 < mk2.string2;
else if (mk1.string3 != mk2.string3)
return mk1.string3 < mk2.string3;
else
return mk1.uint1 < mk2.uint1;
}
Or you can implement it as:
friend bool operator<(const MyKey& mk1, const MyKey& mk2)
{
auto const & t1 = std::tie(mk1.string1, mk1.string2, mk1.string3, mk1.uint1);
auto const & t2 = std::tie(mk2.string1, mk2.string2, mk2.string3, mk2.uint1);
return t1 < t2;
}
In this solution, std::tie function creates two tuples t1 and t1 of the references of the arguments passed to it, and then compare t1 and t2 using overloaded operator< for std::tuple instead. The operator< for tuple compares the elements lexicographically — strict-weak ordering is achieved..
I think you have a problem in that the operator< doesn't necessarily implement strict weak ordering. There are too many combinations where A<B is false and B<A is also false, where A and B are MyKey objects. This is interpreted as A being equal to B.
The problem with your implementation is that it's not stable, consider...
return mk1.string1 < mk2.string1 && mk1.string2 < mk2.string2 &&
mk1.string3 < mk2.string3 && mk1.uint1 < mk2.uint1;
...evaluating { "a", "a", "a", 1 } < { "a", "b", "a", 1 } = a<a && ... = false && ... = false
...but { "a", "b", "a", 1 } < { "a", "a", "a", 1 } = a<a && ... = false && ... = false
So, neither is reported as less than the other, despite them not being equal keys in the map.
A working solution: it's concise and efficient to do each necessary string comparisons only once...
friend bool operator<(const MyKey& mk1, const MyKey& mk2)
{
int x;
return (x = mk1.string1.compare(mk2.string1)) ? x < 0 :
(x = mk1.string2.compare(mk2.string2)) ? x < 0 :
(x = mk1.string3.compare(mk2.string3)) ? x < 0 :
mk1.uint1 < mk2.uint1;
}
Related
I declared a class like this:
class myclass{
array myarr;
pair<PT3D,PT3D> mypair;
ID myid;
CString tag;
}
after fill data, i have a vector<myclass> myvec;
How can I sort this vector based on the CString value of each class?
bool SortCompare(const wchar_t* a, const wchar_t* b)
{
if (wcslen(a) == wcslen(b))
{
int k = (int)wcslen(a);
return std::wcsncmp(a, b, k);
}
else
return wcslen(a) < wcslen(b);
}
sort(myvec.begin(), myvec.end(), [h](myclass& a,
myclass& b) {
return SortCompare(a.tag, b.tag);
});
However this doesn't work properly , my desired result is a list sorted by human sort as this post.
but I don't know how to use CSortStringArray::CompareAndSwap function, how to use this code with std::sort?
How can I correct my codes?
Note CString provides less operator so you do not have to fall back to C-API like wcsncmp.
You are making this overcomplicated.
using C++11:
std::sort(myvec.begin(), myvec.end(), [](const myclass& a, const myclass& b) {
return a.tag < b.tag;
});
C++20 has even something simpler called projection:
std::ranges::sort(myvec, {}, &myclass::tag);
Now the issue of alphanumeric compare. This can look like this:
struct AlphanumericSplitResult {
std::string_view prefix;
std::string_view digits;
std::string_view suffix;
};
AlphanumericSplitResult alphanumericSplit(std::string_view s)
{
auto i = s.find_first_of("0123456789"sv);
i = i == std::string_view::npos ? s.size() : i;
auto j = s.find_first_not_of("0123456789"sv, i);
j = j == std::string_view::npos ? s.size() : j;
return { s.substr(0, i), s.substr(i, j - i), s.substr(j) };
}
bool asNumberLess(std::string_view l, std::string_view r)
{
return l.size() != r.size() ? l.size() < r.size() : l < r;
}
bool alphanumeric_less(std::string_view l, std::string_view r)
{
if (l.empty())
return !r.empty();
auto a = alphanumericSplit(l);
auto b = alphanumericSplit(r);
if (a.prefix != b.prefix)
return a.prefix < b.prefix;
if (a.digits != b.digits)
return asNumberLess(a.digits, b.digits);
return alphanumeric_less(a.suffix, b.suffix);
}
It passes all test I wrote: https://godbolt.org/z/hdG6fq9sa
and it is easy to feed to both algorithms.
Disclaimer: there is small bug in implementation - can you find it? Depending on requirements there are more small issues.
std::wcsncmp returns 0, > 0 or < 0. > 0 and < 0 are converted to true, 0 to false. Thus, compare "a" with "b" and "b" with "a" both return true. std::sort expects a proper compare function. Perhaps you want return std::wcsncmp(a, b, k) < 0.
In your else, you return Len(a) < Len( b) but this is not the same as Len(a) != Len(b). I would just return false in the else block since you know they aren't the same at that point.
So maps need you to implement the operator < if you want to use custom objects as keys.
struct example{
example( int id, String stuff);
int id;
String stuff;
bool operator<( const example& rhs ) const;
}
bool example::operator<( const example& rhs ) const
{
if( id< rhs.id ) return true;
if( rhs.id< id) return false;
if( stuff< rhs.stuff) return true;
if( rhs.stuff< stuff) return false;
return false;
}
From the examples I have seen (see discussion: Why std::map overloaded operator < does not use the Compare ) just return true if the lhs < rhs...
Why does map require you to implement the < operator? Is it because maps are doing binary search in the background to find if the key matched?
Even if it is doing binary search it will still have to compare if the objects are equal at the end right?
example example1 = example(1,"a");
example example2 = example(2,"b");
map<example, String> mymap;
mymap.insert({example1,"hello"});
mymap.insert({example2,"world"});
cout << mymap[example(1,"a")] << endl;
It never asks for an implementation of the operator= so how does it know the new object I have created is the same as the first entry of the map. Would it print "hello"?
It stores values in a balanced binary tree, red black tree as usual, so comparison operator is required for this tree
also keep in mind that if a < b == false and b < a == false it is considered that a is equal to b
I know that I have to overload operator < for std::set.
I overload operator < with two classes: "UniqueID" and "UniqueIDWithBug".
The only difference is "UniqueID" added code this->unique_id_a_ == t.unique_id_a_ while comparing.
Then I put same elements into the two sets.
Finally I find one element inside the sets.
One set can find it, another can not.
This problem confused me for a long time.
struct UniqueID {
uint64_t unique_id_a_{0};
uint64_t unique_id_b_{0};
bool operator<(const UniqueID &t) const {
if (this->unique_id_a_ < t.unique_id_a_) {
return true;
}
if (this->unique_id_a_ == t.unique_id_a_ &&
this->unique_id_b_ < t.unique_id_b_) {
return true;
}
return false;
}
};
struct UniqueIDWithBug {
uint64_t unique_id_a_{0};
uint64_t unique_id_b_{0};
bool operator<(const UniqueIDWithBug &t) const {
if (this->unique_id_a_ < t.unique_id_a_) {
return true;
}
return (this->unique_id_b_ < t.unique_id_b_);
}
};
// init data
std::set<UniqueID> _set = {
{17303934402126834534u, 2922971136},
{8520106912500150839u, 3118989312},
{9527597377742531532u, 2171470080},
{10912468396223017462u, 3972792320},
};
std::set<UniqueIDWithBug> _set_with_bug = {
{17303934402126834534u, 2922971136},
{8520106912500150839u, 3118989312},
{9527597377742531532u, 2171470080},
{10912468396223017462u, 3972792320}};
UniqueID _unique_id = {10912468396223017462u, 3972792320};
UniqueIDWithBug _unique_id_with_bug = {10912468396223017462u, 3972792320};
if (_set.find(_unique_id) == _set.end()) {
std::cout << "_set not find" << std::endl;
}
if (_set_with_bug.find(_unique_id_with_bug) == _set_with_bug.end()) {
std::cout << "_set_with_bug not find" << std::endl;
}
The outputs:
_set_with_bug not find
The less-than operation you define for use with std::set (and others) must be a valid strict weak ordering.
Your UniqueIDWithBug ordering is not.
For example, consider:
UniqueIDWithBug a{1, 10};
UniqueIDWithBug b{2, 5};
Now observe that both a < b and b < a are true. This is just a quick demonstration that you do not have a strict weak ordering; indeed, this is not an ordering at all!
So your program has undefined behaviour. The internals of the std::set mechanism assume a valid ordering, but yours is not. In this case the observable result was "element not found". It could have been "make a pizza".
Constructing a good strict weak ordering can be difficult, but you've already done the hard work, because UniqueID's ordering is correct.
Alternatively, abandon the ordering entirely, define a hash function, and switch to unordered_set.
I built the following map using a self-defined struct.
#include <iostream>
#include <vector>
#include <map>
struct keys {
int first;
int second;
int third;
};
struct keyCompare
{
bool operator()(const keys& k1, const keys& k2)
{
//return (k1.first<k2.first && k1.second<k2.second && k1.third<k2.third);
return (k1.first<k2.first || k1.second<k2.second || k1.third<k2.third);
//return (k1.first<k2.first || (k1.first==k2.first && k1.second<k2.second) || (k1.first==k2.first
// && k1.second==k2.second && k1.third<k2.third));
}
};
int main()
{
keys mk, mk1;
int len = 4;
//int myints1[9] = {1,2,3,4,5,6, 7,8,9};
int myints1[12] = {1,2,3,4,5,6,1,2,3,1,2,3};
std::vector<int> input1(myints1, myints1+12);
std::map<keys, int, keyCompare> c2int;
for (int i = 0; i < len; i++) {
mk.first = input1[i*3];
mk.second = input1[i*3+1];
mk.third = input1[i*3+2];
c2int[mk] = i;
}
for (int i = 0; i < len;i++) {
mk1.first = input1[i*3];
mk1.second = input1[i*3+1];
mk1.third = input1[i*3+2];
std::cout << "map content " << c2int[mk1] << "\n";
}
return 0;}
The code works as expected for non-repeated keys like {1,2,3,4,5,6,7,8,9}. The return is
map content is 0
map content is 1
map content is 2
but when there are repeated patterns, e.g., keys are {1,2,3,4,5,6,1,2,3}. The print out is
map content is 2
map content is 1
map content is 2
while I was expecting
map content is 0
map content is 1
map content is 0
since key {1,2,3} has already assigned value 0. But the compare function seems modify this key to value 2 instead of 0. I tried different compare function but none of them shows expected output. I think I had missed something in this approach. Can someone explain? Thanks
This comparator is incorrect:
bool operator()(const keys& k1, const keys& k2)
{
return (k1.first<k2.first || k1.second<k2.second || k1.third<k2.third);
}
Consider {1,4,9} vs {2,3,4}. {1,4,9} < {2,3,4} because of the first comparison, but then {2,3,4} < {1,4,9} because of the second! That's clearly not what you intended! Besides, operator< must be asymmetric in order to be a StrictWeakOrdering, which is what is required for std::map.
You have to deal with the keys in order:
bool operator()(const keys& k1, const keys& k2) {
if (k1.first != k2.first) {
return k1.first < k2.first;
}
else if (k1.second != k2.second) {
return k1.second < k2.second;
}
else {
return k1.third < k2.third;
}
}
Your keyCompare is not valid for a std::map.
It needs to return true for (a,b) if a should be ordered before b.
You have written a function that could return true for (a,b) and (b,a). This violates strict weak ordering.
Check the following code:
string toLowerCase(const string& str) {
string res(str);
int i;
for (i = 0; i < (int) res.size(); i++)
res[i] = (char) tolower(res[i]);
return res;
}
class LeagueComparator
{
public:
bool operator()(const string& s1, const string& s2)
{
return toLowerCase(s1) < toLowerCase(s2);
}
};
int main()
{
set<string, LeagueComparator> leagues;
set<string, LeagueComparator>::iterator iter;
leagues.insert("BLeague");
leagues.insert("aLeague"); // leagues = {"aLeague", "BLeague"}
leagues.insert("ALeague");
for (iter = leagues.begin(); iter != leagues.end(); iter++)
cout << *iter << endl;
return 0;
}
The output is:
aLeague
BLeague
which is shocking to me. I thought (and expecting) the output would be:
aLeague
ALeague
BLeague
Before the execution of leagues.insert("ALeague");, the leagues contains "aLeague" and "BLeague". My question is, while executing leagues.insert("ALeague"); why the machine treats "ALeague" == "aleague"? According to my understanding, there is no element "ALeague" in leagues. So "ALeague" should be inserted into leagues. The comparator should determine where to put "ALeague".
Thanks in advance.
PS: Please don't hit me for using C style cast. :P I'm too lazy to type static_cast.
Your comparator, thanks to the toLowerCase, says that "aLeague" == "ALeague". Since (according to your comparator) "aLeague" < "ALeague" == false and "ALeague" < "aLeague" == false, they must be equivalent. And inserting an equivalent element into a set doesn't do anything.
When you insert any value to a set, the object checks to see whether it already contains that value. Your LeagueComparator object compares ALeague with the other two values already in the set. It determines that the existing value aLeague is neither greater than nor less than the proposed new entry (ALeague), so they must be equal, and so it doesn't proceed with the insert. The set remains with just two elements. That's the whole point of providing a customer comparison object, so you can control how the set determines whether two elements match.
Given the comparator you provided, "ALeague" is indeed equivalent "aLeague".
Given two values, x and y, and a less-than comparator z:
If z(x, y) is true, then x is less than y
If z(y, x) is true, then y is less than x
If neither is true, then x is equivalent to y
If both are true, then you have a broken comparator.
Replace your LeagueComparator with
class LeagueComparator
{
public:
bool operator()(const string& s1, const string& s2)
{
return toLowerCase(s1) < toLowerCase(s2) ||
!(toLowerCase(s2) < toLowerCase(s1)) && s1 < s2;
}
};