马克的回答几乎是正确的,除了两个问题:
- 如果一行的长度超过
length - 1 个字符(包括换行符),那么while 循环将为同一行增加至少两次count:一次用于第一个length - 1 字符,另一次用于下一个length - 1 字符等
-
rand() * count 的计算会导致整数溢出。
要解决第一个问题,您可以将fgets 调用到垃圾缓冲区,直到它返回NULL(表示I/O 错误或没有读取数据的EOF)或垃圾缓冲区包含换行符:
count = 0;
while (fgets(line, length, stream) != NULL)
{
char *p = strchr(line, '\n');
if (p != NULL) {
assert(*p == '\n');
*p = '\0'; // trim the newline
}
else { // haven't reached EOL yet. Read & discard the rest of the line.
#define TRASH_LENGTH 1024
char trash[TRASH_LENGTH];
while((p = fgets(trash, TRASH_LENGTH, stream)) != NULL) {
if ((p = strchr(trash, '\n')) != NULL) // reached EOL
break;
}
}
assert(strchr(line, '\n') == NULL); // `line` does not contain a newline
count++;
// ...
如果浮点运算不可用,可以通过@tvanfosson 的建议解决第二个问题:
int one_chance_in(size_t n)
{
if (rand() % n == 0) // `rand` returns an integer in [0, `RAND_MAX`]
return 1;
else
return 0;
}
但请注意rand() % n 不是uniform, discrete random variable,即使rand() 被假定为1,因为rand() % n == 0 的概率可能比期望的概率1/@ 高1/RAND_MAX 987654336@。在我的机器上,RAND_MAX 是 2147483647,所以差是 4.66 × 10-10,但 C 标准只要求 RAND_MAX 至少为 32767(3.05 × 10-5 区别)。
另外,对于任何想知道为什么这个方案有效的人(就像我一样),如果有 m lines and generalize:在循环的第一次迭代中,第一行被复制到keptline的概率为1/1。在循环的第二次迭代中,第二行不覆盖第一行的概率为 1/2。在第三次迭代中,第三行不覆盖第一行的概率是 2/3。继续,最后一行不覆盖第一行的概率是 (m - 1)/m。因此,在遍历所有行之后,第一行保留在keptline 中的概率为:
1/1 × 1/2 × 2/3 × 3/4 × ... × (m - 2)/(m - 1) × ( m - 1)/m = 1/m
第二行留在keptline的概率是:
1/2 × 2/3 × 3/4 × ... × (m - 2)/(m - 1) × (m - 1)/m = 1/m
第三行留在keptline中的概率为:
1/3 × 3/4 × ... × (m - 2)/(m - 1) × (m - 1)/m = 1/m
等等。它们都是 1/m。