Как прочитать файл .xyz в массив двойников? - PullRequest
0 голосов
/ 08 ноября 2019

Я новичок в C, пришедший из Python. Я хочу прочитать файл .xyz в динамически изменяемый массив, чтобы использовать его для различных вычислений позже в программе. Файл отформатирован следующим образом:

Title  
Comment    
Symbol 0.000 0.000 0.000  
Symbol 0.000 0.000 0.000  
....

Две первые строки не нужны и должны быть пропущены. «Символ» - это химические символы, например, H, Au, C, Mn, поскольку формат файла .xyz используется для хранения трехмерных координат атомов. Их тоже нужно игнорировать. Мне интересны разделенные пробелами десятичные числа. Поэтому я хочу:

  • Пропустить первые две строки или просто как-то их игнорировать.
  • Пропустить первую часть каждой строки до первого пробела.
  • Сохранение трех столбцов чисел (координат) в массиве.

До сих пор я был в состоянии открыть файл для чтения, а затем я попытался проверить, как долго файл, чтобы размер массива изменялся в зависимости от того, сколько наборов координат необходимо сохранить.

// Variable declaration
FILE *fp;
long file_size;

// Open file and error checking
fp = fopen ("file_name" , "r");
if(!fp) perror("file_name"), exit(1);

// Check file size
fseek(fp, 0, SEEK_END);
file_size = ftell(fp);
rewind(fp);

// Close file
fclose(fp);

Мне удалось пропустить первые две строки, используя fscanf(fp, "%*[^\n]"), чтобы перейти кконец строки. Но я не смог выяснить, как перебрать оставшуюся часть файла, сохраняя в массиве только десятичные числа.

Если я правильно понимаю, мне нужно выделить память для массива, используя что-то вроде malloc() в сочетании с моим file_size, а затем скопировать данные в массив с помощью fread().

Вот пример содержимого реального файла .xyz:

10 atom system
Energy: -914941.6614699
Ag 0.96834 1.51757 0.02281
Ag 0.96758 -1.51824 -0.02206
Ag -1.80329 2.27401 0.03179
Ag -3.58033 0.00046 0.00126
Ag -1.80447 -2.27338 -0.03537
Ag -0.96581 0.02246 -1.51755
Ag -0.96929 -0.02231 1.51463
Ag 1.80613 0.03321 -2.27213
Ag 3.58027 0.00028 0.00206
Ag 1.80086 -0.03407 2.27455

Ответы [ 2 ]

0 голосов
/ 08 ноября 2019

Учитывая, например:

#define STORAGE_INCREMENT 128
typedef struct
{
    double x, y, z ;
} sXYZ ;

Тогда:

int atom_count = 0 ;
int atom_capacity = STORAGE_INCREMENT ;
sXYZ* atoms = malloc( atom_capacity * sizeof(*atoms) ) ;

// While valid triplet, discard symbol, get x,y,z
while( fscanf( fp, "%*s%lf%lf%lf", &atoms[atom_count].x, 
                                   &atoms[atom_count].y, 
                                   &atoms[atom_count].z ) == 3 )
{
    // Increment count
    atom_count++ ;

    // If capacity exhausted, expand allocation
    if( atom_count == atom_capacity )
    {
        atom_capacity += STORAGE_INCREMENT ;
        sXYZ* bigger = realloc( atoms, atom_capacity * sizeof(*atoms) ) ;
        if( bigger == NULL )
        {
            break ;
        }
        atoms = bigger ;
    }
}

Это первоначально выделяет достаточно места для 128 атомов, а если пространство исчерпано, оно расширяется еще на128 атомов - до бесконечности. Меньшее значение можно использовать, если в файлах обычно меньше атомов, чтобы повысить эффективность использования памяти. Такой подход избавляет вас от необходимости сначала подсчитывать количество триплетов в файле.

0 голосов
/ 08 ноября 2019

Вот общий подход в C для чтения файла в массив cstrings (указатели на cstrings, поэтому грубый эквивалент списка строк Python).

    int count = 0;                  // line counter;
    int char_count = 0;             // char counter;
    int max_len = 0;                // for storing the longest line length
    int c;                          // for measuring each line length 
    char **str_ptr_arr;             // array of pointers to c-string    

    //extract characters from the file, looking for endlines; note that 
    //the EOF check has to come AFTER the getc(fp) to work properly
    for (c = getc(fp); c != EOF; c = getc(fp)) {  //edit see comments
        char_count += 1;
        if (c == '\n') {                     //safe comparison see comments
            count += 1;
            if (max_len < char_count) {
                max_len = char_count;      //gets longest line
            }
            char_count = 0;
        }
    }
    //should probably do an feof check here
    rewind(fp);

Итак, теперь у вас естьколичество строк и длина самой длинной строки, (Вы можете попробовать использовать цикл выше, чтобы исключить строки, если хотите, но может быть проще прочитать все это в массив из c-строк, а затем обработать это в массивдвойников). Теперь выделите память для массива указателей на c-строки и для самих c-строк:

    //allocate enough memory to hold all the strings in the file, by first
    //allocating the arr of ptrs then a slot for each c-string pointed to:
    str_ptr_arr = malloc(count * sizeof(char*));               //size of pointer
    for (int i = 0; i < count; i++) {
        str_ptr_arr[i] = malloc ((max_len + 1) * sizeof(char)); // +1 for '\0' terminate
    }
    rewind(fp);    //rewind again;

Теперь у нас есть проблема, заключающаяся в том, как заполнить эти cstrings (Python намного проще!). Это работает, я не уверен, что это экспертный подход, но здесь мы читаем во временный буфер, а затем используем strcpy для перемещения содержимого буфера в наши выделенные слоты массива:

    for (int i = 0; i < count; i++) {
        char buff[max_len + 1]; //local temporary buffer that can store any line in file  
        fscanf(fp, "%s", buff);     //read the first string to buffer
        strcpy(str_ptr_arr[i], buff);
    }

Примечание: этоявляется достойной точкой, с которой нужно начинать исключать строки или удалять различные подстроки из строк, так как вы можете сделать strcpy условным для содержимого буфера, используя другие методы cstring. Я сам новичок в этом (учусь писать функции C для использования в программах Python), но, похоже, это правильный подход.

Возможно также, что можно напрямую перейти к динамически распределенному массивус плавающей запятой для хранения ваших числовых данных без использования массива cstring;это можно сделать в последнем цикле выше. Вы можете разбить строки на пробел, исключить алфавитные части и использовать функцию cstring atof для преобразования в тип данных с плавающей точкой.

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

   for(int i = 0; i < count; i++) {      // free each allocated cstring space
        free(str_ptr_arr[i]);
    }
    free(str_ptr_arr);                   // free the cstring pointer space
    str_ptr_arr = NULL;
...