1.11M Members

Lookup tables - how to perform a switch using a string

 
0
 

Intro

The focus of this tutorial is the code relating to decision making and selection, and not the I/O code. I do not advocate using scanf() in this way for anything other than testing blocks of code in 'toy' programs. If you feel the need to use scanf() when writing more serious programs, please read this: http://c-faq.com/stdio/scanfprobs.html

Switch statement selection

A common question from C beginners comes along the lines of "How can I use a switch statement with a string or a character array?" (It was a question similar to this which prompted the creation of this tutorial) to which the short and simple answer is: You can't. Because a switch statement requires an integral value, such as int, long or char. A similar cry is often heard from beginners to C++ and Java. The usual solution is to write an if/else block instead - but this loses some of the simplicity and brevity which can make switch statements more attractive.

A more in-depth answer to this question may be that there is a workaround available, which allows the use of a switch statement, with the ability to make a decision based on a string.

The 'vending machine' program

Given a common beginner problem to introduce switch statements, the 'vending machine', where a user types in their choice of drink/snack/etc, the program is generally restricted to a numbered menu

Typical Output

"Press 1 for chocolate"
    "Press 2 for biscuits"
    "Press 3 for crisps"
    etc.

Such a program may be written as follows (For brevity, this tutorial only deals with the selection code)

#include <stdio.h>

int main()
{
    int input;

    printf("Enter the snack number: ");
    scanf("%d", &input);

    switch ( input )
    {
    case 0:
        printf("Walkers Cheese & Onion\n");
        break;
    case 1:
        printf("Cadburys Dairy Milk\n");
        break;
    case 2:
        printf("Schweppes Lemonade\n");
        break;
    case 3:
        printf("McVities Digestives\n");
        break;
    default:
        printf("Invalid selection\n");
    }
    return 0;
}

Aside from only handling integer input, the use of integer values loses the meaning of the code. At least that can be easily improved using enumerated constants -

#include <stdio.h>

enum { ITEM_CRISPS, 
       ITEM_CHOCOLATE, 
       ITEM_LEMONADE,
       ITEM_BISCUITS };

int main()
{
    int input;

    printf("Enter the snack number: ");
    scanf("%d", &input);

    switch ( input )
    {
    case ITEM_CRISPS:
        printf("Walkers Cheese & Onion\n");
        break;
    case ITEM_CHOCOLATE:
        printf("Cadburys Dairy Milk\n");
        break;
    case ITEM_LEMONADE:
        printf("Schweppes Lemonade\n");
        break;
    case ITEM_BISCUITS:
        printf("McVities Digestives\n");
        break;
    default:
        printf("Invalid selection\n");
    }
    return 0;
}

But this still doesn't address the issue that selection must be based on an integer, rather than a human readable word or sentence.

Allowing string input - The wrong way!

One possible solution may be a helper function which takes a string as input, and returns a value based on a set of if/else statements. Rather than passing the user's input directly to the switch, we can use this function to resolve the input to an integral value that is acceptable to the switch statement.

#include <stdio.h>
#include <string.h>

enum { ITEM_CRISPS, 
       ITEM_CHOCOLATE, 
       ITEM_LEMONADE,
       ITEM_BISCUITS,
       ITEM_NONE };

int lookup(char* snack)
{
    if( strcmp(snack, "crisps") == 0 )
        return ITEM_CRISPS;
    else if( strcmp(snack, "chocolate") == 0 )
        return ITEM_CHOCOLATE;
    else if( strcmp(snack, "lemonade") == 0 )
        return ITEM_LEMONADE;
    else if( strcmp(snack, "biscuits") == 0 )
        return ITEM_BISCUITS;
    else
        return ITEM_NONE;
}

int main()
{
    char* input;

    printf("Enter the snack's name: ");
    scanf("%s", input);

    switch ( lookup(input) )
    {
    case ITEM_CRISPS:
        printf("Walkers Cheese & Onion\n");
        break;
    case ITEM_CHOCOLATE:
        printf("Cadburys Dairy Milk\n");
        break;
    case ITEM_LEMONADE:
        printf("Schweppes Lemonade\n");
        break;
    case ITEM_BISCUITS:
        printf("McVities Digestives\n");
        break;
    default:
        printf("Invalid selection\n");
    }
    return 0;
}

Another enumerated value, ITEM_NONE, has been added, so that the lookup function is able to return a value when the user's input does not match any of the available snack names.

Overall, this is a very poor solution, and merely adds an extra level of selection. In fact, it puts back into play the ugly if/else block which we were trying to avoid in the first place - The switch statement here suddenly becomes completely superfluous.

Allowing string input - A better way!

What we need is another way to resolve the string to an integer. The answer is to perform a kind of reverse lookup using an array, which holds a list of all the available snacks' names from the vending machine. Every position in an array has a unique index number, which will correspond directly with one of the lines in the switch statement.

#include <stdio.h>
#include <string.h>

enum { ITEM_CRISPS, 
       ITEM_CHOCOLATE, 
       ITEM_LEMONADE,
       ITEM_BISCUITS,
       ITEM_NONE };

const char* lookup_table[] = { "crisps",
                               "chocolate",
                               "lemonade",
                               "biscuits" };

int lookup(char snack[])
{
    const int available_snacks = sizeof lookup_table / sizeof *lookup_table;

    for(int i=0; i!= available_snacks; i++)
        if( strcmp( snack, lookup_table[i]) == 0 )
            return i;
    return ITEM_NONE;
}

int main()
{
    char* input;

    printf("Enter the snack's name: ");
    scanf("%s", input);

    switch ( lookup(input) )
    {
    case ITEM_CRISPS:
        printf("Walkers Cheese & Onion\n");
        break;
    case ITEM_CHOCOLATE:
        printf("Cadburys Dairy Milk\n");
        break;
    case ITEM_LEMONADE:
        printf("Schweppes Lemonade\n");
        break;
    case ITEM_BISCUITS:
        printf("McVities Digestives\n");
        break;
    default:
        printf("Invalid selection\n");
    }
    return 0;
}

The important fact here is that enum statements and arrays both naturally begin at 0. The lookup() function is an example of a simple linear search algorithm, which returns the position of the snack name inside our lookup_table array.
The position found by this reverse lookup process is solely based on the user's input. If the user's input is not matched to any snack name in lookup_table, the function returns ITEM_NONE, as before.

Since the position of the snack name in lookup_table is designed to synchronise with the enum statement, it is imperative when altering the lookup table, that the enum statement is altered to match. The fact that the two are entirely dependent on each another is a serious drawback, so this method should be used with care (However, under a different design approach, it would be possible to create a lookup system without this dependency).

Improving the lookup table

One more problem with this code, is case-sensitivity. currently, if the user types "CHOCOLATE" or "Chocolate" instead of "chocolate", the program will output a message claiming the selection is invalid. The user's input must first be converted to lowercase, so that its recognised by the search algorithm

#include <stdio.h>
#include <string.h>
#include <ctype.h>

const char* lookup_table[] = { "crisps",
                               "chocolate",
                               "lemonade",
                               "biscuits" };

enum { ITEM_CRISPS, 
       ITEM_CHOCOLATE, 
       ITEM_LEMONADE,
       ITEM_BISCUITS,
       ITEM_NONE };

void convert_lowercase(char str[])
{
    for(int i=0; i!=strlen(str); i++)
        str[i] = tolower(str[i]);
}

int lookup(char snack[])
{
    convert_lowercase(snack);

    const int available_snacks = sizeof lookup_table / sizeof *lookup_table;

    for(int i=0; i!= available_snacks; i++)
        if( strcmp( snack, lookup_table[i]) == 0 )
            return i;
    return ITEM_NONE;
}

int main()
{
    char* input;

    printf("Enter the snack's name: ");
    scanf("%s", input);

    switch ( lookup(input) )
    {
    case ITEM_CRISPS:
        printf("Walkers Cheese & Onion\n");
        break;
    case ITEM_CHOCOLATE:
        printf("Cadburys Dairy Milk\n");
        break;
    case ITEM_LEMONADE:
        printf("Schweppes Lemonade\n");
        break;
    case ITEM_BISCUITS:
        printf("McVities Digestives\n");
        break;
    default:
        printf("Invalid selection\n");
    }
    return 0;
}

In summary

There's nothing stopping this method being adapted for Python, C++, Java, or any other language, However, other languages could make this program far simpler through the use of their own container libraries to handle Strings, Arrays and Search algorithms (for example C++ has std::string, std::vector and std::find ).

The real focus of this tutorial should show how versatile the concept of lookup tables can be when associating the program's behaviour with different data values, regardless of their type. Extending the capabilities of a switch statement is only one limited use for this technique. Selection can be fairly long and unweildy - in this example, it would be possible to remove the selection process entirely using lookups from data structures, however, that extends beyond the scope of this tutorial.

 
0
 

This is a very bad no-no:

char* input;
 
    printf("Enter the snack's name: ");
    scanf("%s", input);

This is C++ or C99:

int lookup(char snack[])
{
    convert_lowercase(snack);
 
    const int available_snacks = sizeof lookup_table / sizeof *lookup_table;
 
    for(int i=0; i!= available_snacks; i++)

I'd recommend cleaning these up and making it C90 if it's aimed at a C audience. Because this code first failed to compile for me, and then repeatedly crashed (not good in a tutorial).

Also, using strlen as a loop condition is not recommended for performance reasons -- best not to get into the habit, and the usual idiom is < rather than != for safety reasons:

for(i=0; i!=strlen(str); i++)
Isn't it about time forums rewarded their contributors?

Earn rewards points for helping others. Gain kudos. Cash out. Get better answers yourself.

It's as simple as contributing editorial or replying to discussions labeled or OP Kudos

You
This article has been dead for over six months: Start a new discussion instead
Post:
Start New Discussion
Tags Related to this Article