Doubt on how to read a line in C (sscanf)

Asked

Viewed 30 times

-4

My goal is to read 2 user lines, where each line has a number n >= 0 of integers, separated by space. The problem is that, when n = 0 (which is the case of line 2 of the code below), the program rewrites the numbers read in the previous line, when the desired one was NOT to print any number. Does anyone know how I can do it?

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


int main(){
    char linha[1000];

    for(int i = 1; i <= 2; i++){          
        scanf("%[^\n]s", linha);            
        getchar();          
        int numeroLido, numChars;
        int pos = 0;
        
        while (pos < strlen(linha)) {
            sscanf(&linha[pos],"%d %n",&numeroLido,&numChars);
            printf("O numero lido na linha %d: %d\n", i, numeroLido);
            pos += numChars;
        }
    }

    return 0;
}

1 answer

0

sscanf() It wasn’t written for that. It’s to read tabular data, like csv files. If you use a mask with many specifiers --- those things that start with %and not by %%, --- can read a certain number of them. But it’s like a vector: the total of specifiers has to be a constant. And it has the throw of pointers. See below.

Example

    const char* mascara = "%d %d %d %d %d %d %d %d %d %d %d %d "
    //                      1  2  ..                        12

With this you can read up to 12 of them. sscanf() will return a number between 0 and 12 indicating how many could read.

A few test lines

    const char* linha[] = 
    {
    "-5 -4 -3 -2 -1",
    "4 3 2 1",
    "3 2 1",
    "2 1",
    "                   1", // #4
    "",
    NULL
    };

Consider these sample lines. Using that mask will read all without problem, but:

  • has to have a defined maximum of elements (12 in the example)

  • is very annoying because it has to have all variables in the list of arguments because sscanf() and family do not allocate memory

  • consider line #4: sscanf() will skip any amount of spaces, TAB and '\n' trying to satisfy the input, only it won’t tell you how much of the line jumped to get to the 1. And so you can’t use for example 1 + numero/10 to advance the pointer on the line: you can advance the digits of the number but do not know how many spaces, TAB and newline function read and ignored. And still have signal problem because number may be negative

A program to read these lines

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

int main(void)
{
    const char* linha[] = 
    {
    "-5 -4 -3 -2 -1",
    "4 3 2 1",
    "3 2 1",
    "2 1",
    "                   1", // #4
    "",
    NULL
    };

    const char* mascara = "\
%d %d %d %d %d %d %d %d %d %d %d %d "; // 12
    int numero = 0;

    for(int i = 0; linha[i] != NULL; i++)
    {
        printf("==> \"%s\"\n", linha[i]);
        int res = sscanf(*(linha + i), mascara, &numero, &numero,
                         &numero, &numero, &numero, &numero, &numero,
                         &numero, &numero, &numero, &numero, &numero);
        printf("sscanf() retornou %d\n", res);
    }
    return 0;
}

exit

==> "-5 -4 -3 -2 -1"
sscanf() retornou 5
==> "4 3 2 1"
sscanf() retornou 4
==> "3 2 1"
sscanf() retornou 3
==> "2 1"
sscanf() retornou 2
==> "                   1"
sscanf() retornou 1
==> ""
sscanf() retornou -1

This program consumes lines up to 12 numbers, no problem. But note that you need 12 pointers in the call. Of course I used the same address and sscanf() doesn’t care. It’s just to illustrate what I’m explaining. If it’s a small number the maximum number works fine.

But if the number is more than a half dozen better try another way.

Replicating scanf()

A naive path would be to replicate scanf(), skip whitespace, assemble strings and pass to sscanf() or atoi() to get the number :)

Example

void processa_linha(char*);

The following program does this.

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

void processa_linha(char*);

int main(void)
{
    char* linha[] = {"-5 -4 -3 -2 -1",
                     "4 3 2 1",
                     "3 2 1",
                     "2 1",
                     "                   1",  // #4
                     "",
                     NULL};

    int numero = 0;
    for (int i = 0; *(linha + i) != NULL; i++)
    {   // processa linha 'linha'
        char* p = *(linha + i);  // o inicio da linha
        printf("==>\tLinha %d: \"%s\"\n", 1 + i, p);
        processa_linha(p);
    }
    return 0;
}

void processa_linha(char* linha)
{
    printf("\tExtraidos: [ ");

    char        fase    = 0;
    long long   len     = 0;
    const char* mascara = "%d";
    int         numero  = 0;
    char*       p       = linha;
    char*       pal     = NULL;
    int         res     = 0;
    char*       st      = NULL;

    while (1)
    {
        switch (fase)
        {
            case 0:
                switch (*p)
                {
                    case 0:
                        // acabou
                        printf(" ]\n");  // termina a linha
                        return;
                        break;
                    case ' ':
                    case '\t':
                    case '\n':
                        break;
                    default:
                        st   = p;  // incio de string
                        fase = 1;
                        break;
                };
                p += 1;
                break;

            case 1:
                switch (*p)
                {
                    case ' ':
                    case '\t':
                    case '\n':
                    case 0:
                        // terminou: monta string
                        len = p - st;
                        pal = (char*)malloc(1 + len);
                        memcpy(pal, st, len);
                        *(pal + len) = 0;  // termina a string
                        //printf("achou \"%s\"\n", pal);
                        // sera um numero?
                        res = sscanf(pal, mascara, &numero);
                        if (res == 1) printf("%d ", numero);
                        free(pal);
                        if (*p == 0)
                        {
                            printf("]\n"); // termina a linha
                            return;
                        }
                        fase = 0;
                        break;

                    default:
                        break;
                };
                p += 1;
                break;

        };  // switch()
    };      // while()
    return;
}

example output

==>     Linha 1: "-5 -4 -3 -2 -1"
        Extraidos: [ -5 -4 -3 -2 -1 ]
==>     Linha 2: "4 3 2 1"
        Extraidos: [ 4 3 2 1 ]
==>     Linha 3: "3 2 1"
        Extraidos: [ 3 2 1 ]
==>     Linha 4: "2 1"
        Extraidos: [ 2 1 ]
==>     Linha 5: "                   1"
        Extraidos: [ 1 ]
==>     Linha 6: ""
        Extraidos: [ ]

And it can adapt to what it needs. A common way is to use

    int** processa_linha(char*);

And return a vector of int* finished by NULL. So you can call the function and it returns the neat values for you to process.

Another would be to create something like

typedef struct
{
    unsigned N; // quantos
    int*     valor;

}   Valores;

Valores* processa_linha(char*);

To return a vector of int and the total number on the line

If you need an example of this and can’t find it let me know and I’ll write one and add it here

sscanf() you fscanf()

If the data is in a file fscanf() does not have the problem of advancing the string pointer, since the file is, of course, FILE* a pointer.

So it’s simpler to consume the data.

But to not lose the notion of line it is necessary to use a delimiter, and can not be space nor TAB. As I said, these functions were written with csv type files in mind. The delimiter can be '\n' but it wouldn’t be very useful ;)

If you use a comma, the classic, see this program:

#include <stdio.h>
int main(void)
{
    FILE* in = fopen("teste.txt", "r");
    if (in == NULL) return -1;
    const char* mask = "%30[^,\n]%c"; // virgula
    char        delim;
    int         linha  = 1;
    int         numero = 0;
    int         res    = 0;
    char        campo[30];
    printf("linha %d: [ ", linha);
    while (1)
    {
        res = fscanf(in, mask, campo, &delim);
        switch (res)
        {
            case -1: // tanto faz se deu erro
            case 0: 
                printf("]\n\n");
                return 0;
                break;
            case 1:
                if (sscanf(campo, "%d", &numero) == 1)
                    printf("%d ", numero);
                break;
            case 2:
            default:  // so pode ser 2
                if (delim == '\n')
                {
                    linha += 1;
                    if (sscanf(campo, "%d", &numero) == 1)
                        printf("%d ]\nlinha %d: [ ", numero, linha);
                }
                else if (sscanf(campo, "%d", &numero) == 1)
                    printf("%d ", numero);
                break;
        }
    };  // while()
    return 0;
}

who reads this file

 -5, -4, x3x, -3, -2, -1
 4, 3, 2, 1
 3,2,1
 2,1
 1
x

And shows

linha 1: [ -5 -4 -3 -2 -1 ]
linha 2: [ 4 3 2 1 ]
linha 3: [ 3 2 1 ]
linha 4: [ 2 1 ]
linha 5: [ 1 ]
linha 6: [ ]

Yes, it’s very boring :D

Browser other questions tagged

You are not signed in. Login or sign up in order to post.