I am looking for a data type (or at least a correct name for it) or a map like data structure that allows fast look ups in both directions.
something like:
class DoubleMap{
int getA(int b){
return b2a[b];
}
int getB(int a){
return a2b[a];
}
void insert(int a, int b){
a2b[a] = b;
b2a[b] = a;
}
std::map<int, int> a2b;
std::map<int, int> b2a;
};
of course templated and more sophisticated.
Is there a name for it and some std container or something from Qt or boost?
I suggest using a boost::bimap this is designed for lookups by the key or value.
So in your case you can just do:
#include <boost/bimap.hpp>
typedef boost::bimap< int, int > bm_type;
bm_type doubleMap;
then to perform lookup by key:
doubleMap.left.find(key)
lookup by value:
doubleMap.right.find(val)
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 ...
I have a common algorithm for 2 maps that uses find() and operator[] to access the map. However, elsewhere in the code I need to iterate over these maps and one of them needs to be sorted with a reverse comparison from the other. I ended up using the reverse iterator for that map, but profiling shows me that a huge amount of time is wasted on dereferencing the reverse iterator. I tried to do the following, but it obviously didn't work:
struct Custom
{
list<double> Doubles;
int Integer = 0;
};
typedef map<double, Custom> CustomMap;
typedef map<double, Custom, std::greater<double>> CustomMapGreater;
CustomMap A;
CustomMapGreater B;
...
void Algorithm(bool aChosen)
{
CustomMap* chosenMap;
if (aChosen)
{
chosenMap = &A;
}
else
{
chosenMap = &B; // Conversion not possible
}
// Algorithm that uses chosenMap follows
...
}
Any ideas on how I can get this to work? I have a feeling something can be done with templates, but I'm not very proficient with generic programming.
The template way look like:
template <typename Map>
void Algorithm(Map& map)
{
// ...
}
or, in your specific case, even
template <typename Comp>
void Algorithm(std::map<double, Custom, Comp>& map)
{
// ...
}
and then
void AlgorithmChooser(bool aChosen)
{
if (aChosen) {
Algorithm(A);
} else {
Algorithm(B);
}
}
You can use the same type for both maps:
typedef map<double, Custom, std::binary_function<const Custom &, const Custom &,bool>> CustomMap;
CustomMap lessMap( std::less<Custom>() );
CustomMap greaterMap( std::greater<Custom>() );
then you can pass them as the same type to a function or assign them to a pointer to CustomMap. Or for C++11 and later:
typedef map<double, Custom, std::function<bool( const Custom &, const Custom &)>> CustomMap;
I'm trying to use both a list and an unordered_map to store the same set of objects. I'm new to C++, so still getting comfortable with iterators.
Say I have the following test code:
class Test {
public:
int x;
int y;
int z;
Test (int, int, int);
}
Test t1 = Test(1,2,3);
Test t2 = Test(2,4,6);
Test t3 = Test(3,6,9);
std::list<Test> list;
std::unordered_map<int, Test> map;
list.push_back(t3);
list.push_back(t2);
list.push_back(t1);
map[101] = t1;
map[102] = t2;
map[103] = t3;
Is it possible to look up an object by key, and then generate a list iterator from the reference of the object (or from the unordered_map generator?)
So if I have the key 102, I could look up t2 in constant time. I then want to iterate forward/backward/insert/delete relative to t2's position in the list.
I can use find to get a unordered_map iterator pointing to t2. I don't know how to generate a list iterator that starts at t2 (I can only generate iterators at the beginning or the end of the list, and iterate through.)
Would appreciate anyone pointing me to good tutorials on the STL and iterators.
Thanks!
Afterthought:
Is this an acceptable approach? I have many objects and need to efficiently look them up by integer key. I also need to preserve their order (unrelated to these integer keys) and insert/delete/traverse efficiently.
If what you want to do is this:
Is it possible to look up an object by key, and then generate a list iterator from the reference of the object (or from the unordered_map generator?)
Then you can take advantage of the fact that list iterators aren't invalidated on insertion or erase (unless you erase that particular iterator) and reorganize your structures like this:
std::list<Test> list;
std::unordered_map<int, std::list<Test>::iterator> map;
map.insert(std::make_pair(101,
list.insert(list.end(), t1)));
map.insert(std::make_pair(102,
list.insert(list.end(), t2)));
map.insert(std::make_pair(103,
list.insert(list.end(), t3)));
That way your map lookup gives you exactly what you want: a list iterator.
While Barry's approach is good, there is another one, more advanced and complicated. You can put your data object, (integer) key, and all bookkeeping bits in a single chunk of memory. Thus data locality will be improved and pressure on memory allocator will be less. Example, using boost::intrusive:
#include <boost/intrusive/list.hpp>
#include <boost/intrusive/unordered_set.hpp>
#include <array>
using namespace boost::intrusive;
class Foo {
// bookkeeping bits
list_member_hook<> list_hook;
unordered_set_member_hook<> set_hook;
const int key;
// some payload...
public:
// there is even more options to configure container types
using list_type = list<Foo, member_hook<Foo, list_member_hook<>, &Foo::list_hook>>;
using set_type = unordered_set<Foo, member_hook<Foo, unordered_set_member_hook<>, &Foo::set_hook>>;
Foo(int key): key(key) {};
bool operator ==(const Foo &rhs) const {
return key == rhs.key;
}
friend std::size_t hash_value(const Foo &foo) {
return std::hash<int>()(foo.key);
}
};
class Bar {
Foo::list_type list;
std::array<Foo::set_type::bucket_type, 17> buckets;
Foo::set_type set{Foo::set_type::bucket_traits(buckets.data(), buckets.size())};
public:
template<typename... Args>
Foo &emplace(Args&&... args) {
auto foo = new Foo(std::forward<Args>(args)...);
// no more allocations
list.push_front(*foo);
set.insert(*foo);
return *foo;
}
void pop(const Foo &foo) {
set.erase(foo);
list.erase(list.iterator_to(foo));
// Lifetime management fun...
delete &foo;
}
};
int main() {
Bar bar;
auto &foo = bar.emplace(42);
bar.pop(foo);
}
Measure how good are both algorithms on your data. My idea may give you nothing but greater code complexity.
I have a mix of C and C++ files that collectively form my program. I am using boost unordered hash map which I initially defined as:
typedef boost::unordered_map<char *, int> my_map;
However, the map was acting weird; find(key) could not find keys that actually existed in the hash map. I, then, changed the definition to:
typedef boost::unordered_map<std::string, int> my_map;
and now the hash map works fine. Nevertheless, it is inconvenient for me to convert my char * to std::string in order to use the map. Is there a way to make the first definition work?
std::unordered_map
Oh aha. Noticed you wanted unordered_map. One of my favourites is to use boost::string_ref to represent strings without copying, so you could do
std::unordered_map<boost::string_ref, int> map;
with a quick & dirty hash implementation like:
namespace std
{
template<>
struct hash<boost::string_ref> {
size_t operator()(boost::string_ref const& sr) const {
return boost::hash_range(sr.begin(), sr.end());
}
};
}
See it Live On Coliru
std::map
You can use a custom comparator:
std::map<char const*, int, std::less<std::string> > map;
Note that this is highly inefficient, but it shows the way.
More efficient would be to wrap/use strcmp:
#include <cstring>
struct less_sz
{
bool operator()(const char* a, const char* b) const
{
if (!(a && b))
return a < b;
else
return strcmp(a, b) < 0;
}
};
And then
std::map<char const*, int, less_sz> map;
See it Live On Coliru