views:

626

answers:

9

I am trying to improve a C++ assignment to make it more efficient. I am a beginner with the language (and programming in general too), so I am only using what I know so far (if, else). I have a function that converts scores into levels, so anything under 30 = 1, 30-49 = 2, 50-79 = 3 and so on...

Here is how I am doing it:

if (score1 <= 30) level1 = 1;
else if (score1 <= 49) level1 = 2;
else level1 = 3;

if (score2 <= 30) level2 = 1;
else if (score2 <= 49) level2 = 2;
else level2 = 3;

//etc...

Is there a better way to do this, as I am aware this will require a new line for every single score I have.

+5  A: 

Create a function where you pass in the score and it returns the level. Also, if there are going to be a lot of them you should create an array of scores and levels.

for (x=0;x < num_scores;x++)
{
   level[x] = get_level(score[x]);
}

something like that.

phkahler
+1 for the function idea. The array idea might not be the best, however, since the original post is clearly based on ranges.
Randolpho
@Randolpho: Should still be able to handle ranges easily enough with an array (and if even if there are many, a binary search will still be possible).
Oli Charlesworth
@Randolpho: the arrays are used to store the scores and levels, not as a lookup table to determine a level based on a score.
Luc Touraille
The array is for the starting scores. The get_level() function returns the correct level for the score. And if your ranges change, you need to fix one if statement, not N of them.
Thomas Jones-Low
Why use `x != num_scores` instead of `x < num_scores`?
jball
Apologies, I misread the array suggestion.
Randolpho
@jbail: I use != because I couldn't remember how to put less-than on Stack Overflow and didn't feel like looking it up again. The inability to use that symbol easily seems really stupid for a programming related site :-)
phkahler
well just as you didn't write the actual inequality symbol `≠` and wrote `!=` you could have just written `<=`, which is how you write it in most programming languages anyway :)
KennyCason
Fixed for you. :)
musicfreak
+4  A: 

No, In terms of efficiency it's already optimized.

On another stylistic note. I would recommend putting the conditions and statements on separate lines:

if (score1 <= 30) {
    level1 = 1;
} else if (score1 <= 49) {
    level1 = 2;
} else if (score1 <= 79) {
    level1 = 3;
}

and as the other answer suggest, another stylistic plus would be to extract out the common behavior into a function

KennyCason
Optimized, maybe, but definitely not maintainable, which should always be your primary focus.
Randolpho
I agree, I would create a function personally like phkahler said (I even voted it up), but to answer the OP, his if statements can't really be written to function faster (or at least I can't think of a way to do so)
KennyCason
Ah, though, I did just see the last line of his question ("is there a better way to do this")
KennyCason
In terms of maintainability, the one-entry-per-line style of the OP seems _a lot_ easier to me than yours.
sbi
I guess it has always been my pet peeve to open conditional statements with { and close them with }, so seeing the OP's style makes me instantly want to change it. :)
KennyCason
@Kenny: (You need to properly @address people in comment responses, so that your replies show up on their Responses tab.) I used to like that, too (C#'s reliable auto-indentation somewhat changed that lately), but if the code aligns as good as in an `else if` structure, that always makes it easier to see if you made a mistake somewhere.
sbi
+13  A: 

This rather depends on what you mean by efficiency. You could keep the limits for each level in an array

int level_limits[] = {0, 30, 49, 79, [...]};

int getLevel(int score)
{
   int level;
   for (level = 0; level < N_LEVELS; ++level)
       if (level_limits[level] > score)
            return level;
   return level; // or whatever should happen when you exceed the score of the top level
 }
 ...

 level1 = getLevel(score1);
 level2 = getLevel(score2);

... or something like that.

Paul
`l+=1` is spelled `++l` in C++.
sbi
That's just a style issue? Personally, I prefer l += 1
Paul
This seems to be the most easily maintainable and extendable solution. The mainline code just calls a well-named function, `getLevel`, you have a simple representation for defining the levels (the array), and `getLevel` is easy to unit test. (Even better would be to use a variable named `level`, so that the `l` doesn't look like a `1`. Readability is important, and there's no need to abbreviate in this case. Most style guides will recommend `++level` over `level += 1`.)
Sancho
I wouldn't recommend to use l as a variable name, because it is easy to confuse with 1 (I actually did =)).
vitaut
Alright, already :) Edited the code...
Paul
Typo in array declaration - Nice idea though!
Dijkstra
Thanks. Fixed the typo
Paul
Scoping error in the second `return level`.
bitmask
So there is. Fixed
Paul
@Paul: That's not just a style issue. `for(...; ...; ++i)` is idiomatic in C and C++, and will be more easily recognized by anyone looking at your code. When others see `i+=1` they'll have to internally translate this into `++i`. (Note that I don't say `++i` is easier to read than `i+=1`. I'm just saying that it's idiomatic and that people are more used to it.)
sbi
+5  A: 

First of all factor out the code for computing the level into a separate function, say get_level:

level1 = get_level(score1);
level2 = get_level(score2);

You can implement get_level in different ways.

If the number of levels is small you can use the linear search:

const int bounds[] = {30, 49, 79}; // Add more level bounds here.

int get_level(int score)
{
    const size_t NUM_BOUNDS = sizeof(bounds) / sizeof(*bounds);
    for (size_t i = 0; i < NUM_BOUNDS; ++i)
        if (score <= bounds[i])
            return i + 1;
    return NUM_BOUNDS + 1;
}

Or, if you are an STL fan:

#include <algorithm>
#include <functional>

const int bounds[] = {30, 49, 79}; // Add more level bounds here.

int get_level(int score)
{
    return std::find_if(bounds,
        bounds + sizeof(bounds) / sizeof(*bounds),
        std::bind2nd(std::greater_equal<int>(), score)) - bounds + 1;
}

If you have many levels binary search may be more appropriate:

#include <algorithm>

const int bounds[] = {30, 49, 79}; // Add more level bounds here.

int get_level(int score)
{
    return std::lower_bound(bounds,
        bounds + sizeof(bounds) / sizeof(*bounds), score) - bounds + 1;
}

Or if you have relatively small number of levels then use if-else chain similar to your original version:

int get_level(int score)
{
    if (score <= 30)
        return 1;
    else if (score <= 49)
        return 2;
    else if (score <= 79)
        return 3;
    return 4;
}

Note that putting returns on separate lines can make your program easier to trace in the debugger.

vitaut
HAHAHA! I love the ternary!
Ishpeck
std::lower_bound(bounds,bounds + sizeof(bounds) / sizeof(*bounds), score) - bounds + 1; You have to love the STL sometimes, but I do wonder if the authors of it have ever actually written any working code.
Martin Beckett
They did - it was STL itself =)
vitaut
@Martin: STL to me feels like a design someone came up with to shoe-horn features from other high level languages into C++, without requiring a new compiler.
Merlyn Morgan-Graham
@vitaut: The lower_bound is almost perfect, if it weren't for all the cruft of turning an iterator back into an index. Because of that, I think the first option is the best you have here. I also wouldn't take the ternary option, unless it could be re-formatted like the original if-then blocks. It takes no-brainer code and makes it so you have to think about it for a second, which is exactly the opposite of what you're trying to do.
Merlyn Morgan-Graham
@Merlyn: I wouldn't use the version with ternary operators either - it was only to illustrate the point. It should probably be removed because it was written when a question was about efficiency, not clarity.
vitaut
@Merlyn - It always seems they have tried to accomodate every possible usage without thinking of the 90% case. In reality bugs from missing that +1 is going to waste more programmer time than having lower_bound ever saved.
Martin Beckett
+2  A: 

The absolute fastest way:

Create an array of 80 values, one for each possible score. Fill in the array with the level for each possible score.

Example: int score_array[80] = {1,1,1,1,...};

The following code gets you the level for each score:

level2 = score_array[score2];

This will compile down to one machine instruction. Doesn't get much faster.

Jay
I'd stay away from "absolute fastest". That one instruction does a random access on a large array (were not given any upper limit to the value), which can cause page faults and goodness knows what else, all leading to many thousands of instructions being processed before that one instruction completes!
Skizz
I wouldn't call this a "large" array, but that's a good point.
Jay
+2  A: 

If the score has a "manageable" range, how about the following?

// Convert score to level. Valid scores are in the range [0-79]
int score2level( int score ) {

    static const int level_table[ 80 ] = {
        1, 1, 1, ...     // 31 "1"s for range 0-30
        2, 2, 2, ...     // 19 "2"s for range 31-49
        3, 3, 3, ...     // 30 "3"s for range 50-79
    };

    assert( (0 <= score) && (score <= 79) );

    return level_table[ score ];
 }

The motivation is to avoid conditional code (the if, elses) at the cost of a pre-populated table. May be its too much for 3 levels, but may help when the number of levels increase.

ArunSaha
+1  A: 
  int getLevel(int score)
  {
      if(score<=30)
          return 1;
      int level=2,limit=49;
      while(score > limit)
      {
          limit+=30;
          level++;
      }
      return level;
  }  

  int score[]={35,25,67,56,78};
  int level[5];
  for(int i=0;i<5;i++)
      level[i]=getLevel(score[i]);

Cheers :)

ShyamLovesToCode
+1  A: 

You can utilize modulo and a hash table in order to achieve some code style elegance. Pseudo code:

int getLevel(int num)
{
   hash_table = (30=>1, 49=>2, 79=>3);
   foreach threshold (keys hash_table) {
      if (num modulo (threshold + 1) == num) {
        return hash_table[threshold];
      }
   }
}
D_K
+1  A: 

If you have less levels than fingers on your hands, you should use Paul's suggestion. However, if the number of levels are bigger, you could use binary search if your level thresholds are fairly uniform (and strictly increasing). Then you can get close to logarithmic runtime (I mean, you were asking for efficiency).

This is a good compromise between the lookuptable-suggestion and the linear search, but again, it only pays if you have a lot of levels.

Oh, by the way, if there is a pattern in how the thresholds are chosen, you should use that instead of searching for the right level.

bitmask