Как обрабатывать поля в разделительной запятой, используя sscanf, поддерживая пустые поля?
У меня есть строка, разделенная запятыми, которая может содержать пустые поля. Например:
1,2,,4
Используя базовый
sscanf(string,"%[^,],%[^,],%[^,],%[^,],%[^,]", &val1, &val2, &val3, &val4);
Я получаю все значения до пустого поля и неожиданные результаты из пустого поля.
Когда я удаляю выражение для пустого поля из sscanf(),
sscanf(string,"%[^,],%[^,],,%[^,],%[^,]", &val1, &val2, &val3, &val4);
все работает отлично.
Так как я не знаю, когда я получу пустое поле, есть ли способ переписать выражение, чтобы обрабатывать пустые поля элегантно?
Если вы используете strtok
с запятой в качестве символа разделителя, вы получите список строк, один или несколько из которых будут иметь нулевую/нулевую длину.
Посмотрите на мой ответ здесь для получения дополнительной информации.
[
Соответствует последовательности непустых символов из указанного набора принятые символы;
(выделено курсивом).
Есть несколько проблем с 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
Я сделал модификацию 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
Мне пришлось немного изменить этот код для правильной работы:
//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
Я приехал сюда искать ответы на тот же вопрос. Я тоже не хотел оставлять функцию 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
, %...[...]
и требует, чтобы разделители были %...[...]
; в противном случае я действительно должен был заботиться о строка формата, таким образом, я забочусь только о %
).
Поставьте "*" после "%", чтобы пропустить чтение. Кроме того, можно читать только 3 символа, например, "% 3s".
Вот моя версия для сканирования значений 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
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()
.
Похоже, вы в настоящее время имеете дело со значениями CSV. Если вам нужно расширить его для обработки цитируемых строк (чтобы поля могли содержать запятые, например), вы обнаружите, что scanf
-семейство не может справиться со всеми сложностями формата. Таким образом, вам нужно будет использовать код, специально предназначенный для обработки (вашего варианта) CSV-формата.
Вы найдете обсуждение реализаций библиотеки set CSV в Практика программирования - на C и С++. Несомненно, есть много других.