2009-10-02 19 views
8

Tengo una cadena separada por comas que puede contener campos vacíos. Por ejemplo:¿Cómo analizo los campos en una cadena separada por comas usando sscanf mientras apoyo campos vacíos?

1,2,,4 

El uso de un básica

sscanf(string,"%[^,],%[^,],%[^,],%[^,],%[^,]", &val1, &val2, &val3, &val4); 

consigo todos los valores anteriores al campo vacío y resultados inesperados en el campo vacío en adelante.

Cuando quito la expresión para el campo vacío de la sscanf(),

sscanf(string,"%[^,],%[^,],,%[^,],%[^,]", &val1, &val2, &val3, &val4); 

todo funciona bien.

Como no sé cuándo voy a obtener un campo vacío, ¿hay alguna manera de volver a escribir la expresión para manejar campos vacíos con elegancia?

Respuesta

8

Si usa strtok con la coma como carácter separador, obtendrá una lista de cadenas una o más de las cuales serán nulas/longitud cero.

Eche un vistazo a mi answer here para obtener más información.

+0

Sí, esa es la ruta que voy a ir si no puedo encontrar una manera de adaptar el sscanf. Todavía estoy esperando poder adaptar mi sscanf, pero por lo que puedo decir [^,] no coincide con las secuencias vacías. – Belrog

+0

@Belrog, parece que el contador interno de 'sscanf' se atasca si la cadena contiene' ,, '. Así que es mejor usar 'strtok'. –

+0

Realmente esperaba no tener que hacer eso. Tengo una larga lista de valores de diferentes tipos (chars/ints/floats). Eso significaría un contador de campo y una declaración de conmutación grande para asignarlo correctamente. * suspiro * – Belrog

5

man sscanf:

[ coincide con un no vacío secuencia de caracteres del conjunto especificado de caracteres aceptados;

(énfasis añadido).

+1

Muy cierto. ¿No hay bandera para cambiar este comportamiento? –

0

Parece que actualmente se trata de valores CSV. Si necesita extenderlo para manejar cadenas entre comillas (para que los campos puedan contener comas, por ejemplo), encontrará que la familia scanf no puede manejar todas las complejidades del formato. Por lo tanto, deberá usar un código diseñado específicamente para manejar (su variante de) formato CSV.

Encontrará un debate sobre las implementaciones de una biblioteca CSV en 'The Practice of Programming' - en C y C++. Sin duda, hay muchos otros disponibles.

0

scanf() devuelve la cantidad de elementos asignados. Tal vez pueda usar esa información ...

char *data = "1, 2,,, 5, 6"; 
int a[6]; 
int assigned = sscanf(data, "%d,%d,%d,%d,%d,%d", a, a+1, a+2, a+3, a+4, a+5); 
if (assigned < 6) { 
    char fmt[18]; 
    switch (assigned) { 
     default: assert(0 && "this did not happen"); break; 
     case 0: fmt = ",%d,%d,%d,%d,%d"; break; 
     case 1: fmt = "%d,,%d,%d,%d,%d"; break; 
     case 2: fmt = "%d,%d,,%d,%d,%d"; break; 
     case 3: fmt = "%d,%d,%d,,%d,%d"; break; 
     case 4: fmt = "%d,%d,%d,%d,,%d"; break; 
     case 5: fmt = "%d,%d,%d,%d,%d,"; break; 
    } 
    sscanf(data, fmt, a+(assigned<=0), a+1+(assigned<=1), a+2+(assigned<=2), 
         a+3+(assigned<=3), a+4+(assigned<=4)); 
} 

Ugh! Y eso es solo por 1 valor perdido
Como se ha señalado en otras respuestas, es mucho mejor que analice la cadena de la manera 'usual': fgets() y strtok().

0

Aquí está mi versión para escanear los valores int separados por comas. El código detecta campos vacíos y no enteros.

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

int main(){ 
    char str[] = " 1 , 2 x, , 4 "; 
    printf("str: '%s'\n", str); 

    for(char *s2 = str; s2;){ 
    while(*s2 == ' ' || *s2 == '\t') s2++; 
    char *s1 = strsep(&s2, ","); 
    if(!*s1){ 
     printf("val: (empty)\n"); 
    } 
    else{ 
     int val; 
     char ch; 
     int ret = sscanf(s1, " %i %c", &val, &ch); 
     if(ret != 1){ 
     printf("val: (syntax error)\n"); 
     } 
     else{ 
     printf("val: %i\n", val); 
     } 
    } 
    } 

    return 0; 
} 

Resultado:

str: ' 1 , 2 x, , 4 ' 
val: 1 
val: (syntax error) 
val: (empty) 
val: 4 
0

Ponga una '*' después del '%' para omitir la lectura.Además, es posible leer solo 3 caracteres, por ejemplo, '% 3s'.

+0

Su artículo no trata del tema que se está preguntando aquí. He eliminado el enlace para evitar que su respuesta se marque como spam. –

0

Llegué aquí en busca de respuestas a la misma pregunta. No quería dejar atrás la función scanf tampoco. Al final, construyo un zsscanf yo mismo, donde analicé el formato, examiné cada uno de los datos uno por uno y verifiqué el retorno de sscanf para ver si obtenía una lectura vacía en alguno. Este era en cierto modo mi caso particular: solo quería algunos de los campos, algunos de los cuales podrían estar vacíos y no podían suponer el separador.

#include <stdarg.h> 
#include <stdio.h> 

int zsscanf(char *data, char *format, ...) 
{ 
    va_list argp; 
    va_start(argp, format); 
    int fptr = 0, sptr = 0, iptr = 0, isptr = 0, ok, saved = 0; 
    char def[32]; 
    while (1) 
    { 
     if (format[fptr] != '%') 
     { 
      ok = sscanf(&format[fptr], "%28[^%]%n", def, &iptr); 
      if (!ok) break; 
      fptr += iptr; 
      def[iptr] = '%'; 
      def[iptr+1] = 'n'; 
      def[iptr+2] = 0; 
      ok = sscanf(&data[sptr], def, &isptr); 
      if (!ok) break; 
      sptr += isptr; 
     } 
     else 
      if (format[fptr+1] == '%') 
      { 
       if (data[sptr] == '%') 
       { 
        fptr += 2; 
        sptr += 1; 
       } 
       else 
       { 
        ok = -1; 
        break; 
       } 
      } 
      else 
      { 
       void *savehere = NULL; 
       ok = sscanf(&format[fptr], "%%%28[^%]%n", &def[1], &iptr); 
       if (!ok) break; 
       fptr += iptr; 
       def[0] = '%'; 
       def[iptr] = '%'; 
       def[iptr+1] = 'n'; 
       def[iptr+2] = 0; 
       isptr = 0; 
       if (def[1] != '*') 
       { 
        savehere = va_arg(argp, void*); 
        ok = sscanf(&data[sptr], def, savehere, &isptr); 
        if (ok == 0 && isptr == 0) 
        { 
         // Let's assume only char types. Won't hurt in other cases. 
         ((char*)savehere)[0] = 0; 
         ok = 1; 
        } 
        if (ok > 0) 
        { 
         saved++; 
        } 
       } 
       else 
       { 
        ok = sscanf(&data[sptr], def, &isptr) == 0; 
       } 
       if (ok < 0) break; 
       sptr += isptr; 
      } 
    } 
    va_end(argp); 
    return saved == 0 ? ok : saved; 
} 

int main() 
{ 
    char *format = "%15[^\t;,]%*1[\t;,]" // NameId 
        "%*[^\t;,]%*1[\t;,]" // Name 
        "%*[^\t;,]%*1[\t;,]" // Abbreviation 
        "%*[^\t;,]%*1[\t;,]" // Description 
        "%31[^\t;,]"; // Electrical Line 
    char nameId[16]; 
    char elect[32]; 
    char *line1 = "TVC-CCTV-0002\tTVC-CCTV-0002\tTVC-CCTV-0002\tCCTV DOMO CAMERA 21-32-29\tELECTRICAL_TopoLine_823\tfoo\tbar"; 
    char *line2 = "TVC-CCTV-0000;;;;;foo;bar;"; 

    int ok = zsscanf(line1, format, nameId, elect); 
    printf ("%d: |%s|%s|\n", ok, nameId, elect); 
    ok = zsscanf(line2, format, nameId, elect); 
    printf ("%d: |%s|%s|\n", ok, nameId, elect); 
    return 0; 
} 

Salida:

2: |TVC-CCTV-0002|ELECTRICAL_TopoLine_823| 
    2: |TVC-CCTV-0000|| 

se advirtió, no es totalmente probado y tiene limitaciones severas (las más obvias: sólo acepta %...s, %...c, %...[...] y requiere separadores como %...[...]; de lo contrario estaría Realmente me importa la cadena de formato, de esta manera solo me importa %).

0

tuve que modificar el código un poco para que funcione correctamente:

//rm token_pure;gcc -Wall -O3 -o token_pure token_pure.c; ./token_pure 
#include <stdio.h> 
#include <string.h> 

int main() 
{ 
    char str[] = " 1 , 2 x, , 4 "; 
    char *s1; 
    char *s2; 
    s2=(void*)&str; //this is here to avoid warning of assignment from incompatible pointer type 
     do { 
      while(*s2 == ' ' || *s2 == '\t') s2++; 
      s1 = strsep(&s2, ","); 
      if(!*s1){ 
       printf("val: (empty)\n"); 
      } 
      else{ 
       int val; 
       char ch; 
       int ret = sscanf(s1, " %i %c", &val, &ch); 
       if(ret != 1){ 
        printf("val: (syntax error)\n"); 
       } 
       else{ 
        printf("val: %i\n", val); 
       } 
      } 
     } while (s2!=0); 
     return 0; 
    } 

y la salida:

val: 1 
val: (syntax error) 
val: (empty) 
val: 4 
0

hice una modificación para archivos delimitados TSV, es de esperar que podrían ayudar:

//rm token_tab;gcc -Wall -O3 -o token_tab token_tab.c; ./token_tab 
#include <stdio.h> 
#include <string.h> 

int main() 
{ 
// char str[] = " 1  2 x   text 4 "; 
    char str[] = " 1\t 2 x\t\t text\t4 "; 
    char *s1; 
    char *s2; 
    s2=(void*)&str; //this is here to avoid warning of assignment from incompatible pointer type 
     do { 
      while(*s2 == ' ') s2++; 
      s1 = strsep(&s2, "\t"); 
      if(!*s1){ 
       printf("val: (empty)\n"); 
      } 
      else{ 
       int val; 
       char ch; 
       int ret = sscanf(s1, " %i %c", &val, &ch); 
       if(ret != 1){ 
        printf("val: (syntax error or string)=%s\n", s1); 
       } 
       else{ 
        printf("val: %i\n", val); 
       } 
      } 
     } while (s2!=0); 
     return 0; 
    } 

Y que la salida:

val: 1 
val: (syntax error or string)=2 x 
val: (empty) 
val: (syntax error or string)=text 
val: 4 
0

Hay algunos problemas con strtok() señaladas aquí: http://benpfaff.org/writings/clc/strtok.html

Por lo tanto, es mejor evitar strtok .

Ahora, consideremos una cadena que contiene un campo vacío de la siguiente manera:

char myCSVString[101] = "-1.4,2.6,,-0.24,1.26"; // specify input here 

Puede utilizar función simple para poder Cadena convertido en formato CSV para leerlos a una red de flotadores:

int strCSV2Float(float *strFloatArray , char *myCSVStringing); 

Por favor, encontrar el Uso a continuación:

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



int strCSV2Float(float *strFloatArray , char *myCSVStringing); 

    void main() 
{ 

    char myCSVString[101] = "-1.4,2.6,,-0.24,1.26"; // specify input here 
    float floatArr[10]; // specify size here 
    int totalValues = 0; 

    printf("myCSVString == %s \n",&myCSVString[0]); 

    totalValues = strCSV2Float(&floatArr[0] , &myCSVString[0]); // call the function here 

    int floatValueCount = 0; 

    for (floatValueCount = 0 ; floatValueCount < totalValues ; floatValueCount++) 
    { 

     printf("floatArr[%d] = %f\n",floatValueCount , floatArr[floatValueCount]); 

    } 

} 




int strCSV2Float(float *strFloatArray , char *myCSVStringing) 
{ 

int strLen = 0; 
int commaCount =0; // count the number of commas 
int commaCountOld =0; // count the number of commas 
int wordEndChar = 0; 
int wordStartChar = -1; 
int wordLength =0; 


    for(strLen=0; myCSVStringing[strLen] != '\0'; strLen++) // first get the string length 
    { 

     if ((myCSVStringing[strLen] == ',') || (myCSVStringing[strLen+1] == '\0')) 
     { 
      commaCount++; 
      wordEndChar = strLen; 
     } 
     if ((commaCount - commaCountOld) > 0) 
     { 
      int aIter =0; 
      wordLength = (wordEndChar - wordStartChar); 
      char word[55] = ""; 
      for (aIter = 0; aIter < wordLength; aIter++) 
      { 
      word[aIter] = myCSVStringing[strLen-wordLength+aIter+1]; 
      } 

      if (word[aIter-1] == ',') 
      word[aIter-1] = '\0'; 

      // printf("\n"); 
      word[wordLength] = '\0'; 
      strFloatArray[commaCount-1] = atof(&word[0]); 

      wordLength = 0; 
      wordStartChar = wordEndChar; 
      commaCountOld = commaCount; 

     } 
    } 

    return commaCount; 

} 

salida es el siguiente:

myCSVString == -1.4,2.6,,-0.24,1.26 
floatArr[0] = -1.400000 
floatArr[1] = 2.600000 
floatArr[2] = 0.000000 
floatArr[3] = -0.240000 
floatArr[4] = 1.260000 
Cuestiones relacionadas