Shorthand for multiple equivalency checks in c

Viewed 104

Is there any shorthand for the following in C?

if (letter == 'a' || letter == 'd' || letter == 'i' || letter == 'k') {
    ...
}

In python there's something like the in operator where it can be done like:

if (letter in ['a','d','i','k']) {
   ...
}

How are multiple equivalency checks normally done in C? Or is long-form the only way?

5 Answers

If letter is a byte value, you could write:

if (memchr("adik", letter, 4))

or if letter is known to not be a null byte1:

if (strchr("adik", letter))

strchr() and memchr() are declared in <string.h>.


1 strchr("adik", 0) will return a pointer to the null terminator of the string "adik", so the test would evaluate to true for letter == 0.

Rather than scanning a string with (e.g. strchr et. al.), if you're doing this a lot and you have more chars in your "set", you could use a translation table.


The initialization may seem a bit cumbersome, but it would be very fast to execute.

char list[256] = {
    ['a'] = 1,
    ['d'] = 1,
    ['i'] = 1,
    ['k'] = 1,
};

int
match(int letter)
{

    return list[(unsigned char) letter];
}

A more general way may be to initialize the table once from a string.

char list[256];

void
init(char *list,const char *str)
{

    memset(list,0,256);
    for (;  *str != 0;  ++str)
        list[(unsigned char) *str] = 1;
}

int
match(int letter)
{

    return list[(unsigned char) letter];
}

int
main(void)
{

    init(list,"adik");

    // ...
}

UPDATE:

thanks, could you explain this initialization a bit more? char list[256] = { ['a'] = 1, ['d'] = 1,...} ? I've never seen that notation before.

It's what's called a "designated initializer".

There are two types: one for arrays, and one for structs. They have different syntax.

Normal initializers are "positional".

With any initializer, any position that is not explicitly initialized gets a value of 0.

One of the key features is that designated initializers can appear in any order, unlike positional ones.

Since you have python experience, they are [particularly for the struct variants], similar/analogous to keyword arguments in python function calls.


Arrays:

For example, ['a'] = 1 statically initializes the array element just as if you had done (in executable code): list['a'] = 1;

These are equivalent:

int arr[10] = { 0, 0, 0, 0, 7, 0, 0, 0, 0, 0 };
int arr[10] = { 0, 0, 0, 0, 7 };
int arr[10] = { [4] = 7 };

And, these:

int arr[10] = { 0, 0, 0, 0, 7, 6, 0, 0, 0, 0 };
int arr[10] = { 0, 0, 0, 0, 7, 6 };
int arr[10] = { [5] = 6, [4] = 7 };

Structs:

The initializer syntax is:

.sym = value

Here's an example:

struct foo {
    int x;
    int y;
    const char *z;
};

struct foo str1 = { 0, 23, "hello" };
struct foo str2 = { .z = "hello", .y = 23 };

Combinations:

We can combine/intersperse these for more complex scenarios:

struct foo {
    int x;
    int y;
    const char *z;
};

struct bar {
    int j;
    const char *k;
};

struct joint {
    struct foo j_foo[3];
    struct bar j_bar[4];
    const char *jname;
};

struct joint jlist[6] = {
    [3] = {
        .jname = "jlist[3]",
        .j_bar = {
            [2] = { .j = 19, .k = "jbar2" },
            [0] = { .j = 62 },
        },
        .j_foo = {
            [1] = { .y = 37, .z = "jfoo1" },
            [0] = { .y = 17 },
        },
    },
};

There is no general shorthand for this, but for checking if a character is one of several you can use a standard function for searching a character in a string:

#include <string.h>

//.....
    if (strchr("adik", letter) { // This will return NULL if not found
      //  .....
    }
//.....

The canonical C way to check an integer type against a (small) set of constant values is with a switch statement.

switch(c)
{
case 'a':
case 'd':
case 'i':
case 'k':
    /* do something */
    break;
default:
    /* do something else */
    break;
}

This can be made somewhat more "shorthand" at the price of some macro trickery.

#include <stdio.h>

#define NARGS(...) NARGS_(__VA_ARGS__, 4, 3, 2, 1)
#define NARGS_(_4, _3, _2, _1, N, ...) N

#define CONCAT(A, B) CONCAT_(A, B)
#define CONCAT_(A, B) A##B

#define CASES_1(C, ...) case C
#define CASES_2(C, ...) case C: CASES_1(__VA_ARGS__)
#define CASES_3(C, ...) case C: CASES_2(__VA_ARGS__)
#define CASES_4(C, ...) case C: CASES_3(__VA_ARGS__)

#define CASES(...) CONCAT(CASES_, NARGS(__VA_ARGS__)) (__VA_ARGS__)

int main()
{
    char s[] = { 'a', 'K', 'x' };
    for(int n = 0; n < sizeof(s); n++)
    {
        switch(s[n])
        {
        CASES('a', 'd', 'i', 'k'):
            printf("char '%c' lowercase\n", s[n]); break;
        CASES('A', 'D', 'I', 'K'):
            printf("char '%c' uppercase\n", s[n]); break;
        default:
            printf("char '%c' not found\n", s[n]); break;
        }
    }
    return 0;
}

Output from code above using gcc (for some reason msvc requires an extra level of expansion):

char 'a' lowercase
char 'K' uppercase
char 'x' not found

How are multiple equivalency checks normally done in C? Or is long-form the only way?

If you want something similar to Python, you can put the values in an array and loop through them. You can wrap that into a function or macro, if needed.

Related