Initially I had code that looked like this:
std::map< std::pair<int,int>, std::vector<Class0*> > aMap;
It worked. Now I have code that looks like this:
std::map< std::pair<Vec3f, Vec3f>, std::vector<Class0*> > aMap;
It no longer maps correctly (compiles fine). Why? And how can I fix that?
EDIT: After popular demand here is the comparison code for a 3D vector (3 floats):
class Vec3f {
...
bool operator () ( const Vector3f& v0, const Vector3f& v1 ) const {
return std::tie(v0[0], v0[1], v0[2]) < std::tie(v1[0], v1[1], v1[2]);
} ...
from this question Overloading operator for set. The above comparison works fine for a set, but apparently not for a pair. Why?
The key to the map is a pair. The comparison of pair is lexicographic: it first compares the first elements of both pairs. If both appears to be equal, it also compares the second elements. So you need a proper comparison for both Class1 and Class.
Additional considerations:
On each class of the pair, the map comparator less-than must comply with some constraints:
it must establish a stritct weak oredering between all the elements.
(! k1<k2) && (! k2<k1) is equivalent to k1==k2
k1<k2 && k2<k3 implies that k1<k3
If any of this property is broken, the strict order is not guaranteed and the mapping may fail.
Related
I constructed an unordered_map using key type rot3d, which is defined below:
#ifndef EPS6
#define EPS6 1.0e-6
#endif
struct rot3d
{
double agl[3]; // alpha, beta, gamma in ascending order
bool operator==(const rot3d &other) const
{
// printf("== used\n");
return abs(agl[0]-other.agl[0]) <= EPS6 && abs(agl[1]-other.agl[1]) <= EPS6 && abs(agl[2]-other.agl[2]) <= EPS6;
}
};
Equality of rot3d is defined by the condition that each component is within a small range of the same component from the other rot3d object.
Then I defined a value type RotMat:
struct RotMat // rotation matrix described by a pointer to matrix and trunction number
{
cuDoubleComplex *mat = NULL;
int p = 0;
};
In the end, I defined a hash table from rot3d to RotMat using self-defined hash function:
struct rot3dHasher
{
std::size_t operator()(const rot3d& key) const
{
using std::hash;
return (hash<double>()(key.agl[0]) ^ (hash<double>()(key.agl[1]) << 1) >> 1) ^ (hash<double>()(key.agl[2]) << 1);
}
};
typedef std::unordered_map<rot3d,RotMat,rot3dHasher> HashRot2Mat;
The problem I met was, a key was printed to be in the hash table, but the function "find" didn't find it. For instance, I printed a key using an iterator of the hash table:
Key: (3.1415926535897931,2.8198420991931510,0.0000000000000000)
But then I also got this information indicating that the key was not found:
(3.1415926535897931,2.8198420991931505,0.0000000000000000) not found in the hash table.
Although the two keys are not 100% the same, the definition of "==" should ensure them to be equal. So why am I seeing this key in the hash table, but it was not found by "find"?
Hash-based equivalence comparisons are allowed to have false positives, which are resolved by calling operator==.
Hash-based equivalence comparisons are not allowed to have false negatives, but yours does. Your two "not 100% the same" keys have different hash values, so the element is not even found as a candidate for testing using operator==.
It is necessary that (a == b) implies (hash(a) == hash(b)) and your definitions break this precondition. A hashtable with a broken precondition can misbehave in many ways, including not finding the item you are looking for.
Use a different data structure that is not dependent on hashing, but nearest-neighbor matching. An octtree would be a smart choice.
Equality of rot3d is defined by the condition that each component is within a small range of the same component from the other rot3d object.
This is not an equivalence. You must have that a==b and b==c implies a==c. Yours fails this requirement.
Using a non-equality in a std algorithm or container breaks the std preconditions, which means your program is ill-formed, no diagnostic required.
Also your hash hashes equivalent values differently. Also illegal.
One way to fix this is to build buckets. Each bucket has a size of your epsilon.
To find if a value is in your buckets, check the bucket you'd put the probe value in, plus all adjacent buckets (3^3 or 27 of them).
For each element, double check distance.
struct bucket; // array of 3 doubles, each a multiple of EPS6. Has == and hash. Also construct-from-rod3d that rounds.
bucket get_bucket(rot3d);
Now, odds are that you are just caching. And within EPS-ish is good enough.
template<class T, class B>
struct adapt:T{
template<class...Args>
auto operator()(Args&&...args)const{
return T::operator()( static_cast<B>(std::forward<Args>(args))... );
}
using is_transparent=void;
};
std::unordered_map<bucket, RotMat, adapt<std::hash<rot3d>, bucket>, adapt<std::equal_to<>, bucket>> map;
here we convert rod3ds to buckets on the fly.
This is what I am trying right now. I made a comparison function:
bool compare(const std::pair<int, Object>& left, const std::pair<int, Object>& right)
{
return (left.second.name == right.second.name) && (left.second.time == right.second.time) &&
(left.second.value == right.second.value);
}
After I add an element I call std::unique to filter duplicates:
data.push_back(std::make_pair(index, obj));
data.erase(std::unique(data.begin(), data.end(), compare), data.end());
But it seems that this doesn't work. And I don't know what the problem is.
From my understanding std::unique should use the compare predicate.
How should I update my code to make this work ?
I am using C++03.
edit:
I have tried to sort it too, but still doens't work.
bool compare2(const std::pair<int, Object>& left, const std::pair<int, Object>& right)
{
return (left.second.time< right.second.time);
}
std::sort(simulatedLatchData.begin(), simulatedLatchData.end(), compare2);
std::unique requires the range passed to it to have all the duplicate elements next to one another in order to work.
You can use std::sort on the range before you a call unique to achieve that as sorting automatically groups duplicates.
Sorting and filtering is nice, but since you never want any duplicate, why not use std::set?
And while we're at it, these pairs look suspiciously like key-values, so how about std::map?
If you want to keep only unique objects, then use an appropriate container type, such as a std::set (or std::map). For example
bool operator<(object const&, object const&);
std::set<object> data;
object obj = new_object(/*...*/);
data.insert(obj); // will only insert if unique
I am trying to create a priority queue consisting of pairs of int, char that gives me the pair with the greater int, but my code is not working properly. What am I doing wrong?
This is my comparator class:
class Compare
{
public:
bool operator() (pair<int, char>a, pair<int, char>b)
{
return a.first > b.first;
}
};
And this is my priority queue:
priority_queue<pair<int, char>, vector<pair<int, char>>, Compare> party;
But if I execute the code:
party.push(make_pair(2, 'A'));
party.push(make_pair(3, 'B'));
cout<<party.top().first;
It returns 2, instead of 3. How do I fix my implementation of the priority queue?
The same fix that Geordi La Forge would use: reverse the polarity:
bool operator() (const pair<int, char> &a, const pair<int, char> &b) const
{
return a.first < b.first;
}
The comparison function always implements strict weak ordering, a.k.a. the logical < operation. But priority_queue, by definition, gives you the largest value in the priority queue, first:
... provides constant time lookup of the largest (by default) element,
But the comparison function is still strict weak ordering:
A Compare type providing a strict weak ordering.
Slightly counter-intuitive, but after a while, it does make sense...
P.S.: The comparison function should be a const function, and take const parameters, for efficiency, as shown in my example; but that's an additional detail.
Priority queue expects Comparator to implement less - as any other container requiring weak ordering. However, it works by placing the bigger element on top. Since you effectively implemented greater, you reversed the queue, and now the smallest element goes on top.
To fix the problem, change your comparator to return lesser of the two elements.
let's say I have a map whose key is a pair and whose custom comparator guarantees unicity against the first element of that pair.
class comparator
{
public:
bool operator()(const std::pair<std::string, std::int>& left,
const std::pair<std::string, std::int>& right)
{
return left.first < right.first;
}
};
std::map<std::pair<std::string, std::int>, foo, comparator>;
Now I'd like this map to be more intelligent than that, if possible.
Instead of being rejected at insertion time in case a key with the same string as first element of the pair already exists, I'd to overwrite the "already existing element" if the pair's integer (.second) of the "possibly going to be inserted element" is bigger.
Of course I can do this by looking in to the map for the key, getting the key details and overwriting it if necessary.
Alternatively I could adopt a post-insertion approach with a multimap on top of which I would iterate to clean up duplicates keeping just the key with the biggest pair integer.
The question is : can I do that natively by overriding part of the stl implementation ([] operator - insert method) or improving my custom comparator and then simply relying on map's insert method ?
I don't know if this is accepted but we could imagine having a non const comprator which would be able of updating the already stored (key, value) pair under certain circumstances.
ValueThe answer to your question is that you cannot do it.
There are two problems with your proposed implementation:
The keys must remain const as they are the index for the map
Independent of what the comparator did to the elements it is comparing the std::map would still insert the item before or after left based on the return of the comparator
The solution to the problem is as suggested by #MvG. Your key should not be paired, it is your value that should be paired.
This has the added benefit that you don't need a custom comparator.
The problem is that you will need a custom inserter:
std::pair< int, foo >& tempValue = _myMap[ keyToInsert ];
if( valueToInsert.first >= tempValue.first )
{
tempValue = valueToInsert;
}
Note that this will only work if all the valueToInsert.firsts that you use are positive, cause the default constructor for an int is 0. If you had negative valueToInsert.firsts the default constructed value pair would be inserted instead of your element.
I have this small program that reads a line of input & prints the words in it, with their respective number of occurrences. I want to sort the elements in the map that stores these values according to their occurrences. I mean, the words that only appear once, will be ordered to be at the beginning, then the words that appeared twice 7 so on. I know that the predicate should return a bool value, but I don't know what the parameters should be. Should it be two iterators to the map? If some one could explain this, it would be greatly appreciated. Thank you in advance.
#include<iostream>
#include<map>
using std::cout;
using std::cin;
using std::endl;
using std::string;
using std::map;
int main()
{
string s;
map<string,int> counters; //store each word & an associated counter
//read the input, keeping track of each word & how often we see it
while(cin>>s)
{
++counters[s];
}
//write the words & associated counts
for(map<string,int>::const_iterator iter = counters.begin();iter != counters.end();iter++)
{
cout<<iter->first<<"\t"<<iter->second<<endl;
}
return 0;
}
std::map is always sorted according to its key. You cannot sort the elements by their value.
You need to copy the contents to another data structure (for example std::vector<std::pair<string, int> >) which can be sorted.
Here is a predicate that can be used to sort such a vector. Note that sorting algorithms in C++ standard library need a "less than" predicate which basically says "is a smaller than b".
bool cmp(std::pair<string, int> const &a, std::pair<string, int> const &b) {
return a.second < b.second;
}
You can't resort a map, it's order is predefined (by default, from std::less on the key type). The easiest solution for your problem would be to create a std::multimap<int, string> and insert your values there, then just loop over the multimap, which will be ordered on the key type (int, the number of occurences), which will give you the order that you want, without having to define a predicate.
You are not going to be able to do this with one pass with an std::map. It can only be sorted on one thing at a time, and you cannot change the key in-place. What I would recommend is to use the code you have now to maintain the counters map, then use std::max_element with a comparison function that compares the second field of each std::pair<string, int> in the map.
A map has its keys sorted, not its values. That's what makes the map efficent. You cannot sort it by occurrences without using another data structure (maybe a reversed index!)
As stated, it simply won't work -- a map always remains sorted by its key value, which would be the strings.
As others have noted, you can copy the data to some other structure, and sort by the value. Another possibility would be to use a Boost bimap instead. I've posted a demo of the basic idea previously.
You probably want to transform map<string,int> to vector<pair<const string, int> > then sort the vector on the int member.
You could do
struct PairLessSecond
{
template< typename P >
bool operator()( const P& pairLeft, const P& pairRight ) const
{
return pairLeft.second < pairRight.second;
}
};
You can probably also construct all this somehow using a lambda with a bind.
Now
std::vector< std::map<std::string,int>::value_type > byCount;
std::sort( byCount.begin(), byCount.end(), PairLessSecond() );