在C语言中以均匀概率从文本文件中有效地选择随机行?
这本质上是一个更受约束的版本 假设我们有一个非常大的文本文件,包含大量的行 我们需要以统一的概率从文件中随机选择一行,但有一些限制:在C语言中以均匀概率从文本文件中有效地选择随机行?,c,random,random-access,C,Random,Random Access,这本质上是一个更受约束的版本 假设我们有一个非常大的文本文件,包含大量的行 我们需要以统一的概率从文件中随机选择一行,但有一些限制: 因为这是一个软实时应用程序,我们不能迭代整个文件。选择应该持续一定的时间 由于内存限制,无法缓存该文件 由于允许文件在运行时更改,因此不能假定文件的长度为常量 我的第一个想法是使用lstat()调用来获取以字节为单位的总文件大小fseek()直接访问随机字节偏移量,从而获得对文件随机部分的O(1)访问 问题是,我们不能像读到下一条新行并结束这一天那样做,因为这
- 因为这是一个软实时应用程序,我们不能迭代整个文件。选择应该持续一定的时间
- 由于内存限制,无法缓存该文件
- 由于允许文件在运行时更改,因此不能假定文件的长度为常量
lstat()
调用来获取以字节为单位的总文件大小<然后可以使用code>fseek()直接访问随机字节偏移量,从而获得对文件随机部分的O(1)访问
问题是,我们不能像读到下一条新行并结束这一天那样做,因为这样会产生一个偏向于长行的分布
我解决这个问题的第一个想法是一直读到第一个“n”换行符(如果需要,可以换回文件的开头),然后从这个较小的集合中选择一个概率相同的行。可以安全地假设文件的内容是随机排序的,因此该子样本的长度应该是一致的,并且由于其起点是从所有可能的点中统一选择的,因此它应该代表从整个文件中统一选择的。因此,在pseudo-C中,我们的算法类似于:
lstat(filepath, &filestat);
fseek(file, (int)(filestat.off_t*drand48()), SEEK_SET);
char sample[n][BUFSIZ];
for(int i=0;i<n;i++)
fgets(sample[i], BUFSIZ, file); //plus some stuff to deal with file wrap around...
return sample[(int)(n*drand48())];
lstat(文件路径和文件状态);
fseek(file,(int)(filestat.off\u t*drand48()),SEEK\u SET);
煤焦样品[n][BUFSIZ];
对于(int i=0;i如果文件只在末尾更改(添加更多行),则可以创建具有统一概率的算法:
准备:创建一个索引文件,其中包含第n行的偏移量。使用固定宽度格式,以便可以使用该位置确定您拥有的记录
打开索引文件并读取最后一条记录。使用ftell
确定记录编号
打开大文件并fseek
到步骤1中获得的偏移量
将大文件读到最后,计算换行的数量。现在,您已经获得了大文件中的总行数
生成一个不超过步骤3中获得的行数的随机数
fseek
查找并读取索引文件中的相应记录
fseek
到大文件中的适当偏移量。跳过其余的换行符
读台词
示例
假设我们选择了n=100,并且这个大文件包含367行
索引文件:
00000000,00004753,00009420,00016303
索引文件有4条记录,因此大文件至少包含300条记录(100*(4-1))。最后一个偏移量为16303
打开大文件并fseek
至16303
计算剩余的行数(67)
生成[0-366]范围内的随机数。假设我们得到112
112/100=1,余数为12。读取偏移量为1的索引文件记录。我们得到结果4753
fseek
到大文件中的4753,然后跳过11(12-1)行
读第12行
瞧
编辑:
我看到有关目标文件的注释发生了变化。如果目标文件很少发生变化,那么这可能仍然是一种可行的方法。在切换目标文件之前,您需要创建一个新的索引文件。当目标文件增长超过n
行时,您可能还需要更新索引文件。找到了解决方案,该解决方案在非常好。在这里为我自己和其他人记录
这个示例代码在实践中每秒绘制80000次,平均行长度与大多数运行中文件的行长度匹配到4个有效数字。相比之下,我使用来自的方法每秒绘制250次
从本质上讲,它所做的是在文件中随机抽取一个位置,然后丢弃它,并以与行长度成反比的概率再次绘制。这就消除了较长单词的偏差。平均而言,该方法在接受一个绘制之前,使绘制数等于文件中的平均行长度
一些明显的缺点:
- 线条长度较长的文件在每次绘制时会产生更多的拒绝,这会使绘制速度慢得多
- 具有较长线条长度的文件要求rdraw函数中的常量大于50,这意味着如果线条长度表现出较大的差异,则实际查找时间可能要长得多。例如,在我测试的一个文件上,将其设置为BUFSIZ,绘制速度降低到每秒10000次左右。仍然比在t中计算线条快得多不过他还是归档了
int rdraw(FILE* where, char *storage, size_t bytes){
int offset = (int)(bytes*drand48());
int initial_seek = offset>50?offset-50:0;
fseek(where, initial_seek, SEEK_SET);
int chars_read = 0;
while(chars_read + initial_seek < offset){
fgets(storage,50,where);
chars_read += strlen(storage);
}
return strlen(storage);
}
int main(){
srand48(time(NULL));
struct stat blah;
stat("/usr/share/dict/words", &blah);
FILE *where = fopen("/usr/share/dict/words", "r");
off_t bytes = blah.st_size;
char b[BUFSIZ+1];
int i;
for(i=0;i<1000000; i++){
while(drand48() > 1.0/(rdraw(where, b, bytes)));
}
}
int-rdraw(文件*其中,字符*存储,大小\u t字节){
int offset=(int)(字节*drand48());
int初始搜索=偏移>50?偏移-50:0;
fseek(其中,初始搜索,搜索集);
int chars_read=0;
while(字符读取+初始搜索<偏移){
fgets(储存,50,若有);
字符读取+=strlen(存储);
}
返回strlen(存储);
}
int main(){
srand48(时间(空));
结构统计等等;
stat(“/usr/share/dict/words”,等等);
文件*where=fopen(“/usr/share/dict/words”,“r”);
off_t bytes=blah.st_size;
字符b[BUFSIZ+1];
int i;
对于(i=0;i 1.0/(rdraw(其中,b,字节));
}
}
从文件中选择一个随机字符(如您所述,通过rand和seek)。现在,我将应用以下算法,而不是查找关联的换行符,因为这是有偏差的,如您所述:
Is the character a newline character?
yes - use the preceeding line
no - try again
我看不出这怎么能给出线的均匀分布。效率取决于线的平均长度。如果