Я пытаюсь проанализировать файл .csv, содержащий целые числа, в двумерный массив в C, но моя программа не работает, и я не знаю, почему.
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2
(Выше представлен файл .csv, который я пытаюсь проанализировать, это просто целые числа 1 и 2.)
Моя программа:
#include <stdio.h>
#define ROWS 12
#define COLUMNS 12
int main () {
int array [LEVEL_ROWS] [LEVEL_COLUMNS];
FILE *file;
file = fopen("data.csv","r");
if (file == NULL)
{
printf("error opening file");
return 1;
}
for (int i = 0; i < COLUMNS; i++)
{
for(int j = 0; j < ROWS; j++)
{
if (fscanf(file,"%d,",&array[i][j] )!=1)
{
fprintf(stderr,"error reading from file\n");
fclose(file);
return 1;
}
}
}
fclose(file);
for(int i = 0; i < COLUMNS; i++)
{
for (int j =0; j < ROWS; j++)
{
printf("%d ", array[i][j]);
}
printf("\n");
}
return 0;
}
Однако моя программа совершенно неправильно печатает содержимое файла .csv, и я не могу понять, почему.
распечатанный вывод (не совпадает с фактическим содержимым файла .csv):
2 2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2 1
1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 2 2 1 1 1
1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 2 2 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1
1 1 2 2 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1
2 2 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 2 2
Любая помощь и объяснение того, что не так? Спасибо!
🤔 А знаете ли вы, что...
C стандартизирован ISO и ANSI, что обеспечивает переносимость кода между различными компиляторами и платформами.
#define COLUMNS 12
не соответствует количеству столбцов в файле. В файле 20 столбцов.LEVEL_ROWS
и LEVEL_COLUMNS
вообще не определены.int array [LEVEL_ROWS][LEVEL_COLUMNS];
, но считываете значения столбцов в строки и наоборот.,
в конце последней строки сейчас не является проблемой, но выглядит незавершенным. Лучше быть последовательным. Я бы удалил последнюю запятую в каждой строке (или, возможно, добавил бы ее ко всем).Если вы затем определите COLUMNS
равным 20 и поменяете местами чтение строк и столбцов (и печать), все будет работать нормально. Пример:
#include <stdio.h>
#define ROWS (12)
#define COLUMNS (20)
int main(void) {
const char* filename = "data.csv";
FILE* file = fopen(filename, "r");
if (file == NULL) {
perror(filename);
return 1;
}
int array[ROWS][COLUMNS];
for(int row = 0; row < ROWS; row++) {
for(int col = 0; col < COLUMNS; col++) {
if (fscanf(file, "%d,", &array[row][col]) != 1) {
fprintf(stderr, "error reading from file\n");
fclose(file);
return 1;
}
}
}
fclose(file);
for(int row = 0; row < ROWS; row++) {
for(int col = 0; col < COLUMNS; col++) {
printf("%d ", array[row][col]);
}
putchar('\n');
}
}
Этот
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2
не является допустимым файлом CSV. CSV-файлы должны иметь одинаковое количество полей в каждой записи. Поскольку разделители определяют поля, каждая строка должна иметь одинаковое количество разделителей, даже если для некоторых полей нет данных. Поля являются необязательными, но их позиции — нет.
Это действительный файл CSV:
, , , ,
1,2,3,4,5
,6,7,8,
Это также справедливо
, , ,
, , ,
, , ,
field1,field2,field3,field4,quantity
,,,,
1,2,3,4,5
,6,7,8,
Посмотрите этот файл, импортированный в Таблицы из Google.
Обратите внимание, что отсутствующие поля сохраняются как пустые ячейки. Вот почему каждая запись (строка) должна иметь одинаковое количество разделителей.
field1,"Unit Price",field3,field4,quantity
,,,,
1,2,3,4,5
,6,7,8,
Посмотрите это, импортированное в Excel из Microsoft
Обратите внимание, что пробелы в B1
и B3
сохраняются благодаря кавычкам.
Этот формат существует примерно с 70-х годов и присутствует во многих областях. Поскольку это формат обмена, во многих случаях у вас есть только файл для чтения, в других случаях вам нужно написать файл, чтобы отправить его кому-то, и вы не можете контролировать ни одну сторону, ни другую.
csv-файлы импортируются в любую базу данных, в программы MS-Office, в продукты Google Cloud, программное обеспечение для печати этикеток из 90-х годов часто поставлялось с программой на 5,25-дюймовой дискете для создания этикеток на основе csv-файла.
А в последние годы CSV получил еще большую популярность благодаря ИИ, поскольку почти весь набор данных можно загрузить в виде файла CSV, а анализ CSV для подготовки данных перед обучением модели очень удобен в C
. В Python также есть модуль csv.
Вот пример импорта CSV-файла в базу данных MySQL из документации.
LOAD DATA INFILE 'file.csv'
INTO TABLE discounts
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS;
Последняя строка — это инструкция игнорировать строку заголовка. Это тот случай, когда имена столбцов в базе данных уже заданы. Остальное говорит само за себя.
Мы видим те же детали, о которых я писал ранее, о разделителе, признаке конца строки, строке заголовка и кавычках полей.
Формального определения формата csv не существует, поскольку на момент его создания не существовало таких понятий, как IETF или W3C. IETF подготовил что-то вроде RFC, RFC4180, который пытается дать некоторые определения, но уже слишком поздно, поскольку эти файлы повсюду с 70-х годов, и с тех пор программы импортируют эти файлы.
И с тех пор файлы такие, как я написал выше: возможно, гигантская таблица MxN. M записей, в которых ровно N полей отмечены N-1 одинаковыми разделителями, по одному в строке.
Я покажу пример импорта такой вещи. Я не буду реализовывать синтаксический анализ и цитирование строки заголовка, поскольку это не имеет значения, и это уже длинный и, возможно, скучный пост.
В этом примере читается файл csv с любым количеством записей и любым количеством полей. Можно легко изменить. Память выделяется блоками настолько большого размера, насколько хочет пользователь (и позволяет память машины). Размер памяти может увеличиваться по требованию и в данном примере происходит автоматически. Полный код и вывод приведены в конце.
Обычный подход, используемый здесь, заключается в сканировании первой строки файла и просто подсчете количества полей.
Эта функция делает именно это:
size_t csv_get_n_fields(const char* file, const char delim)
{
FILE* fl = fopen(file, "r");
if (fl == NULL) return 0;
char line[250];
size_t n_fields = 0;
char* p = fgets(line, sizeof(line), fl);
if (p == NULL)
{
fclose(fl);
return 0;
}
while (*p != 0)
if (delim == *p++) ++n_fields;
fclose(fl);
return 1 + n_fields;
}
Зная количество полей, мы можем приступить к чтению всех записей. Но нам нужна абстракция, модель.
int
typedef struct
{
size_t n_fields; // columns
size_t n_records; // rows
size_t max_records; // limit
int** array;
} CSV_i;
Каждая строка массива содержит действительную запись файла csv. Строка 0 файла — это array[0] и представляет собой массив int
с полями, что неудивительно. Количество полей было определено функцией выше, поэтому мы можем просто отсканировать файл и загрузить данные.
CSV_i
CSV_i* csv_create(
const char* file, size_t block, const char delim);
CSV_i* csv_destroy(CSV_i* del);
CSV_i* csv_read(
const char* file, size_t block, const char delim);
int csv_show(CSV_i* csv, const char* msg);
Здесь нет ничего удивительного: только самый минимум. In csv_create()
block
— единица распределения в байтах. В примере используется 64 КБ. Конечно, может быть в командной строке.
main
в примере#define BLK_SIZE (size_t)(64 * 1024)
int main(int argc,char** argv)
{
const char* def_file = "input5.csv";
char f_name[200] = {0};
if (argc < 2)
strcpy(f_name, def_file);
else
strcpy(f_name, argv[1]);
fprintf(stderr, "File is '%s'\n", f_name);
CSV_i* my_csv =
csv_read(f_name, BLK_SIZE, DELIM_COMMA);
char msg[80] = {0};
sprintf(msg, "test_file for %zu bytes: ", BLK_SIZE);
csv_show(my_csv, msg);
csv_destroy(my_csv);
return 0;
}
Таким образом можно прочитать любой файл csv. Пользователь может ввести имя файла в командной строке. Структура выделяется и расширяется по мере необходимости.
Stack Overflow > type input5.csv
, , , ,
1,2,3,4,5
,6,7,8,
Stack Overflow > p input5.csv
File is 'input5.csv'
csv has 5 fields
test_file for 65536 bytes: array [3276,5] (in use: 3/3276 records)
0 0 0 0 0
1 2 3 4 5
0 6 7 8 0
Stack Overflow > type input4.csv
,999,,
15,16,17,18
1,2,3,4
10,,,11
Stack Overflow > p input4.csv
File is 'input4.csv'
csv has 4 fields
test_file for 65536 bytes: array [4096,4] (in use: 4/4096 records)
0 999 0 0
15 16 17 18
1 2 3 4
10 0 0 11
[end]
Stack Overflow >
Stack Overflow > type input.csv
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2
Stack Overflow > p input.csv
File is 'input.csv'
csv has 20 fields
test_file for 65536 bytes: array [819,20] (in use: 13/819 records)
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2
2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2
2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2
2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2
2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2
2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2
2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2
2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
[end]
Stack Overflow >
Строки ввода, в которых нет необходимого количества полей, просто игнорируются.
Основная логика находится в csv_read
, как показано ниже.
CSV_i* csv_read(
const char* file, size_t block, const char delim)
{
CSV_i* one = csv_create(file, block, delim);
if (one == NULL) return NULL;
FILE* fl = fopen(file, "r");
if (fl == NULL)
{
csv_destroy(one);
return NULL;
}
// read all records
do {
// read all fields
char line[MAX_LINE_SIZE] = {0};
char* p_in = fgets(line, (int)sizeof(line), fl);
if (p_in == NULL) break; // read line by line
size_t res = csv_get_line(line, one, delim);
if (res == 0) ++one->n_records;
if (one->n_records == one->max_records)
{
if (csv_extend(one) != 0)
{
fprintf(stderr, "no more space on array\n");
fclose(fl);
break;
}
}
} while (1);
fclose(fl);
++one->n_records;
return one;
}
fgets
stderr
в этих точках расширения.size_t csv_get_line(
const char* line, CSV_i* one, const char delim)
эта функция получает строку и анализирует ее в CSV. Можно было бы использовать sscanf
, но вместо этого используется очень простой двухсостоятельный автомат FSM. Легче тестировать и изменять, например. если мы решим добавить кавычки для полей.
Поскольку это отдельная функция, ее легче тестировать с помощью специальных строк ввода, помимо CSV.
C
код для примера// 64K buffer at start
#define BLK_SIZE (size_t)(64 * 1024)
// standard delimiter
#define DELIM_COMMA ','
// max 4-digits fields, for testing
#define MAX_FIELD_SIZE 4
#define MAX_LINE_SIZE 250
#include <limits.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct
{
size_t n_fields; // columns
size_t n_records; // rows
size_t max_records; // limit
int** array;
} CSV_i;
CSV_i* csv_create(
const char* file, size_t block, const char delim);
CSV_i* csv_destroy(CSV_i* del);
CSV_i* csv_read(
const char* file, size_t block, const char delim);
int csv_show(CSV_i* csv, const char* msg);
// helpers
int csv_extend(CSV_i* csv);
size_t csv_get_n_fields(const char* file, const char delim);
size_t csv_get_line(
const char* line, CSV_i* csv, const char delim);
int main(int argc,char** argv)
{
const char* def_file = "input5.csv";
char f_name[200] = {0};
if (argc < 2)
strcpy(f_name, def_file);
else
strcpy(f_name, argv[1]);
fprintf(stderr, "File is '%s'\n", f_name);
CSV_i* my_csv =
csv_read(f_name, BLK_SIZE, DELIM_COMMA);
char msg[80] = {0};
sprintf(msg, "test_file for %zu bytes: ", BLK_SIZE);
csv_show(my_csv, msg);
csv_destroy(my_csv);
return 0;
}
CSV_i* csv_create(
const char* file, size_t block, char delim)
{
if (file == NULL) return NULL;
size_t n_fields = csv_get_n_fields(file, delim);
if (n_fields == 0) return NULL;
printf("csv has %zu fields\n", n_fields);
CSV_i* one = (CSV_i*)malloc(sizeof(CSV_i));
if (one == NULL) return NULL;
one->n_fields = n_fields;
one->n_records = 0; // empty
one->max_records = block / n_fields / sizeof(int);
if (one->max_records < 1) one->max_records = 1;
// malloc the lines
one->array = malloc(sizeof(int*) * one->max_records);
if (one->array == NULL)
{
free(one);
return NULL;
}
// build the columns inside lines
size_t row = 0;
for (; row < one->max_records; row += 1)
{
one->array[row] = malloc(sizeof(int) * n_fields);
if (one->array[row] == NULL) break;
}
if (row == one->max_records) return one;
// error: undo everything
for (size_t i = 0; i < row; i += 1) free(one->array[i]);
free(one->array);
free(one);
return NULL;
}
CSV_i* csv_destroy(CSV_i* del)
{
if (del == NULL) return NULL;
for (size_t row = 0; row < del->max_records; row += 1)
free(del->array[row]);
free(del->array);
free(del);
return NULL;
}
int csv_extend(CSV_i* csv)
{
if (csv == NULL) return -1;
size_t new_size = csv->max_records + csv->max_records;
int** new_block =
realloc(csv->array, sizeof(int*) * new_size);
if (new_block == NULL)
{
fprintf(
stderr,
"ERROR: Could not extend size from %zu to "
"%zu\n",
csv->max_records, new_size);
return -2;
}
// build the columns inside lines
csv->array = new_block;
size_t row = csv->max_records;
for (; row < new_size; row += 1)
{
csv->array[row] =
malloc(sizeof(int) * csv->n_fields);
if (csv->array[row] == NULL) break;
}
if (row == new_size)
{
fprintf(
stderr,
"EXTEND: size went from %zu to "
"%zu\n",
csv->max_records, new_size);
csv->max_records = new_size;
csv->array = new_block;
return 0; // ok
}
// error: undo everything in reverse
for (size_t i = csv->max_records; i < row; i += 1)
free(csv->array[i]);
new_size = csv->max_records / 2;
new_block =
realloc(csv->array, sizeof(int*) * new_size);
if (new_block == NULL)
{ // this should never fail
fprintf(
stderr,
"ERROR: Could not trim size from %zu to "
"%zu\n",
new_size, csv->max_records);
return -3;
}
csv->array = new_block; // reset
fprintf(
stderr,
"EXTEND: failed: size reset from %zu to "
"%zu\n",
csv->max_records, new_size);
csv->max_records = new_size;
return 0;
}
CSV_i* csv_read(
const char* file, size_t block, const char delim)
{
CSV_i* one = csv_create(file, block, delim);
if (one == NULL) return NULL;
FILE* fl = fopen(file, "r");
if (fl == NULL)
{
csv_destroy(one);
return NULL;
}
// read all records
do {
// read all fields
char line[MAX_LINE_SIZE] = {0};
char* p_in = fgets(line, (int)sizeof(line), fl);
if (p_in == NULL) break; // read line by line
size_t res = csv_get_line(line, one, delim);
if (res == 0) ++one->n_records;
if (one->n_records == one->max_records)
{
if (csv_extend(one) != 0)
{
fprintf(stderr, "no more space on array\n");
fclose(fl);
break;
}
}
} while (1);
fclose(fl);
++one->n_records;
return one;
}
int csv_show(CSV_i* csv, const char* msg)
{
if (csv == NULL) return -1;
if (msg != NULL) printf("%s", msg);
printf(
"array [%zu,%zu] (in use: %zu/%zu records)\n\n",
csv->max_records, csv->n_fields, csv->n_records - 1,
csv->max_records);
for (size_t row = 0; row < csv->n_records - 1; row += 1)
{
for (size_t col = 0; col < csv->n_fields; col += 1)
printf("%d ", csv->array[row][col]);
printf("\n");
}
printf("\n[end]\n");
return 0;
}
size_t csv_get_n_fields(const char* file, const char delim)
{
FILE* fl = fopen(file, "r");
if (fl == NULL) return 0;
char line[250];
size_t n_fields = 0;
char* p = fgets(line, sizeof(line), fl);
if (p == NULL)
{
fclose(fl);
return 0;
}
while (*p != 0)
if (delim == *p++) ++n_fields;
fclose(fl);
return 1 + n_fields;
}
#define ST_INFIELD 0
#define ST_OVERFLOW 1
size_t csv_get_line(
const char* line, CSV_i* one, const char delim)
{
char field[MAX_LINE_SIZE] = {0};
const char* p_in = line;
char* p_out = field; // pointer to field
size_t col = 0;
for (; col < one->n_fields; col += 1)
{
char state = ST_INFIELD;
char read_one = 0;
while (!read_one)
{
switch (state)
{
case ST_INFIELD:
if (*p_in == 0)
{ // eol
*p_out = 0;
one->array[one->n_records][col] =
atoi(field);
read_one = 1;
break;
}
if (*p_in == delim)
{
*p_out = 0;
one->array[one->n_records][col] =
atoi(field);
p_out = field; // reset field ptr
++p_in;
read_one = 1;
break;
}
if (*p_in == '\n')
{ // must be the last field
*p_out = 0;
one->array[one->n_records][col] =
atoi(field);
p_out = field; // reset field ptr
++p_in;
read_one = 1;
break;
}
if ((p_out - field) == MAX_FIELD_SIZE)
{
state = ST_OVERFLOW;
++p_in;
break;
}
*p_out++ = *p_in++; // copy this one
break;
case ST_OVERFLOW:
if (*p_in == 0)
{ // eol
*p_out = 0;
one->array[one->n_records][col] =
atoi(field);
p_out = field; // reset field ptr
read_one = 1;
state = ST_INFIELD;
break;
}
if (*p_in == delim)
{
*p_out = 0;
one->array[one->n_records][col] =
atoi(field);
p_out = field; // reset field ptr
state = ST_INFIELD;
++p_in;
read_one = 1;
break;
}
if (*p_in == '\n')
{
*p_out = 0;
one->array[one->n_records][col] =
atoi(field);
p_out = field; // reset field ptr
state = ST_INFIELD;
read_one = 1;
break;
}
++p_in; // ignore
default:
break;
}
} // while()
if (*p_in == 0) break;
} // for
if (col == one->n_fields - 1) return 0;
return col;
}