Как обрабатывать поля в разделительной запятой, используя sscanf, поддерживая пустые поля?

130
19

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


1,2,,4

Используя базовый


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

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


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


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

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


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

спросил(а) 2009-10-02T13:20:00+04:00 11 лет, 5 месяцев назад
1
Решение
127

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

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

ответил(а) 2009-10-02T13:24:00+04:00 11 лет, 5 месяцев назад
105

man sscanf:


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


(выделено курсивом).

ответил(а) 2009-10-02T15:43:00+04:00 11 лет, 5 месяцев назад
44

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


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


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


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

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


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

ответил(а) 2016-09-23T17:30:00+03:00 4 года, 5 месяцев назад
44

Я сделал модификацию 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

ответил(а) 2014-04-30T00:01:00+04:00 6 лет, 10 месяцев назад
43

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


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

ответил(а) 2014-04-29T23:44:00+04:00 6 лет, 10 месяцев назад
44

Я приехал сюда искать ответы на тот же вопрос. Я тоже не хотел оставлять функцию scanf.
В конце концов, я сам создаю zsscanf, где я разбирал формат, sscanf'ed каждый из данных один за другим и проверял возврат 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 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, %...[...] и требует, чтобы разделители были %...[...]; в противном случае я действительно должен был заботиться о строка формата, таким образом, я забочусь только о %).

ответил(а) 2014-01-29T15:11:00+04:00 7 лет, 1 месяц назад
43

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

ответил(а) 2012-10-30T08:41:00+04:00 8 лет, 4 месяца назад
43

Вот моя версия для сканирования значений 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

ответил(а) 2009-10-02T17:10:00+04:00 11 лет, 5 месяцев назад
43

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().

ответил(а) 2009-10-02T16:46:00+04:00 11 лет, 5 месяцев назад
43

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


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

ответил(а) 2009-10-02T16:26:00+04:00 11 лет, 5 месяцев назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема