I have a 3D map container declared as follows:
std::map<std::string, std::map<std::string, std::map<std::string, CGridItem*> > > m_3DGridItems;
Suppose I have a CGridItem object pointer value, how can I get all the three map key strings in an efficient way? Thank you!
First of all, do you really need such a clumsy container ?
It would be much easier to have a Key structure:
struct Key {
std::string x;
std::string y;
std::string z;
};
And then define an ordering on Key:
bool operator<(Key const& left, Key const& right) {
if (left.x < right.x) { return true; }
if (left.x > right.x) { return false; }
if (left.y < right.y) { return true; }
if (left.y > right.y) { return false; }
return left.z < right.z;
}
Then you can have a much easier structure to manipulate:
std::map<Key, GridItem*>
If you need to map both ways, check out Boost.Bimap which maintains a two-ways mapping Key <-> GridItem* (so you don't have to sync two structures yourself).
You can just use iterator to get all key/value in a map. When the value is a map too, you can get the key/values the same way ...
First thing: if you are primarily making look-ups like that, this data-structure is definitely not the best performing alternative.
I don't see any other way than to make three nested for loops, since the map is laid out to make look-ups by key and not by value. It would look something like this:
std::map<std::string, std::map<std::string, std::map<std::string, CGridItem*> > >:iterator it1;
CGridItem* obj = ...;
for(it1 = mymap.begin(); it != mymap.end(); ++it1)
{
std::map<std::string, std::map<std::string, CGridItem*> > it2;
for(it2 = it1->second.begin(); it2 != it->second.end(); ++it2)
{
std::map<std::string, CGridItem*> it3;
for(it3 = it2->second.begin(); it3 != it2->second.end(); ++it3)
{
if(it3->second == obj) {
/*found it!*/
/* your 3 strings are in it1->first, it2->first, it3->first */
}
}
}
}
EDIT: I propose the following data structure:
std::map<CGridItem*, std::tuple<std::string, std::string, std::string> > mymap;
This maps your CGridItem object to 3 strings. Note: the std::tuple may not be available when you are not using c++11, but it is available in the boost libraries.
Related
I have a multimap with duplicates. When I finished the collection of the elements i would like to erase the dups.
Here is the container:
std::multimap<int, std::pair<int, bool>> container;
This following code is inside an iteration.(it is a simpler version of the original)
container.emplace(LeafId, std::make_pair(NodeId, isElectronic));
Is it good solution?
std::pair<int, std::pair<int, bool>> lastValue {-1 , {-1, -1}};
for (auto it = container.cbegin(); it != container.cend();)
{
if (it->first == lastValue.first && it->second == lastValue.second)
{
it = container.erase(it);
} else
{
lastValue = *it;
++it;
}
}
Is it good solution?
Unless you keep internal pair sorted inside multimap, no it is not a good solution as it would miss duplicates. If you can change data type then you can use:
std::map<int, std::vector<std::pair<int, bool>>>
instead of std::multimap and then for each element sort vector and remove duplicates using standard algorithms as described here Removing duplicates in a vector of strings
if you cannot I suggest to use additional std::set or std::unordered_set:
std::set<std::pair<int, bool>> tset;
int lastValue = 0;
for (auto it = container.cbegin(); it != container.cend();)
{
if( it->first != lastValue ) {
tset.clear();
lastValue = it->first;
}
if( !tset.insert( it->second ).second )
it = container.erase( it );
else
++it;
}
I have three maps of integer
std::map<string,int> map1;
map1["ymax"]=10;
map1["ymin"]=16;
map1["xval"]=10;
std::map<string,int> map2;
map2["ymax"]=16;
map2["ymin"]=20;
map2["xval"]=28;
std::map<string,int> map3;
map3["ymax"]=16;
map3["ymin"]=20;
map3["xval"]=10;
and a map contain this maps
std::map<string,std::map<string,int>> almap;
allmap["map1"]=map1;
allmap["map2"]=map2;
allmap["map3"]=map3;
I want to sort the last map as a key ymin in the inner map, but if remain equal maps in big map I want to sort as a key xval then as key ymax, same idea
The right sort to allmap >> map1,map3,map2
Create vector of all the maps and sort them by tieing their keys in the order of key's specified priority:
vector<map<string,int>> v{map1, map2, map3};
std::sort(v.begin(), v.end(), [](std::map<string,int> &lhs, std::map<string,int> &rhs){
return tie(lhs["ymax"], lhs["ymin"], lhs["xval"]) <
tie(rhs["ymax"], rhs["ymin"], rhs["xval"]);}
);
Live Demo
For the purposes of education...
std::map requires that the key in the key/value pair is invariant. It also requires that the key fully describes the ordering.
In the case of allmap, the key provided is a std::string, which is all the map has to go on - even with a complex custom comparison function.
In order to allow any kind of sorting, we would need to roll both the outer name and the maps they represent into one key object, and sort on that.
This starts to argue for either using a set of objects (since there is now no associated data) or keeping a separate, sorted index of keys, sorted by our custom predicate.
Here is the latter:
#include <string>
#include <map>
#include <set>
#include <vector>
#include <utility>
#include <algorithm>
struct by_keys
{
template<class...Keys>
by_keys(std::map<std::string, std::map<std::string, int>> const& allmap, Keys&&...keys)
: keys_ { std::forward<Keys>(keys)... }
, allmap_(allmap)
{
}
bool operator()(const std::string& ls, const std::string& rs) const
{
auto& l = allmap_.find(ls)->second;
auto& r = allmap_.find(rs)->second;
for (auto& key : keys_)
{
auto const& il = l.find(key);
auto const& ir = r.find(key);
if (il == std::end(l) && ir == std::end(r)) return false;
if (il == std::end(l) && ir != std::end(r)) return true;
if (il != std::end(l) && ir == std::end(r)) return false;
if (*il < *ir) return true;
if (*ir < *il) return false;
}
return false;
}
std::vector<std::string> keys_;
std::map<std::string, std::map<std::string, int>> const& allmap_;
};
int main()
{
std::map<std::string,int> map1;
map1["ymax"]=10;
map1["ymin"]=16;
map1["xval"]=10;
std::map<std::string,int> map2;
map2["ymax"]=16;
map2["ymin"]=20;
map2["xval"]=28;
std::map<std::string,int> map3;
map3["ymax"]=16;
map3["ymin"]=20;
map3["xval"]=10;
std::map<std::string,std::map<std::string,int>> allmap;
allmap["map1"]=map1;
allmap["map2"]=map2;
allmap["map3"]=map3;
// ok, now lets make an index into this map
std::vector<std::string> sorted_keys;
for (auto& entry : allmap) { sorted_keys.push_back(entry.first); }
std::sort(std::begin(sorted_keys), std::end(sorted_keys),
by_keys(allmap, "ymin", "xval", "ymax"));
// sorted_keys should now contain the names "map1", "map3", "map2"
}
I have a map<int, map<int, int>>.
The first key stands for node, the second key stands for an attribute, and the 'deepest' element represents a particular value.
I need to check that element, but doing the following unnecessary adds keys to my map:
map<int, map<int, int>> test;
if (test[4][3] > 5)
{
//do something
}
The alternative as I think of it is
map<int, map<int, int>> test;
if (test.find(4) != test.end())
{
if (test[4].find(3) != test[4].end())
{
if (test[4][3] > 5)
{
//do something
}
}
}
Is there a better way to do this? I don't know if there exists the key [4][3] in the map, and I don't want to unnecessarily add it. Thanks!
This should work - first you search in the first map and saves the returned iterator, then if that iterator is valid you search the map that he points to and saves the result in another iterator, and if he's valid that means that the objects you seek exists and you just check the value of it:
map<int, map<int, int>> test;
map<int, map<int, int>>::iterator it1;
map<int, int>::iterator it2;
if ((it1 = test.find(4)) != test.end())
{
if ((it2 = it1->second.find(3)) != it1->second.end())
{
if (it2->second > 5)
{
//do something
}
}
}
return 0;
Not fundamentally different from Tomer Arazy's suggestion, but making use of C++11's decltype to get rid of repeated type declarations (correcting which can be tedious when you modify the data type of the map):
#include <map>
#include <iostream>
int main()
{
using std::map;
map<int, map<int, int>> test;
test[4][3] = 6;
decltype(test.begin()) outer;
decltype(test.begin()->second.begin()) inner;
if (((outer = test.find(4)) != test.end())
&& ((inner = outer->second.find(3)) != outer->second.end())
&& (inner->second > 5))
std::cout << "Found!" << std::endl;
return 0;
}
(Note that the argument of decltype() isn't evaluated, so this works even if the map is empty.)
This will obviously only work with C++11.
Another thing to mention in the context of C++11 is that std::map has now an at() function similar to std::vector. It returns the value for a given key, and throws an exception if the key doesn't exist. This only a useful idea if you consider the non-existence of the key as an error condition. But if so, you could use
if (test.at(4).at(3) > 5)
std::cout << "Found!" << std::endl;
And then possibly catch the std::out_of_range exception somewhere.
I think this might make it a bit easier to see what's going on:
map<int, map<int, int>> test;
map<int, map<int, int>>::iterator outer = test.find(4);
if (outer != test.end())
{
map<int, int>::iterator inner = outer->second.find(3);
if (inner != outer->second.end())
{
if (inner->second > 5)
{
//do something
}
}
}
return 0;
How can I can gain access to a map which is stored in std::set? I need to do something like
for (iterator=map.begin(); iterator!=map.end(); iterator++) {
some_function(iterator->first);
}
, but instead of map im using set containing maps.
It's not very different from iterating any other map.
set<map<int, int> > s;
for (set<map<int, int> >::iterator it = s.begin(); it != s.end(); ++it) {
for (map<int, int>::iterator iter = it->begin(); iter != it->end(); ++iter) {
.. do something ...
}
}
So first you iterate over the set and then over the elements of the map pointed to by the outer container's iterator. I have used map<int, int> here just for illustration.
Using range-for makes this much simpler (assuming I understand your question):
for (map<int, int>& m : my_set) {
some_function(m);
}
I have a map and I want the first column i.e (*it).first to be pushed back into a vector then (*it)->second to be pushed back into another vector
Is this the best way to do it?
std::vector<std::string>test;
for ( it=mymap.begin() ; it != mymap.end(); it++ )
{
test.push_back((*it).first);
}
My other question is if i have a loop i.e
how would I insert all the integers i into (*it).first?
for(int i = 0; i < 10; i++)
{
// 1 - 10 will go in (*it).first
}
I want to have some integers in (*it).first and have associated values in (*it).second;
Use std::transform.
First define two functions key and value which take the pair of strings and return the first or second value, respectively.
#include <map>
#include <vector>
#include <algorithm>
#include <iostream>
#include <iterator>
const std::string& key(const std::pair<std::string, std::string>& keyValue)
{
return keyValue.first;
}
const std::string& value(const std::pair<std::string, std::string>& keyValue)
{
return keyValue.second;
}
Then use std::transform from <algorithm> with the functions to transform the map into either a vector of keys or a vector of values.
int main()
{
using namespace std; // be explicit normally, trying to be brief here
map<string, string> contacts;
contacts["alice"] = "555-2701";
contacts["bob"] = "555-2702";
vector<string> keys(contacts.size());
vector<string> values(contacts.size());
transform(contacts.begin(), contacts.end(), keys.begin(), key);
transform(contacts.begin(), contacts.end(), values.begin(), value);
cout << "Keys:\n";
copy(keys.begin(), keys.end(), ostream_iterator<string>(cout, "\n"));
cout << "\n";
cout << "Values:\n";
copy(values.begin(), values.end(), ostream_iterator<string>(cout, "\n"));
return 0;
}
Output:
Keys:
alice
bob
Values:
555-2701
555-2702
Your first question, "how can I push the first column of my map into one vector and the 2nd column into another" is solved thus:
std::map<std::string, std::string> mymap;
std::vector<std::string> keys;
std::vector<std::string> values;
for ( std::map<std::string,std::string>::iterator it=mymap.begin() ; it != mymap.end(); ++it )
{
keys.push_back(it->first);
values.push_back(it->second);
}
Your second question, "how would insert all the integers i into (*it).first ?" is solved thus:
std::map<int, int> mymap2;
for(int i = 0; i < 10; i++)
{
// Insert default value into map
// This sets '(*it).first' to 'i' and
// '(*it).second' to a default value (in
// this case, 0).
mymap2[i];
}
or
std::map<int, int> mymap3;
for(int i = 0; i < 10; i++)
{
// Insert specified value into map
// this sets '(*it).first' to 'i', and
// '(*it).second' to the value returned from the function.
maymap3[i] = ChooseSpecificValue(i);
}
Well, it can be done with a simple loop:
for (auto const& p: mymap) {
vec1.push_back(p.first);
vec2.push_back(p.second);
}
Or using the std::transform algorithm, though it's quite verbose here:
std::transform(mymap.begin(), mymap.end(), std::back_inserter(vec1),
[](MyMap::const_reference p) { return p.first; });
Assuming you've declared your map as string key and value (ie map<string, string> mymap; then it would be like below, also assuming you've declare 'it' variable as map<string, string>::iterator it, etc:
std::vector<std::string> test;
std::vector<std::string> second;
std::map<string, string>::iterator it;
for ( it=mymap.begin() ; it != mymap.end(); it++ )
{
test.push_back((*it).first);
second.push_back((*it).second);
}
Not sure about your next question.
The first part of your question:
std::vector<std::string> test;
std::vector<std::string> test2; // assuming map is from string to string
for (it = mymap.begin(); it != mymap.end(); ++it)
{
test.push_back(it->first); // push first in one vector
test2.push_back(it->second); // push second in another vector
}
So, yes a simple for can do what you want.
The second part of your question:
Since you are updating the key of the map, you would need to remove it from the map and insert the changed one. So:
std::string first, second;
first = it->first;
second = it->second;
mymap.erase(it); // be careful with invalidating iterator
// change first
mymap[first] = second;
To change first by adding all integers i to it, that would really depend on the type of first. For example with a string, you may mean something like this:
ostringstream sout;
for (int i = 0; i < 10; ++i)
sout << (i?" ":"") << i;
first = sout.str();
Or if first is for example a set, you may mean something like this:
for (int i = 0; i < 10; ++i)
first.insert(i);
and my other question is if i have a loop i.e how would insert all the
integers i into (*it).first?
In the case of a std::map, you can't modify the iterator returned like that ... the key member (i.e., the first) in the std::map key/value pair data-structure is intentionally designated as a constant value, and is initialized to its constant value at the beginning of the key/value pair's lifetime in the std::map data-structure. If the keys weren't constant, you would end up creating havoc when you change the key, since the nodes in a std::map are suppose to be sorted by the keys. The second member of the key/value pair data-structure is the member that can be changed.
So if you want to insert a set of key/value pairs in a map, you could simply do the following:
std::map<int, int> mymap;
int some_other_value = 100;
for (int i=0; i < 10; i++)
{
mymap[i] = some_other_value++;
}
it here will be an iterator which will point to one of the position in map and at max have one first and second value for one iterator . At max you can have multiple key or same key holding same/different values depending on key/value combination.
As far as pushing the value in the vector for a key in map is concern you can do it in the same way you are pushing the key
std::vector<std::string>test;
std::vector<std::string>test2;
for ( it=mymap.begin() ; it != mymap.end(); it++ )
{
test.push_back((*it).first);
test2.push_back((*it).second);
}
Neways yours question is very unclear .
Just in case you want to deal with different data types in your map I would template a generic copy function:
template <class A, class B>
void mycopy(std::map<A, B>&m, std::list<A>& keys, std::list<B>& values) {
typename std::map<A, B>::iterator it;
for (it = m.begin(); it != m.end(); ++it) {
keys.push_back( (*it).first );
values.push_back( (*it).second );
}
}
Mixing it up:
std::map<int, std::string> mymap;
std::list<int> keys;
std::list<std::string> values;
mymap[1] = "string1";
mymap[2] = "string2";
mycopy(mymap, keys, values);
std::map<std::string, int> mymap1;
std::list<std::string> keys1;
std::list<int> values1;
mymap1["string1"] = 1;
mymap1["string2"] = 2;
mycopy(mymap1, keys1, values1);
Edit: yes __copy isnt the best definition. Thanks