tags:

views:

100

answers:

4

Ok, this may be really noobish question, but i am wishing there is something i dont know yet.

I go through a file, and check which string each line has, depending on the string value i execute a different function for it (or functions).

This is how i do it now:

Edit: I need to use variables outside the if-else-if range inside the if's, updated code:

string s1 = "used";
string s2 = "in";
string s3 = "functions";

if(str == "something"){
    something = process(s1, s2);
}else if(str == "something else"){
    something = process(s2, s3);
}else if(str == "something more"){
    something = process(s1, s3);
    something = process(s1, s2);
}else if(str == "something again"){
    // do more stuff
}else if(str == "something different"){
    // do more stuff
}else if(str == "something really different"){
    // do more stuff
}

I am afraid this will become "slow" after i have to repeat those else if lines a lot...

I tried to use switch() statement, but obviously it doesnt work here, is there something similar to switch() to use here?

A: 

That should be the most effective. But maybe a switch-case statement would be more elegant?

nicklasp
C++ only allows switch statements on integral values, not strings.
Eclipse
+5  A: 

If you just want to execute different functions, you can use a map from strings to function pointers or functors like boost::function / tr1::function:

void f1() { /* ... */ }
void f2() { /* ... */ }

// ... creating the map:
typedef void (*FuncPtr)();
typedef std::map<std::string, FuncPtr> FuncMap;
FuncMap fnMap;
fnMap["something"] = &f1;
fnMap["something else"] = &f2;

// ... using the map:
FuncMap::const_iterator it = fnMap.find(str);
if (it != fnMap.end()) { // is there an entry for str?
    it->second(); // call the function
}

As for passing parameters, with the details given so far i'd probably go for passing the unparsed remainder of the line or a list of string tokens to the functions and let them handle those as they see fit:

void f1(const std::vector<std::string>& tokens) { /* ... */ }
// ...

typedef void (*FuncPtr)(const std::vector<std::string>&);
typedef std::map<std::string, FuncPtr> FuncMap;
// ...

std::vector<std::string> tokens = /* ...? */; 
FuncMap::const_iterator it = fnMap.find(str);
if (it != fnMap.end()) { 
    it->second(tokens); 
}

You could also take a look at the interpreter example from Boost.FunctionTypes (header, source file) wether that is similar to your scenario.

Georg Fritzsche
+1 for being more elegant, but if std::string operator == compares the length before comparing the contents, the original version is liable to be faster, at least with the example strings whose lengths are different but have a common prefix which operator < has to iterate over several times for the map to perform a lookup.
Pete Kirkham
Hmm. Before i accept this, could you show how to send parameters to the functions?
Newbie
@Newbie: What parameters do you want to pass? Just a string, a variable amount of strings, variant types, ...?
Georg Fritzsche
1-3 parameters of strings depending on the function i call, but i use the same variables in all of the functions (look my edits), should that make it easier? i have no idea.
Newbie
@Newbie: Updated, does that fit? Otherwise you'd have to tell a bit more about your use-case.
Georg Fritzsche
Oh yeah, i guess i could send them in one vector too, was hoping to see how to send more than 1 parameter though. Could i do it like typedef void (*FuncPtr)(const string if i wanted 3 strings as parameter?
Newbie
@Newbie: That of course works fine too if your functions always only take 3 strings. A list is nice if you'd parse a format like say `functionName param1 param2 ... paramN`.
Georg Fritzsche
Yeah, but now i have to use tokens[0] etc in my functions, it looks ugly, is there some way to make alias for those, and then after 4 tokens i have to use the index number instead. So token[0] as token.first, token[3] as token.fourth, and token[4] as token[4] inside my functions, without me having to define those every time in all of my 500 functions? (would triple the amount of code required...) I decided to use a simple struct now, but i am afraid i have to switch back to token[0] etc. Should i make new question for this?
Newbie
@Newbie: If you always pass 3 strings as the first parameters, you could just use 3 string parameters and pass the rest as a vector? I guess for further discussion a new question might be better, take care to add enough details of what you want to do this time :)
Georg Fritzsche
A: 

I would try a simple data structure, but maybe C++ gurus and STL lovers can think something better. My idea is like

struct smap {
  string s;
  int i;
} str_int_map[] = { { string("strng"), 1 }, /*...*/ };

and then

  j = 0; //default if no match found
  for(i=0; i < 2; i++)
  {
    if ( str_int_map[i].s == a ) 
    {
      j = str_int_map[i].i;
      break;
    }
  }
  switch( j )
  {
  case 1:
    cout << "oooo"; break;
  case 2:
    cout << "nnnn"; break;
  }

Instead of "mapping" to integer, you could "map" to a function address and call it directly, if it fits your need this approach. Instead of this looping for match approach, you could use a real map/dictionary whatever is called in STL from string to number, and then switch on that number as the above example.

ShinTakezou
+1  A: 

First off, Georg's version with the map is more elegant and extensible than a long cascade of comparisons. However, if you are concerned with performance and your cascade being 'slow', then measure how fast it is.

For the example strings you give, I constructed three variations of a function which incremented a value in an array based on the string passed to it. The first was based on your code, the second on Georg's, and the third on the code below, which is the same as yours but with constant strings rather than having to construct the strings from literals each time it's called.

void foo (const std::string& str, int (&count)[6]) 
{
    static const std::string s0 = "something";
    static const std::string s1 = "something else";
    static const std::string s2 = "something more";
    static const std::string s3 = "something again";
    static const std::string s4 = "something different";
    static const std::string s5 = "something really different";

    if(str == s0) {
        ++count[0];
    }else if(str == s1) {
        ++count[1];
    }else if(str == s2) {
        ++count[2];
    }else if(str == s3) {
        ++count[3];
    }else if(str == s4) {
        ++count[4];
    }else if(str == s5) {
        ++count[5];
    }
}

Using VS 2008 Express on the default release settings, these come out as:

operator== with constant strings (the code above)

time 7.015 seconds
0.116917 microseconds per call

std::map lookup (Georg's code)

time 9.687 seconds
0.16145 microseconds per call

operator== with literal strings (original code)

time 10.437 seconds
0.17395 microseconds per call

So unless you're calling the code a lot, then you are not going to notice 174 nanoseconds for your original version. Assuming the map and the if-cascade behave as expected (O(N) and O(log2N)), then the map should be faster at around 13 strings. But if it matters that much to you, then profile or benchmark it.

Pete Kirkham
Ah, i guess the speed isnt really a problem, but the elseifs also look really ugly, i like the map example.
Newbie