Как мне разобрать поля в строке через запятую, используя sscanf, поддерживая пустые поля? - PullRequest
11 голосов
/ 02 октября 2009

У меня есть разделенная запятыми строка, которая может содержать пустые поля. Например:

1,2,,4

Использование базового

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

Я получаю все значения до пустого поля и неожиданные результаты из пустого поля и далее.

Когда я удаляю выражение для пустого поля из sscanf (),

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

все отлично работает.

Поскольку я не знаю, когда собираюсь получить пустое поле, есть ли способ переписать выражение для элегантной обработки пустых полей?

Ответы [ 10 ]

9 голосов
/ 02 октября 2009

Если вы используете strtok с запятой в качестве символа-разделителя, вы получите список строк, одна или несколько из которых будут иметь нулевую / нулевую длину.

Посмотрите мой ответ здесь для получения дополнительной информации.

5 голосов
/ 02 октября 2009

man sscanf :

[ Соответствует непустому последовательности символов из указанного набора принятые персонажи;

(выделение добавлено).

0 голосов
/ 23 сентября 2016

Есть некоторые проблемы со strtok () , перечисленные здесь: http://benpfaff.org/writings/clc/strtok.html

Следовательно, лучше избегать строк .

Теперь рассмотрим строку, содержащую пустое поле, следующим образом:

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

Вы можете использовать простую функцию, чтобы иметь возможность конвертировать String в формат CSV для чтения их в массив с плавающей точкой :

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

Пожалуйста, найдите Использование ниже:

#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;

}

Вывод выглядит следующим образом:

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
0 голосов
/ 30 апреля 2014

Я сделал модификацию для файлов TSV с разделителями табуляции, надеюсь, это может помочь:

//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;
    }

И выход:

val: 1
val: (syntax error or string)=2 x
val: (empty)
val: (syntax error or string)=text
val: 4
0 голосов
/ 30 апреля 2014

Мне пришлось немного изменить этот код для правильной работы:

//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;
    }

и вывод:

val: 1
val: (syntax error)
val: (empty)
val: 4
0 голосов
/ 29 января 2014

Я прибыл сюда в поисках ответов на тот же вопрос. Я не хотел оставлять позади функцию scanf. В конце концов, я сам создаю zsscanf, где я анализировал формат, sscanf просматривал все данные один за другим и проверял возвращение sscanf, чтобы увидеть, получил ли я пустое чтение в любом. Это был мой частный случай: я хотел только некоторые из полей, некоторые из которых могли бы быть пустыми, и не мог использовать разделитель.

#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;
}

Выход:

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

Имейте в виду, что он не полностью протестирован и имеет серьезные ограничения (наиболее очевидные из которых: принимает только %...s, %...c, %...[...] и требует разделителей как %...[...]; в противном случае мне действительно пришлось бы заботиться о форматная строка, таким образом, я забочусь только о %).

0 голосов
/ 30 октября 2012

Поставьте «*» после «%», чтобы пропустить чтение. Кроме того, можно прочитать только 3 символа, например,% 3s.

0 голосов
/ 02 октября 2009

Вот моя версия для сканирования значений int, разделенных запятыми. Код обнаруживает пустые и нецелые поля.

#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;
}

Результат:

str: ' 1 , 2 x, , 4 '
val: 1
val: (syntax error)
val: (empty)
val: 4
0 голосов
/ 02 октября 2009

scanf() возвращает количество назначенных элементов. Может быть, вы можете использовать эту информацию ...

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));
}

Тьфу! И это только для 1 пропущенного значения
Как указывалось в других ответах, вам намного лучше разбирать строку «обычным» способом: fgets() и strtok().

0 голосов
/ 02 октября 2009

Похоже, что вы сейчас работаете со значениями CSV. Если вам нужно расширить его для обработки строк в кавычках (например, чтобы поля могли содержать запятые), вы обнаружите, что семейство scanf не может обрабатывать все сложности формата. Таким образом, вам нужно будет использовать код, специально разработанный для обработки (вашего варианта) CSV-формата.

Вы найдете обсуждение набора реализаций библиотеки CSV в ' Практика программирования ' - на C и C ++. Без сомнения, есть много других доступных.

...