Leer línea aleatoria de archivo .txt

Estoy intentando actualizar mi juego de Hangman leyendo palabras al azar de un archivo .txt. La cosa es que no puedo entender cómo leer una línea aleatoria del archivo .txt. Hay palabras sueltas en cada nueva línea del archivo .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); } 

Si, por alguna razón, no puede cargar todo el conjunto de líneas en la memoria (demasiado grande o lo que sea), hay una manera de seleccionar una entrada aleatoria de un conjunto de entradas de transmisión. No se escalará indefinidamente, y mostrará pequeños sesgos, pero esto es un juego, no criptografía, por lo que no debería ser un factor de ruptura.

La lógica es:

  1. Declara un búfer para mantener la palabra
  2. Abre el archivo
  3. Para cada línea:
    • Incrementa un contador que indica en qué línea estás
    • Genere un double aleatorio (por ejemplo, con drand48 o cualquier otra instalación de PRNG disponible para usted)
    • Si 1.0 / lineno > randval , reemplaza la palabra almacenada actualmente con la palabra de la línea actual (de modo que la primera línea se almacena automáticamente, la segunda línea es 50% probable que la reemplace, la tercera es 33% probable que lo haga, etc. .)
  4. Cuando te quedas sin líneas, lo que esté almacenado en word es tu selección

Suponiendo que el número de líneas es lo suficientemente pequeño (y el rango de double s producido por su PRNG es lo suficientemente detallado), esto da lo más cerca posible a la misma probabilidad de que se seleccione una línea determinada; para dos líneas, cada una tiene un disparo de 50/50, para tres, 33.33 …%, etc.

Me falta un comstackdor de C en este momento, pero el código básico se vería así:

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

rand() tiene sus limitaciones, que incluyen solo generar valores 0 a RAND_MAX y un archivo puede tener muchas veces líneas RAND_MAX . Suponiendo que el recuento de líneas es del orden de RAND_MAX/10 o menos, lo siguiente cumplirá con los objectives de OP.

Realice una pasada para contar el número de líneas. -> lc

Para cada línea aleatoria necesaria, vuelva a leer las líneas del archivo, desde el inicio hasta el índice de línea antes de algún número aleatorio en el rango [0... lc-1] .

Luego simplemente lee e imprime esa línea. No hay necesidad de un buffer de línea. El archivo es el búfer de línea. El código reutiliza Line_Count() tanto para el cálculo del recuento total de líneas como para leer hasta la línea nth.

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

Aquí hay otra solución, aún limitada por RAND_MAX , que no requiere leer cada línea hasta la línea elegida. La idea es usar un archivo binario que almacene cada palabra en el mismo número de bytes, de modo que se pueda acceder a cualquier palabra utilizando fseek() y fread() . La primera entrada en el archivo es un valor long que almacena el número de palabras en el archivo. Cuando se agregan palabras, este valor se actualiza.

Aquí hay una implementación que busca un archivo de texto normal llamado wordlist.txt , que tiene una palabra en cada línea. Si se encuentra, el progtwig actualiza (o crea, si es necesario) un archivo llamado wordlist.fmt . La función de actualización lee cada palabra del archivo de texto, omite las líneas en blanco y la almacena en un archivo binario en un número fijo de bytes. Después de leer todas las palabras, el recuento de palabras se actualiza. Después de ejecutar el progtwig una vez con un archivo de texto, debe eliminar el archivo de texto, o la próxima ejecución agregará las palabras nuevamente. El archivo .fmt debe permanecer, y si desea agregar más palabras, simplemente coloque un nuevo archivo de texto en el directorio con el ejecutable y vuelva a ejecutarlo.

El bucle que imprime cinco palabras aleatorias genera un número aleatorio, usa ese número para moverse a una posición de archivo que contiene una palabra, lee esa palabra en una matriz y la imprime.

 #include  #include  #include  #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'; }