【问题标题】:How to read a .xyz file into an array of doubles?如何将 .xyz 文件读入双精度数组?
【发布时间】:2019-11-08 17:00:47
【问题描述】:

我是 C 的新手,来自 Python。我想将 .xyz 文件读入一个动态大小的数组,以便稍后在程序中用于各种计算。文件格式如下:

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

前两行不是必需的,应该跳过。文件的“符号”部分是化学符号——例如H、Au、C、Mn——.xyz 文件格式用于存储原子的 3D 坐标。它们也需要被忽略。我对空格分隔的十进制数字感兴趣。因此,我想:

  • 跳过前两行,或以某种方式忽略它们。
  • 跳过每行的第一部分直到第一个空格。
  • 将三列数字(坐标)存储在一个数组中。

到目前为止,我已经能够打开一个文件进行读取,然后我尝试检查文件的长度,以便根据需要存储多少坐标集来改变数组的大小.

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

【问题讨论】:

  • 使用fgets逐行读取
  • 带有fseekftell 的那个东西会给你字节 的文件大小,它几乎不告诉你有多少行,因此有多少坐标是你的三倍'将不得不阅读。 (您可能可以将它用作将有多少行的估计,方法是除以您设法确定的一些平均行长度,但由于您将不得不无论如何都要掌握动态分配和增长的东西,我建议现在跳过文件大小的确定。)
  • fscanf(fp, "%*[^\n]") 是一种从文件中读取行的不必要的麻烦、困难且容易出错的方法。只需使用fgets
  • 如果要分配正确的内存量(不使用realloc),则需要计算文件中的行数(并减去2)。你可以使用fgets 来做到这一点。然后分配内存,rewind文件,用fgets读取每一行。用sscanf从每一行提取xyz坐标。
  • 一旦您的初步版本工作正常,您应该返回并重写盲目跳过前两行的代码。您应该让它检查前两行,如果它们不符合预期,请抱怨。迟早,有人会在错误类型的文件上运行您的程序,如果它盲目地阅读(没有注意到格式错误),这可能会导致神秘的失败。

标签: c arrays file


【解决方案1】:

这是 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-strings 的指针数组和 c-strings 本身分配内存:

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

注意:这是开始排除行或从行中删除各种子字符串的合适点,因为您可以使用其他 cstring 方法使 strcpy 以缓冲区的内容为条件。我自己对此还很陌生(学习编写用于 Python 程序的 C 函数),但这似乎是正确的方法。

也可以直接进入一个动态分配的浮点数组来存储你的数字数据,而不用打扰 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;

【讨论】:

  • char c 应该是 EOF 的 int
  • 谢谢,作为编辑(int)(c) != EOF,类型转换是否适用于检查? [cut] 必须是char(c) 才能找到终点线'\n'
  • 没有。使它成为一个int
  • @neutrino_logic getc() 返回一个 int,并且在 C 字符常量(如 '\n')中具有类型 int,但即使不是这种情况(如在 C++ 中),表达式c == '\n' 将隐式提升右手操作数。这是一个安全的比较。
【解决方案2】:

给定,例如:

#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 个原子 - 无限期。如果文件通常具有较少的原子以提高内存效率,则可以使用较小的值。这种方法使您不必先计算文件中三元组的数量。

【讨论】:

  • 请查一下为什么feof在这个网站上一段时间条件不好
  • @EdHeal:TL;博士。在任何情况下都是不必要的;重构它。
  • @EdHeal 谢谢;如果您选择为我修正错字,我不会生气。它是键入的代码,未编译或测试 - 在 Chromebook 上不是那么简单!
  • @EdHeal :通过onlinegdb.com 编译(未运行)片段,因此它现在至少在语法上有效。
  • @EdHeal:我们最终会到达那里!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-19
  • 1970-01-01
  • 1970-01-01
  • 2019-12-05
  • 2011-07-07
相关资源
最近更新 更多