2
votes

I'm new to C and I met a problem when using sscanf() in C.

My code prompts the user to input several lines of data. A single data is like a Python tuple (1, 3) or (-3, 2), and each line may contain several "tuple" separated by zero or more spaces, my code is below,

char buffer[100];

while(fgets(buffer, sizeof buffer, stdin) != NULL) {
    while(sscanf(&buffer, "%*[^0-9|-]%i%*[^0-9|-]%i", &key, &value) != 1) {
        break;
    }
    printf("key==%d, value==%d\n", key, value);
}

I use fgets to read each line that the user inputs, and sscanf to get a tuple, but it seems that sscanf cannot match the second tuple in one line (or how to match). Why? Or is there some wiser solutions for my problem?

1
The simpler format string " (%d ,%d )%n" should also work, the space included before each punctuation will filter any whitespace. - Weather Vane
@WeatherVane This seems cannot work well since fgets function read all of the keyboard input of the user. If the user press left or right on their keyboard, fgets will also read them into the buffer... - Jasper Zhou
@JasperZhou but did you try it? it is in conjunction with the accepted answer, as it gets the number of bytes scanned. It won't filter out accidentally pressed keys, only whitespace: garbage in - garbage out! Ideally you reject any line that is nonsense. - Weather Vane

1 Answers

3
votes

The %n specifier will give the number of characters processed by the scan and store it in an int.
Accumulate those in an offset to iterate through the line.
This sscanf could return EOF, 0, 1 or 2. Consider using == 2 since you want to scan two integers.

char buffer[100];
int offset = 0;
int scanned = 0;
while ( fgets ( buffer, sizeof buffer, stdin) != NULL) {
    offset = 0;
    scanned = 0;
    while ( sscanf ( buffer + offset, "%*[^0-9|-]%i%*[^0-9|-]%i%n", &key, &value, &scanned) == 2) {
        offset += scanned;
        printf("key==%d,value==%d\n",key,value);
    }
}

In event %n does not work, strchr could be used to find a (, sscanf the ( and two integers, then find the closing ). Repeat.
This uses @WeatherVane's suggestion of a MUCH simpler format string.

char buffer[100];
char *begin = NULL;
char *end = NULL;
while ( fgets ( buffer, sizeof buffer, stdin) != NULL) {
    end = buffer;
    while ( begin = strchr ( end, '(')) {//get pointer to (
        if ( sscanf ( begin, "(%d ,%d", &key, &value) == 2) {//scan ( and two integers
            printf("key==%d,value==%d\n",key,value);
        }
        end = strchr ( begin, ')');//get pointer to )
        if ( ! end) {
            break;//did not find )
        }
    }
}

Another strategy could use strspn and strcspn

char buffer[100];
size_t begin = 0;
size_t count = 0;
while ( fgets ( buffer, sizeof buffer, stdin) != NULL) {
    begin = 0;
    while ( buffer[begin]) {// not zero
        count = strcspn ( &buffer[begin], "-0123456789");//count of non digit or -
        begin += count;
        if ( sscanf ( &buffer[begin], "%d ,%d", &key, &value) == 2) {//scan ( and two integers
            printf("key==%d,value==%d\n",key,value);
        }
        count = strspn ( &buffer[begin], " ,-0123456789");//count space , - or digit
        begin += count;
    }
}