In my current C++-project I have an STL map which maps integer keys onto objects. An algorithm returns a set of entries. The returned data depends on the algorithm's input and hence cannot be predicted:
class MyClass
{
//...
};
int myAlgorithm(vector<int>::iterator inputIt)
{
// return a key for myMap which is calculated by the current value of inputData
}
int main(int argc, char *argv[])
{
vector<int> inputData;
map<int, MyClass> myMap;
//<fill map with some data>
//<fill inputData>
vector<MyClass> result;
for (vector<int>::iterator it = inputData.begin(); it != inputData.end(); it++)
{
int myMapKey = myAlgorithm(*it);
// count() > 0 means "check whether element exists. Performance can be improved by replacing
// the operator[] and count() calls by map::find(). However, I want to simplify things
// in this example.
if (myMap.count(myMapKey) > 0)
{
// in some cases there is no entry in myMap
result.push_back(myMap[myMapKey]);
}
}
}
As mentioned in the example I can replace map::count()
and operator[]
-calls with find. The STL-reference says that map::find()'s complexity is logarithmic in size (O(log n)
).
I discovered that in most cases the entries in myMap are very close for two sequent entries in the result. Therefore I came to the conclusion that I would achieve better performance if I replaced the map.find() calls by iterators:
map<int, MyClass>::iterator myMapIt = myMap.begin();
for (vector<int>::iterator it = inputData.begin(); it != inputData.end(); it++)
{
int myMapKey = myAlgorithm(*it);
// just increment iterator
while (myMapKey != myMapIt->first)
{
myMapIt++;
// we didn't find anything for the current input data
if (myMapIt == myMap::end() || myMapIt->first > myMapKey)
{
break;
}
}
// I know that I'm checking this twice, but that's not the point of my
// question ;)
if (myMapIt == myMap::end() || myMapIt->first > myMapKey)
{
// probably it would be better to move the iterator back to the position
// where we started searching, to improve performance for the next entry
myMapIt = myMap.begin();
}
else
{
result.push_back(myMapIt.second);
}
}
This concept works but I have a big problem: Depending on the inputData, I have to search forward or backward. Consider that I call the code inside main()
multiple times and the inputData changes for these calls. Instead of checking whether to increment or decrement the iterator inside the while
-loop, I could decide that before entering the for
-loop.
I thought that I'm fine with just switching the map<>::iterator
to map<>::reverse_iterator
and using rbegin()
/rend()
instead of begin()
/end()
. But then I realized that reverse_iterator
and iterator
have no common base class:
map<int, MyClass>::base_iterator myIt;
if (/* ... */)
{
myMapIt = myMap::begin();
myMapEndIt = myMap::end();
}
else
{
myMapIt = myMap::rbegin();
myMapEndIt = myMap::rend();
}
/* for (...) ... */
That would be great, but there is no base_iterator
.
I know a simple workaround for this problem: I just need to copy the whole for
-loop and adjust it for both cases:
if (/* ... */)
{
/* for(...) which uses normal iterator in the while-loop */
}
else
{
/* for(...) which uses reverse iterator in the while-loop */
}
Very bad ... Do you know a better solution?
A common base type is unnecessary when the language allows Generic Programming.
What you simply need to realize is that instead of having a long-winded linear functions with several choices along the way, you can have several nested function in which each choice lead to a different call.
Taking your example:
boost::any_iterator start, end;
if (/* ... */) {
start = map.begin(), end = map.end();
} else {
start = map.rbegin(), end = map.rend();
}
// do something with start and end
You can transform the code into the following:
// Define a free-function in the .cpp to help factor common stuff
template <typename FwdIt>
static void dosomething(FwdIt start, FwdIt end) {
// do something with start and end
}
And then inject the call straight into the if/else
body:
if (/* ... */) {
dosomething(map.begin(), map.end());
} else {
dosomething(map.rbegin(), map.rend());
}
And one good thing is that you reduce the number of changes of states within your functions and thus their complexity.
Use a templated function. The only place in the Standard library where inheritance is used over templates is IOstreams, as far as I'm aware (and that was a mistake).
template<typename Iterator> ... stuff(Iterator begin, Iterator end) {
// implement loop here
}
if (/*...*/) {
stuff(map.rbegin(), map.rend());
} else {
stuff(map.begin(), map.end());
}
However, I question if you would simply be better off changing to an always O(1) container, like an unordered_map
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With