I have a problem with std::map.
I'm using it to map some list of pairs under a specific index:
map<string, list<pair<string, int> > > List;
It's used in Dijkstra algorithm.
The main problem is that map sorts string keys in alphabetical order, like this:
AAA, AA0, AA1, AAB, AC1 = AA0->AA1->AAA->AAB->AC1
But I would like to sort it in a different way:
AAA, AA0, AA1, AAB, AC1 = AAA->AAB->AA0->AA1->AC1
Is there any solution to this? I read about making my own comparing class, but I have no idea how to do this. Or maybe there's some other way to solve it?
You have to provide your own comparison functor, which must be passed as 3rd template parameter when instantiating the map. For example:
struct Comp
{
bool operator()(const std::string& lhs, const std::string& rhs) const
{
// implement your comparison logic here
}
};
Instances of this class is callable (hence "functor") with two string parameters, and should return true or false based in a strict weak ordering logic.
Then instantiate the map using the functor type:
std::map<string, list<pair<string, int>>, Comp> List;
Now the map will use your comparison logic internally to define the ordering of its elements.
Like others have said, you need to implement a custom comparer...
struct custom_comparer
{
bool operator()(const std::string& left, const std::string& right) const
{
return std::lexicographical_compare(
left.cbegin(), left.cend(),
right.cbegin(), right.cend(),
[](char l, char r) -> bool
{
bool ldigit = isdigit(l) != 0,
rdigit = isdigit(r) != 0;
return (!ldigit && rdigit) || (ldigit == rdigit && l < r);
});
}
};
And use it...
std::map<string, list<pair<string, int>>, custom_comparer> List;
Normal string comparison operators use lexicographical_compare. My custom_comparer above also uses it, but with a custom comparer plugged in. The custom comparer uses isdigit to do the comparison you want.
You have to write your own comparer:
struct custom_string_comparer
{
bool operator()(const std::string& s1, const std::string& s2)
{
return ...; // your comparison here
}
};
map<string, list<pair<string, int>>, custom_string_comparer> List;
Yes. You need to supply a third template argument, see the docs.
Related
I was trying to make a map sort by value using a custom comparator but I couldn't figure out why I kept getting the error of "no matching call to compareByVal"
Here's what I had in my main.cpp:
#include <map>
#include <iostream>
struct compareByVal {
bool operator[](const std::pair<int,int> & a, const std::pair<int,int> & b)
return a.second < b.second;
}
int main() {
std::map<int,int,compareByVal> hash;
hash[1] = 5;
hash[2] = 2;
hash[3] = 10;
std::cout << hash.begin()->first << std::endl;
}
The first, simple problem is
struct compareByVal {
bool operator[](const std::pair<int,int> & a, const std::pair<int,int> & b)
return a.second < b.second;
}
should be
struct compareByVal {
bool operator()(const std::pair<int,int> & a, const std::pair<int,int> & b) const {
return a.second < b.second;
}
};
The second, serious problem is the signature of the compare is wrong. It should be
struct compareByVal {
bool operator()(const int leftKey, const int rightKey) const;
}
You can't access the value in the compare function. There is no (simple) way to sort a map by value.
Simply put, you cannot. Not sure which compiler you're using, but clang and gcc both give useful messages. with context.
clang:
static_assert(__is_invocable<_Compare&, const _Key&, const _Key&>{},
gcc:
if (__i == end() || key_comp()(__k, (*__i).first))
You can see that clang and gcc are both calling the compare method with only they key, and not a value. This is simply how maps work.
If you want to sort by value, you would have to create your own custom map, or, more realistically, use the value as the key instead. Creating your own map to achieve this would be more difficult than you'd think, since it would have to sort after any value is modified.
If you want to sort a std::map by its value, then you are using the wrong container. std::map is sorted by the keys by definition.
You can wrap key and value:
struct foo {
int key;
int value;
};
and then use a std::set<foo> that uses a comparator that only compares foo::value.
Well, first, the reason you're getting the error: "no matching call to compareByVal" is because map's comparator works only with the keys. So the comparator should like:
struct compareByVal {
template <typename T>
bool operator()(const T& a, const T& b) const
return a < b;
}
Coming on to what you want to achieve, I see two ways of doing so:
Copy all the elements of the map to a std::vector and sort that:
std::vector<std::pair<int,int> > v(hash.begin(), hash.end());
std::sort(v.begin(), v.end(), [](const auto& a, const auto& b) { return a.second < b.second; });
Copy all the elements of the map to another map with keys as values and values as keys. If values of your map are not unique, you can use a std::multimap instead.
This may be an X-Y issue.
If you need to sort by both key and value, then a single std::map may not be the most efficient choice.
In database theory, all the data would be placed into a single table. An index table would be created describing the access or sorting method. Data that needs to be sorted in more than one method would have multiple index tables.
In C++, the core table would be a std::vector. The indices would be std::map<key1, vector_index>, std::map<key2, vector_index>, where vector_index is the index of the item in the core table.
Example:
struct Record
{
int age;
std::string name;
};
// Core table
std::vector<Record> database;
// Index by age
std::map<int, unsigned int> age_index_table;
// Index by name
std::map<std::string, unsigned int> name_index_table;
// Fetching by age:
unsigned int database_index = age_index_table[42];
Record r = database[database_index];
// Fetching by name:
unsigned int database_index = name_index_table["Harry Potter"];
Record r = database[database_index];
You can learn more by searching the internet for "database index tables c++".
If it looks like a database and smells like a database ...
I was trying to make a map sort by value using a custom comparator but I couldn't figure out why I kept getting the error of "no matching call to compareByVal"
Here's what I had in my main.cpp:
#include <map>
#include <iostream>
struct compareByVal {
bool operator[](const std::pair<int,int> & a, const std::pair<int,int> & b)
return a.second < b.second;
}
int main() {
std::map<int,int,compareByVal> hash;
hash[1] = 5;
hash[2] = 2;
hash[3] = 10;
std::cout << hash.begin()->first << std::endl;
}
The first, simple problem is
struct compareByVal {
bool operator[](const std::pair<int,int> & a, const std::pair<int,int> & b)
return a.second < b.second;
}
should be
struct compareByVal {
bool operator()(const std::pair<int,int> & a, const std::pair<int,int> & b) const {
return a.second < b.second;
}
};
The second, serious problem is the signature of the compare is wrong. It should be
struct compareByVal {
bool operator()(const int leftKey, const int rightKey) const;
}
You can't access the value in the compare function. There is no (simple) way to sort a map by value.
Simply put, you cannot. Not sure which compiler you're using, but clang and gcc both give useful messages. with context.
clang:
static_assert(__is_invocable<_Compare&, const _Key&, const _Key&>{},
gcc:
if (__i == end() || key_comp()(__k, (*__i).first))
You can see that clang and gcc are both calling the compare method with only they key, and not a value. This is simply how maps work.
If you want to sort by value, you would have to create your own custom map, or, more realistically, use the value as the key instead. Creating your own map to achieve this would be more difficult than you'd think, since it would have to sort after any value is modified.
If you want to sort a std::map by its value, then you are using the wrong container. std::map is sorted by the keys by definition.
You can wrap key and value:
struct foo {
int key;
int value;
};
and then use a std::set<foo> that uses a comparator that only compares foo::value.
Well, first, the reason you're getting the error: "no matching call to compareByVal" is because map's comparator works only with the keys. So the comparator should like:
struct compareByVal {
template <typename T>
bool operator()(const T& a, const T& b) const
return a < b;
}
Coming on to what you want to achieve, I see two ways of doing so:
Copy all the elements of the map to a std::vector and sort that:
std::vector<std::pair<int,int> > v(hash.begin(), hash.end());
std::sort(v.begin(), v.end(), [](const auto& a, const auto& b) { return a.second < b.second; });
Copy all the elements of the map to another map with keys as values and values as keys. If values of your map are not unique, you can use a std::multimap instead.
This may be an X-Y issue.
If you need to sort by both key and value, then a single std::map may not be the most efficient choice.
In database theory, all the data would be placed into a single table. An index table would be created describing the access or sorting method. Data that needs to be sorted in more than one method would have multiple index tables.
In C++, the core table would be a std::vector. The indices would be std::map<key1, vector_index>, std::map<key2, vector_index>, where vector_index is the index of the item in the core table.
Example:
struct Record
{
int age;
std::string name;
};
// Core table
std::vector<Record> database;
// Index by age
std::map<int, unsigned int> age_index_table;
// Index by name
std::map<std::string, unsigned int> name_index_table;
// Fetching by age:
unsigned int database_index = age_index_table[42];
Record r = database[database_index];
// Fetching by name:
unsigned int database_index = name_index_table["Harry Potter"];
Record r = database[database_index];
You can learn more by searching the internet for "database index tables c++".
If it looks like a database and smells like a database ...
Word.
I have a struct, containing a single field that I would like set to use for comparison and equivalence, and other fields as metadata:
struct read_tag{
unsigned int read_id; // want std::set to use this
int offset; // metadata
bool orientation; // metadata
};
I have a functor to do the job:
struct read_tag_compare {
bool operator() (const read_tag &a, const read_tag &b) const {
return a.read_id > b.read_id
}
};
and decl. the required set as
std::set<read_tag, read_tag_compare> block;
Everything so far compiles. The problem is below:
How do I make a set containing std::set<read_tag, read_tag_compare>. I want something like this:
std::set< std::set<read_tag, read_tag_compare> > blocks;
blocks.insert(a_block); // comp error
But this gives me a very large, and hard to decipher error.
I thought it would recursively check how the inner sets are compared and extend this to the outer sets. All one had to do is define the comparator for the inner most set.
For example
std::set<std:set<unsigned int>> set_o_sets;
works fine, without me having to define how to compare std::set<unsigned int>
Any help is mucho appreciated :D
The <-comparison on std::set uses std::lexicographical_compare without comparator, i.e. it just forwards to < on the element type. (This is a limitation of the standard library, since this is defined for all containers, not just the ordered-associative ones.) So what you need is a custom comparator for sets of sets that uses the correct overload of lexicographical comparison:
using read_tag_set = std::set<read_tag, read_tag_compare>;
struct read_tag_set_compare {
bool operator()(const read_tag_set &a, const read_tag_set &b) const noexcept {
return std::lexicographical_compare(a.begin(), a.end(),
b.begin(), b.end(), a.key_comp());
// ^^^^^^^^^^^^
}
};
Now use: std::set<read_tag_set, read_tag_set_compare>
The code shows why there isn't an obvious "fix" to the ordered associative containers that would make this "just work": If the containers use custom, stateful predicates, then it's not in general guaranteed that the members of two distinct containers can actually be compared with one another at all. All you know is that the elements within one container are comparable with that container's comparator. So when you're using a custom comparator, you better also say explicitly how two distinct containers relate, and you assert explicitly that it makes sense to compare two containers.
It compiled with no error with my g++-5.3.1 ubuntu..
#include<set>
#include<iostream>
using namespace std;
struct read_tag{
unsigned int read_id; // want std::set to use this
int offset; // metadata
bool orientation; // metadata
};
struct read_tag_compare {
bool operator() (const read_tag &a, const read_tag &b) const {
return a.read_id > b.read_id;
}
};
struct read_compare {
bool operator() (const set<read_tag, read_tag_compare> &a, const set<read_tag, read_tag_compare> &b) const {
return true;
}
};
int main()
{
set<read_tag, read_tag_compare> block;
set<set<read_tag, read_tag_compare>, read_compare> blocks;
blocks.insert(block)
}
Above was what I compiled.
I want to map md5 to sha1 and sha256 to sha1.
Instead of using 2 different maps I thought of using std::map<pair<string, string>, string>.
When inserting vlaues, I would have both md5 and sha256, but I can query only with md5 for exampe.
Example :
md5= x;
map.find (x,null)--> return sha1
sha256 =y;
map.find ("" , y) --> return sha1
map(x,y) ----> return sha1
Is there any way of doing it?
some kind of OR between the keys..
Thanks a lot
I once created a class for a similar purspose maybe it could help you.
template<typename T, typename K1, typename K2>
class BiKeyMap
{
public:
typedef boost::shared_ptr<T> ptr_type;
private:
std::map<K1, ptr_type > _map1;
std::map<K2, ptr_type > _map2;
public:
bool insert(const ptr_type& value, const K1& key1, const K2& key2)
{
bool lResult1;
bool lResult2;
lResult1= _map1.insert(std::make_pair(key1, value)).second;
lResult2= _map2.insert(std::make_pair(key2, value)).second;
return (lResult1&&lResult2);
}
ptr_type find1(const K1& key)
{
typename std::map<K1, ptr_type>::iterator itr = _map1.find(key);
if (itr == _map1.end())
throw ...;
return itr->second;
}
ptr_type find2(const K2& key)
{
typename std::map<K2, ptr_type>::iterator itr = _map2.find(key);
if (itr == _map2.end())
throw ....
return itr->second;
}
};
The problem is in the way the operator<is implemented between std::pair (that use lexicographic order between components, so that an empty string will always be "first").
Apart doing bad things like specialize operator< for std::pair<string,string> (that makes the new behavior available to any of such pairs even not involved in the map), you'll probably need as a key a class holding two strings, implementing operator< so that if one of the first members is empty the comparison is done only on the seconds.
like
struct mykey { std::string fisrt, std::string second; };
bool operator<(const mykey& a, const mykey& b)
{
int ck = (a.first.size() && b.first.size()) + 2*(a.second.size() && b.second.size());
return (ck==1)? a.first<b.first:
(ck==2)? a.second<b.second:
a.first+a.second < b.first+b.second;
}
and providing a ctor like
mykey::mykey(const std::string s1, const std::string& s2), so that you can create e "key to compare" by giving an empty string to s1 or s2, or insterting value in the map by giving both s1 and s2.
I am reading a object from a database of type Foo, as defined below. This object is a vector of Foo Members, where a Foo Members consists of a string id and a container object.
typedef std::pair<std::string, Container> FooMember;
typedef std::vector<FooMember> Foo;
I wish to iterate over a Foo object in its sorted form, where sorting is done with respect to the id. To do this I am using the following function to create first a sorted version of the object. As you can see, the object is sorted in a case insensitive manner. Is there a better way for me to iterate over this object compared to how I am currently doing it?
Foo sortedFoo(Foo& value) const {
Foo returnValue;
returnValue.reserve(value.size());
// use a map to sort the items
std::map<std::string, FooMember> sortedMembers;
{
Foo::iterator i = value.begin();
Foo::iterator end = value.end();
for(; i!=end; ++i) {
std::string name = i->first;
boost::algorithm::to_lower(name);
sortedMembers[name] = *i;
}
}
// convert the map to a vector of its values in sorted order
std::map<std::string, FooMember >::iterator i = sortedMembers.begin();
std::map<std::string, FooMember >::iterator end = sortedMembers.end();
for(; i!=end; ++i) {
returnValue.push_back(i->second);
}
return returnValue;
}
Yes: Copy the vector, then use std::sort with a custom comparison predicate:
struct ByIdCaseInsensitive {
bool operator ()(const FooMember& lhs, const FooMember& rhs) const {
return boost::algorithm::to_lower_copy(lhs.first) <
boost::algorithm::to_lower_copy(rhs.first);
}
};
Way more efficient than filling a map, and then copying back to a vector.
The predicate would be even better if it used a proper Unicode collation algorithm, but that isn't available in the standard library or Boost.
You can use std::sort
#include <algorithm>
bool comparator(const FooMember& i, const FooMember& j)
{
std::string str1 = i.first;
boost::algorithm::to_lower(str1);
std::string str2 = j.first;
boost::algorithm::to_lower(str2);
return (str1 < str2);
}
void sortFoo(Foo& value) {
std::sort (value.begin(), value.end(), comparator);
}
Or, you can keep Foo objects in a std::map<std::string, Foo> from the beginning so they remain always sorted.
The best way would be to use std::sort with a custom comparator for FooMembers:
bool cmp(const FooMember& lhs, const FooMember& rhs);
Foo sortedFoo(const Foo& value) const
{
Foo tmp = value;
return std::sort(tmp.begin(), tmp.end(), cmp);
}
where the comparison can be implemented with the help of std::lexicographical_compare and tolower:
#include <cctype> // for std::tolower
bool ci_cmp(char a, char b)
{
return std::tolower(a) < std::tolower(b);
}
#include <algorithm> // for std::sort, std::lexicographical_compare
bool cmp(const FooMember& lhs, const FooMember& rhs)
{
return std::lexicographical_compare(lhs.first.begin(),
lhs.first.end(),
rhs.first.begin(),
rhs.first.end(),
ci_cmp);
}
You can also use std::sort with a lambda expression:
std::sort(value.begin(), value.end(), [](const FooMember &lhs, const FooMember &rhs)
{
std::string str1 = i.first, str2 = j.first;
boost::algorithm::to_lower(str1);
boost::algorithm::to_lower(str2);
return str1 < str2;
});
Or use the version provided by erelender. It's up to you.
Semantically std::vector<std::pair<T,U> > is a std::map<T,U> (but implementations are usually different). If you can re-design Foo, you probably better do it. As side effect, you will get sorting for free.
typedef std::map<std::string, Container> Foo;