HB-
HB-

Reputation: 617

A better alternative to using a long series of "if/else if" statements in C?

Originally I used switch/case, but the condition had to be a constant value that the variable would match, versus a boolean of whether the variable was within the range.

Instead, I have this monstrosity:

    if ( data[y] > 91 ) {
       grades[9] = grades[9] + 1;
    }
    else if ( data[y] > 88 && data[y] < 92 ) {
        grades[8] = grades[8] + 1;
    }
    else if ( data[y] > 84 && data[y] < 89 ) {
        grades[7] = grades[7] + 1;
    }
    else if ( data[y] > 81 && data[y] < 85) {
        grades[6] = grades[6] + 1;
    }
    else if ( data[y] > 79 && data[y] < 82) {
        grades[5] = grades[5] + 1;
    }
    else if ( data[y] > 74 && data[y] < 79 ) {
        grades[4] = grades[4] + 1;
    }
    else if ( data[y] > 71 && data[y] < 75 ) {
        grades[3] = grades[3] + 1;
    }
    else if ( data[y] > 68 && data[y] < 72 ) {
        grades[2] = grades[2] + 1;
    }
    else if ( data[y] > 59 && data[y] < 69 ) {
        grades[1] = grades[1] + 1;
    else {
        //data[y] < 60:
        grades[0] = grades[0] + 1;
    }

Does anybody know a nicer way to handle this block of code, since my switch/case idea can't apply? Surely there has to be a better way to do this.

Upvotes: 7

Views: 9450

Answers (8)

Jonathan Leffler
Jonathan Leffler

Reputation: 753455

For a radical variation on the theme, assuming marks can only be in the range 0..100, consider creating an array which contains the grade number for the score:

enum { MAX_SCORE = 100 };
static const int upper_bound[] = { 60, 69, 72, 75, 80, 82, 85, 89, 92, MAX_SCORE+1 };
enum { N_BOUNDS = sizeof(upper_bound) / sizeof(upper_bound[0]) };
int grade_for_score[MAX_SCORE + 1];

int score = 0;
for (int i = 0; i < N_BOUNDS; i++)
{
    while (score < upper_bound[i])
        grade_for_score[score++] = i;
}

The compensation for the setup work is that processing a score is trivial:

assert(data[y] >= 0 && data[y] <= MAX_SCORE);
grades[grade_for_score[data[y]]++;

This might be a benefit because it doesn't require any conditionals to process the grades as they come in, beyond the basic validation that the score is in the range [0..MAX_SCORE]. It does use some data space, of course. Of course, if the data is being read from a file (or a database), the I/O interaction time completely swamps the grading time, and the optimization is essentially pointless.

Working code:

#include <assert.h>
#include <stdio.h>

int main(void)
{
    /* Setup */
    enum { MAX_SCORE = 100 };
    static const int upper_bound[] = { 60, 69, 72, 75, 80, 82, 85, 89, 92, MAX_SCORE+1 };
    enum { N_BOUNDS = sizeof(upper_bound) / sizeof(upper_bound[0]) };
    int grade_for_score[MAX_SCORE + 1];

    int score = 0;
    for (int i = 0; i < N_BOUNDS; i++)
    {
        while (score < upper_bound[i])
            grade_for_score[score++] = i;
    }

    /* Scoring - test each score from [0..MAX_SCORE] */
    int grades[N_BOUNDS] = { 0, };
    int data[1];
    int y = 0;
    for (int i = 0; i <= MAX_SCORE; i++)
    {
        data[y] = i;
        assert(data[y] >= 0 && data[y] <= MAX_SCORE);
        grades[grade_for_score[data[y]]]++;
    }

    /* Print grades */
    int sum = 0;
    for (int i = 0; i < N_BOUNDS; i++)
    {
        sum += grades[i];
        printf("Grade %d: %3d (cumulative %3d)\n", i, grades[i], sum);
    }

    return 0;
}

Note that it would be possible to initialize the grade_for_score table at compile time instead of doing it at run time:

/* Manually generated; not formally checked for correctness */
int grade_for_score[] =
{
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    1,  1,  1,  1,  1,  1,  1,  1,  1,  2,
    2,  2,  3,  3,  3,  4,  4,  4,  4,  4,
    5,  5,  6,  6,  6,  7,  7,  7,  7,  8,
    8,  8,  9,  9,  9,  9,  9,  9,  9,  9,
    9
};

However, the table is easily computed from the table of upper bounds, and computers are better at getting the counting right than humans, so it is better to create the table at runtime.

Example run:

Grade 0:  60 (cumulative  60)
Grade 1:   9 (cumulative  69)
Grade 2:   3 (cumulative  72)
Grade 3:   3 (cumulative  75)
Grade 4:   5 (cumulative  80)
Grade 5:   2 (cumulative  82)
Grade 6:   3 (cumulative  85)
Grade 7:   4 (cumulative  89)
Grade 8:   3 (cumulative  92)
Grade 9:   9 (cumulative 101)

Upvotes: 0

KevinZ
KevinZ

Reputation: 3301

int const datum = data[y];
int const index = (datum > 59) + (datum > 68) + (datum > 71)
    + (datum > 74) + (datum > 79) + (datum > 81)
    + (datum > 84) + (datum > 88) + (datum > 91);
++ grades[index];

Just calculate the index.

Upvotes: 0

shoelzer
shoelzer

Reputation: 10708

Use a loop. You'll also need an array of the minimum data value for each grade. Here's some untested code as an example:

for (int i = 9; i >= 0; i--)
{
    if (data[y] >= minDataValueForGrade[i])
    {
        grades[i]++;
        break;
    }
}

It's short, easy to read, and makes it really easy to change the values that correspond to each grade.

Upvotes: 5

ouah
ouah

Reputation: 145829

If you want to avoid the long serie of if .. else what about:

int arr[] = {60, 69, 72, 75, 79, 82, 85, 89, 92};
int i = 0;

while (i < sizeof arr/ sizeof *arr && data[y] < arr[i] && i++);

grades[i]++;

Upvotes: 2

Kerrek SB
Kerrek SB

Reputation: 476930

You might use a simple lookup table:

const unsigned int index[] = { 1, 1, /* ... */, 2, 2, /* ... */ };

++grades[data[y] < 60 ? 0 : index[data[y]]];

Upvotes: 2

hobbs
hobbs

Reputation: 239652

This seems like a sensible data-driven approach:

int grade_cutoff[] = { 59, 68, 71, 74, 79, 81, 84, 88, 91, INT_MAX };
int grade_bucket;

for (grade_bucket = 0; data[y] > grade_cutoff[grade_bucket]; grade_bucket++) {
  /* nothing */
}

grades[grade_bucket]++;

Upvotes: 6

Frank V
Frank V

Reputation: 25419

My C is rusty, to be truthful, however, you might consider looking at the problem from a different perspective than a conditional.

You should (I think) be able to restructure your data so that you could implement the decision logic without (or with much less) conditionals. I'm thinking something like a 2 dimensional array or a lookup table. Something along those lines....

Upvotes: 0

ooga
ooga

Reputation: 15501

The most obvious to shorten your code is to get rid of the unnecessary second tests:

if      (data[y] >= 92) ++grades[9];
else if (data[y] >= 89) ++grades[8];
else if (data[y] >= 85) ++grades[7];
else if (data[y] >= 82) ++grades[6];
else if (data[y] >= 80) ++grades[5];
else if (data[y] >= 75) ++grades[4];
else if (data[y] >= 72) ++grades[3];
else if (data[y] >= 69) ++grades[2];
else if (data[y] >= 60) ++grades[1];
else                    ++grades[0];

Upvotes: 15

Related Questions