xmitz xmitz - 1 month ago 20
C Question

Read random line from .txt file

I'm trying to upgrade my Hangman game by reading random words from a .txt file. Thing is, I can't figure out how to read a random line from the .txt file. There are single words on every new line of the .txt file.

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

Answer

If, for some reason, you can't just load the whole set of lines into memory (too big or whatever), there is a way to select a random entry from a streaming set of entries. It won't scale indefinitely, and it will exhibit small biases, but this is a game, not cryptography, so that shouldn't be a dealbreaker.

The logic is:

  1. Declare a buffer to hold the word
  2. Open the file
  3. For each line:
    • Increment a counter indicating which line you're on
    • Generate a random double (e.g. with drand48 or whatever PRNG facilities are available to you)
    • If 1.0 / lineno > randval, replace the currently stored word with the word from the current line (so the first line is auto stored, the second line is 50% likely to replace it, the third is 33% likely to do so, etc.)
  4. When you run out of lines, whatever is stored in word is your selection

Assuming the number of lines is small enough (and the range of doubles produced by your PRNG is fine-grained enough), this gives as close as possible to an equal likelihood of any given line being selected; for two lines, each has a 50/50 shot, for three, 33.33...%, etc.

I lack a C compiler right now, but the basic code would look like:

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