我正试图通过从.txt文件中读取随机单词来升级我的Hangman游戏。事实是,我无法弄清楚如何从.txt文件中读取随机行。 .txt文件的每一行都有单个单词。
void ler_palavras()
{
FILE *words;
if ((words = fopen("words.txt", "r")) == NULL) {
printf("Error! opening file");
exit(1);
}
// reads text until newline
fscanf(words,"%[^\n]", word);
fclose(words);
}
答案 0 :(得分:2)
如果由于某种原因,你不能只将整组行加载到内存中(太大或其他),有一种方法可以从一组流条目中选择一个随机条目。它不会无限缩放,它会表现出很小的偏见,但这是一个游戏,而不是加密,所以这不应该是一个破坏者。
逻辑是:
double
(例如,使用drand48
或任何可用的PRNG设施)1.0 / lineno > randval
,请用当前行中的单词替换当前存储的单词(因此第一行是自动存储的,第二行是50%可能会替换它,第三行是33%可能会执行等等。)word
中存储的内容就是您的选择假设行数足够小(并且PRNG生成的double
s的范围足够精细),则尽可能接近选择任何给定行的相等可能性;对于两条线,每条线有50/50的射击,三个,33.33 ......%等等。
我现在缺少C编译器,但基本代码如下:
/* Returns a random line (w/o newline) from the file provided */
char* choose_random_word(const char *filename) {
FILE *f;
size_t lineno = 0;
size_t selectlen;
char selected[256]; /* Arbitrary, make it whatever size makes sense */
char current[256];
selected[0] = '\0'; /* Don't crash if file is empty */
f = fopen(filename, "r"); /* Add your own error checking */
while (fgets(current, sizeof(current), f)) {
if (drand48() < 1.0 / ++lineno) {
strcpy(selected, current);
}
}
fclose(f);
selectlen = strlen(selected);
if (selectlen > 0 && selected[selectlen-1] == '\n') {
selected[selectlen-1] = '\0';
}
return strdup(selected);
}
答案 1 :(得分:0)
rand()
有其局限性,包括仅生成0
到RAND_MAX
的值,而文件可以有多次RAND_MAX
行。假设行数大约为RAND_MAX/10
或更少,则以下内容将符合OP的目标。
执行一次传递来计算行数。 - &GT; lc
对于所需的每个随机行,重新读取文件的行,从之前的行索引开始 [0... lc-1]
范围内的一些随机数。
然后只需阅读并打印该行。不需要行缓冲区。该文件是行缓冲区。代码重新使用Line_Count()
进行总行计数和读取直到第n行。
#include <assert.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
// Return line count, but stop once the count exceeds a maximum
int Line_Count(FILE *istream, int line_index) {
int lc = 0;
int previous = '\n';
int ch;
rewind(istream);
while (line_index > 0 && (ch = fgetc(istream)) != EOF) {
if (ch == '\n') {
line_index--;
}
if (previous == '\n') {
lc++;
}
previous = ch;
}
return lc;
}
void print_random_line(FILE *istream, int line_index) {
printf("%8d: <", line_index + 1);
Line_Count(istream, line_index);
int ch;
while ((ch = fgetc(istream)) != EOF && ch != '\n') {
if (isprint(ch)) {
putchar(ch);
}
}
printf(">\n");
}
int main() {
srand((unsigned) time(NULL));
FILE *istream = fopen("test.txt", "r");
assert(istream);
int lc = Line_Count(istream, RAND_MAX);
assert(lc && lc < RAND_MAX);
for (int i = 0; i < 5; i++) {
print_random_line(istream, rand() % lc);
}
fclose(istream);
}
答案 2 :(得分:0)
这是另一种解决方案,仍然受RAND_MAX
的限制,不需要读取每条直到选定的行。我们的想法是使用一个二进制文件,以相同的字节数存储每个单词,因此可以使用fseek()
和fread()
访问任何单词。文件中的第一个条目是long
值,用于存储文件中的单词数。添加单词时,会更新此值。
这是一个实现,它查找名为wordlist.txt
的普通文本文件,每行有一个单词。如果找到,程序将更新(或创建,如有必要)名为wordlist.fmt
的文件。更新函数从文本文件中读取每个单词,跳过空白行,并以固定的字节数将其存储在二进制文件中。读完所有单词后,单词计数会更新。使用文本文件运行程序一次后,应删除文本文件,否则下一次运行将再次添加单词。 .fmt
文件应保留,如果要添加更多单词,只需将新文本文件放在包含可执行文件的目录中,然后再次运行。
打印五个随机单词的循环生成一个随机数,使用该数字移动到包含单词的文件位置,将该单词读入数组并打印出来。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define RAW_WORDS "wordlist.txt"
#define FMT_WORDS "wordlist.fmt"
#define OFFSET_SZ (sizeof(long))
#define MAXWORD 30
void update_words(FILE *fp_fmt, FILE *fp_raw);
void strip(char *str);
int main(void)
{
FILE *raw_words, *formatted_words;
char word[MAXWORD];
long wordcount;
int i;
int wpos;
raw_words = fopen(RAW_WORDS, "r");
/* Try to open existing file for update, otherwise open new file */
if ((formatted_words = fopen(FMT_WORDS, "r+b")) == NULL){
if ((formatted_words = fopen(FMT_WORDS, "w+b")) == NULL) {
fprintf(stderr, "Unable to open file %s\n", FMT_WORDS);
exit(EXIT_FAILURE);
} else { // initialize file wordcount
wordcount = 0L;
fwrite(&wordcount, OFFSET_SZ, 1, formatted_words);
fflush(formatted_words);
}
}
/* Update FMT_WORDS file if RAW_WORDS is present */
if (raw_words != NULL)
update_words(formatted_words, raw_words);
/* Get 5 random words and print them */
srand((unsigned)time(NULL));
rewind(formatted_words);
fread(&wordcount, OFFSET_SZ, 1, formatted_words);
printf("Five random words from %s:\n", FMT_WORDS);
for (i = 0; i < 5; i++) {
wpos = rand() % wordcount;
fseek(formatted_words, wpos * MAXWORD + OFFSET_SZ, SEEK_SET);
fread(word, MAXWORD, 1, formatted_words);
puts(word);
}
if (raw_words && (fclose(raw_words) != 0))
fprintf(stderr, "Unable to close file %s\n", RAW_WORDS);
if (fclose(formatted_words) != 0)
fprintf(stderr, "Unable to close file %s\n", FMT_WORDS);
return 0;
}
void update_words(FILE *fp_fmt, FILE *fp_raw)
{
char word[MAXWORD];
long wordcount;
/* Read in wordcount and move to end of file */
rewind(fp_fmt);
fread(&wordcount, OFFSET_SZ, 1, fp_fmt);
fseek(fp_fmt, wordcount * MAXWORD, SEEK_CUR);
/* Write formatted words, skipping blank lines */
while (fgets(word, MAXWORD, fp_raw) != NULL) {
if (word[0] != '\n') {
strip(word);
if (fwrite(word, MAXWORD, 1, fp_fmt) != 1) {
fprintf(stderr, "Error writing to %s\n", FMT_WORDS);
exit(EXIT_FAILURE);
}
++wordcount;
}
}
/* Update wordcount in file and flush output */
rewind(fp_fmt);
fwrite(&wordcount, OFFSET_SZ, 1, fp_fmt);
fflush(fp_fmt);
}
void strip(char *str)
{
while (*str != '\n' && *str != '\0')
str++;
*str = '\0';
}