Comparator needed for an associative container - c++

I need a std::set<std::pair<std::string, int>, Compare> that compares two pairs according to their int values (in reverse order), if their int values are the same then according to their string values (in same order), but if their strings are equal then the two pairs are considered equal (regardless of their int values). So the Compare class I came up with is:
struct Compare {
bool operator()(const std::pair<std::string, int>& a, const std::pair<std::string, int>& b) const {
if (a.first == b.first)
return false;
if (a.second > b.second)
return true;
if (a.second < b.second)
return false;
return a.first < b.first;
}
};
The test
std::set<std::pair<std::string, int>, Compare> s;
s.insert({"Apple", 3});
s.insert({"Apple", 5});
works fine (inserting only the first pair). But
int main() {
std::set<std::pair<std::string, int>, Compare> s;
s.insert({"Ai", 14});
s.insert({"Am", 14});
s.insert({"F", 5});
s.insert({"Apple", 3});
s.insert({"Apple", 5});
}
shows both {"Apple", 3} and {"Apple", 5} being inserted, and I can't figure out why. What is the logical error in my Compare class? What is it supposed to be instead? I considered using std::map<std::string, int, Compare> but in this case the comparator could only use the key type std::string, which won't suffice for my specs.
I also tried:
bool operator()(const std::pair<std::string, int>& a, const std::pair<std::string, int>& b) const {
if (a.first < b.first || a.first > b.first) {
if (a.second > b.second)
return true;
if (a.second < b.second)
return false;
return a.first < b.first;
}
return false;
}
and it still does not give the results I want.

After examining your requirements, I came to the conclusion that your criteria for comparing objects do not meet requirements of strictly week ordering.
Say you insert the following objects to the set:
std::pair<std::string, int> obj1 = {"F", 5};
std::pair<std::string, int> obj2 = {"Apple", 3};
std::pair<std::string, int> obj3 = {"Apple", 5};
s.insert{obj1);
s.insert(obj2);
s.insert(obj3);
obj1 gets added since there is nothing else to compare with in the set. obj2 also gets added since it compare unequal to obj1. However. since obj1.second > obj2.second, the order of the objects in the set is:
obj1
obj2
Now, we come to insert obj3. obj3 < obj1 evaluates to true. Hence, it gets inserted before obj1. The logic for inserting an item into the set is such that obj3 never gets compares with obj2. Consequently, you end up with:
obj3
obj1
obj2

This is not how Compare in a std::set works. It is meant to provide an order from smallest to biggest. With your set you are trying to make 2 different kinds of comparisons.
You can order it by first int value, secondly string value. No problem.
But 2 elements in a set is considered equal if none if them compares smaller then the other one.
When you do your first example the 2 elements happens to be next to each other, so then the comparison function will be used on them and your a.first == b.first case triggers and none of them seems to be smaller then the other one so they are considered equal.
When you do your second attemp, by the time you insert "Apple", 5 your set look like this.
Ai 14
Am 14
F 5
Apple 3
Apple, 5 will here Compare smaller then Am 14 and bigger then F 5 so it will never be compared with Apple 3 at all, but it will be inserted between the two elements it's bigger and smaller then. Since the std::set is expected to be sorted in order already the elements beyond are irrelevant as far as the Compare is concerned.

The problem is the first comparison, it's just wrong. Remove it and it works.

Related

How to sort a list of pairs based on second element in decreasing order in c++

I was wondering if there's a way to sort my list of pairs based on the second element. Here is a code:
std::list<std::pair<std::string, unsigned int>> words;
words.push_back(std::make_pair("aba", 23);
words.push_back(std::make_pair("ab", 20);
words.push_back(std::make_pair("aBa", 15);
words.push_back(std::make_pair("acC", 8);
words.push_back(std::make_pair("aaa", 23);
I would like to sort my list words based in the integer element in decreasing order so that my list would be like:
<"aba", 23>,<"aaa", 23>,<"ab", 20>,<"aBa", 15>,<"acC", 8>
Also, is it possible to sort them by both the first and second element such that it sorts by the second elements first (by integer value), and then if there's two or more pairs with the same second element (i.e. same integer value), then it will sort those based on the first element in alphabetical order, then the first 2 pairs on my sorted list above would swap, so:
<"aaa", 23>,<"aba", 23>,<"ab", 20>,<"aBa", 15>,<"acC", 8>
I would like to sort my list words based in the integer element in decreasing order
The sorting predicate must return true if the first element (i.e., the first pair) passed precedes the second one in your established order:
words.sort([](auto const& a, auto const& b) {
return a.second > b.second;
});
Since you want to sort the list in decreasing order, the pair a will precede b if its second element (i.e., the int) is greater than b's second element.
Note that std::sort() doesn't work for sorting an std::list because it requires random access iterators but std::list only provides bidirectional iterators.
is it possible to sort them by both the first and second element such that it sorts by the second elements first (by integer value), and then if there's two or more pairs with the same second element (i.e. same integer value), then it will sort those based on the first element in alphabetical order
Assuming again decreasing order for the int element, just resort to the second element of the pairs when both int elements are the same:
lst.sort([](auto const& a, auto const& b) {
if (a.second > b.second)
return true;
if (a.second < b.second)
return false;
return a.first < b.first;
});
or more concise thanks to std::tie():
lst.sort([](auto const& a, auto const& b) {
return std::tie(b.second, a.first) < std::tie(a.second, a.first);
});
std::list has a member function std::list::sort that is supposed to do the sorting.
One of its two overloads accepts custom comparison function:
template <class Compare>
void sort(Compare comp);
which you can use as follows:
words.sort([](const std::pair<string, unsigned int> &x,
const std::pair<string, unsigned int> &y)
{
return x.second > y.second;
});

How does a priority queue of pair<int,int> work even without specifying a custom comparator?

A std::priority_queue uses a std::vector as the default container (Reference this). For sorting on the basis of the first element in a std::vector<pair<int, int>>, we need to define our own comparison function (Reference this). This is what I understand.
Now, the following code returns the k most frequent elements in a non-empty array, in O(NlogK):
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
if(nums.empty())
return vector<int>();
unordered_map< int, int > hashMap;
for(int i=0; i<nums.size(); i++)
hashMap[nums[i]]++;
priority_queue< pair< int, int >> pq;
vector< int > result;
unordered_map< int, int >::iterator it=hashMap.begin();
for(it=hashMap.begin(); it!=hashMap.end(); it++) {
//the first one is frequency and the second one is the value
pq.push(make_pair(it->second, it->first));
//the peculiar implementation below is because we the code to be O(NlogK)
if(pq.size()>(hashMap.size()-k)) {
result.push_back(pq.top().second);
pq.pop();
}
}
return result;
}
};
This code works correctly and gets accepted by the judge - but how? The std::priority_queue, using a std::vector<pair<int, int>> as its underlying container must contain a custom comparison function so that it sorts correctly. So, how does it work?
Frankly, it works because it is designed to do so.
A few things:
a std::priority_queue employs std::less<T>, where T is the underlying sequence value type, as the default comparator when no override is specified.
std::less<T> invokes operator < against two T arguments, resolving to whatever best-fits and/or is available.
Therefore, if this works as you desired with no special override of the sequence type comparator, it must mean that there exists an operator < for std::pair<int,int> that wire this whole thing together.
And indeed there is. Checking the documentation for std::pair<T1,T2>, you'll find there is an operator < overload that effectively does this:
if (lhs.first < rhs.first)
return true;
else if (!(rhs.first < lhs.first))
return lhs.second < rhs.second
else
return false;
Mind-play examples of how this works are left to the reader to think about.

return value std::mismatch for equal vectors

I am using std::mismatch to check whether two vectors of structs are exactly the same. Usually, in my program, they are not, but in exceptional cases it might happen. In the documentation I find the following:
"If the elements compared in both sequences have all matched, the function returns a pair with first set to last1 and second set to the element in that same relative position in the second sequence."
However, if I create two vectors that are completely equal, std::mismatch does not return a value. A small example of what I am trying to do:
#include <vector>
#include <algorithm>
#include <utility>
struct structwithnumber {
int id;
};
bool compare_structs (structwithnumber* struct1, structwithnumber* struct2) {
return struct1->id == struct2->id;
};
bool compare_structvectors(std::vector<structwithnumber*> v1, std::vector<structwithnumber*> v2) {
if (v1.size() != v2.size())
{
return false;
}
std::pair<std::vector<structwithnumber*>::iterator, std::vector<structwithnumber*>::iterator> mypair;
mypair = std::mismatch(v1.begin(), v1.end(), v2.begin(), compare_structs);
return (compare_structs(*mypair.first, *mypair.second));
}
void simple_example() {
structwithnumber* struct1 = new structwithnumber();
structwithnumber* struct2 = new structwithnumber();
struct1->id = 1;
struct2->id = 2;
std::vector<structwithnumber*> v1;
std::vector<structwithnumber*> v2;
v1.push_back(struct1);
v1.push_back(struct2);
v2.push_back(struct1);
v2.push_back(struct2);
compare_structvectors(v1, v2);
}
When I run this code in visual studios 15 I get an error on the line:
return (compare_structs(*mypair.first, *mypair.second));
On further investigation it turns out mypair remains empty after mismatch. From the documentation, I though this would return the last value of each vector. Did I misunderstand how mismatch would behave when presented with 2 sequences in which all elements match?
std::mismatch, in case everything matches, returns a(t least one) past-the-end iterator. You cannot dereference it as you're doing in compare_structs(*mypair.first, *mypair.second).
The code should test for the case as follows:
mypair = std::mismatch(v1.begin(), v1.end(), v2.begin(), compare_structs);
if(mypair.first == v1.end()) {
// No mismatch, do something sensible
} else {
return (compare_structs(*mypair.first, *mypair.second));
}
However, if I create two vectors that are completely equal, std::mismatch does not return a value
Of course it does, it has to return something.
On further investigation it turns out mypair remains empty after mismatch.
What does that mean? How is mypair empty? It's a pair, it had exactly two members when it was created, and it still has exactly two members after the call to mismatch.
However, when both sequences match, it's a pair of end iterators.
That's like saying you have a pair of empty mismatched sequences (which is still not the same as an impossible empty pair).
You can't dereference these iterators, so your code that does so is broken. You need to test before dereferencing:
const bool v1_mismatch = (mypair.first != v1.end());
const bool v2_mismatch = (mypair.second != v2.end());
const bool identical = !(v1_mismatch || v2_mismatch);
if (v1_mismatch && v2_mismatch) {
// this is the only case where you can dereference both
return compare_structs(*mypair.first, *mypair.second);
}
// otherwise you can dereference at most one iterator,
// and if v1,v2 are identical, you can't dereference either

How to sort elements of pairs inside vector?

i have a pair of int and string inside a vector, how to sort them first on basis of int and if int value is duplicate then sort according to lexicographical manner of string.
vector< pair<int, string> > v;
You just:
std::sort(v.begin(), v.end());
std::pair is lexicographically compared.
On the other hand if you want to sort them with respect the second element of the std::pair then you would have to defind a custom comparator in the following manner:
std::sort(v.begin(), v.end(), [](std::pair<int, std::string> const &p1,
std::pair<int, std::string> const &p2) {
return (p1.second == p2.second)?
p1.first < p2.first :
p1.second < p2.second;
});
what i did was to store int value multiplied with -1 and then sorted it in ascending order and then again multiplied to -1 to the stored int value to restore int values. doing this made the vector of pairs arranged as needed.

How to sort only subset in std::vector?

I have a vector of pairs:
std::vector<std::pair<std::string, Cell::Ptr>> mCells;
I want to sort only a subset of elements (on the first's string). The Cell has method GetSorted() which indicates if it's part of this subset or not.
This is what I had initially:
std::sort(mCells.begin(), mCells.end(),
[](std::pair<std::string, Cell::Ptr> const &a,
std::pair<std::string, Cell::Ptr> const &b)
{
// Only compare when both cells need to be sorted; otherwise return false
// to indicate that they are already in correct order. This keeps the
// non-marked cells at their original positions.
if (a.second->GetSorted() && b.second->GetSorted())
{
return a.first < b.first;
}
else
{
return false;
}
});
But it does not work, because sort, of course, does not compare all combinations. Sometimes the return a.first < b.first line is not even executed once.
To define the required sort function, here's an example. Suppose the elements are:
G* F C* A* D B E*
Only the *-ones need to be sorted. But, the sort should only be applied to adjacent to-be-sorted elements. (That's why I had a.second->GetSorted() && b.second->GetSorted().) The result should then be:
G* F A* C* D B E*
So, only A and C are adjacent, and are sorted. Is there an easy solution to this problem?
Alternatively a solution that results in:
A* F C* E* D B G*
would also be usable for me at the moment. So, sorting all * elements, while leaving the others where they are. This appears to be easier to do.
You need to separate finding the ranges to be sorted and sorting them:
using namespace std;
auto isSorted = [](std::pair<std::string, Cell::Ptr> const &a) {
return a.second->GetSorted();
}
auto it = begin(mCells);
const auto itEnd = end(mCells);
while (it != itEnd) {
auto rangeStart = find_if(it, itEnd, isSorted);
if (rangeStart == itEnd)
break;
auto rangeEnd = find_if_not(rangeStart, itEnd, isSorted);
if (distance(rangeStart, rangeEnd) > 1) {
// pair comparison should do the trick here
sort(rangeStart, rangeEnd);
}
it = rangeEnd;
}
Just saw your edit: you can achieve the alternate solution by defining a custom input iterator class that skips non-sorted elements, then using a single sort() call on the whole "range".