From ba1e3c0874e5c78f5ceb4e71827695d8be0a1410 Mon Sep 17 00:00:00 2001 From: Krzysztof Rudnicki Date: Mon, 29 Dec 2025 16:10:43 +0100 Subject: [PATCH] feat: added inverse mode for anki --- C/vocabulary_curve/main.c | 113 +++++++++++++++++++++++++++- C/vocabulary_curve/vocabulary_curve | Bin 16768 -> 20936 bytes 2 files changed, 109 insertions(+), 4 deletions(-) diff --git a/C/vocabulary_curve/main.c b/C/vocabulary_curve/main.c index f28c2c5..769b0cf 100644 --- a/C/vocabulary_curve/main.c +++ b/C/vocabulary_curve/main.c @@ -328,11 +328,90 @@ static void dump_vocabulary(int max_rank) { printf("VOCAB_DUMP_END\n"); } +/* Find longest excerpt using only top N words (inverse mode) */ +static void find_longest_excerpt(int max_vocab) { + /* Sliding window: find longest contiguous sequence where all words have rank <= max_vocab */ + int best_start = 0; + int best_length = 0; + + int left = 0; + for (int right = 0; right < num_words; right++) { + /* If current word is outside our vocabulary, move left past it */ + if (word_sequence[right]->rank > max_vocab) { + left = right + 1; + } else { + /* Current window [left, right] is valid */ + int length = right - left + 1; + if (length > best_length) { + best_length = length; + best_start = left; + } + } + } + + /* Print results */ + printf("======================================================================\n"); + printf("INVERSE MODE: LONGEST EXCERPT WITH TOP %d WORDS\n", max_vocab); + printf("======================================================================\n"); + printf("\n"); + printf("Total words in text: %d\n", num_words); + printf("Unique words: %d\n", num_unique_words); + printf("Vocabulary limit: top %d words\n", max_vocab); + printf("\n"); + printf("----------------------------------------------------------------------\n"); + printf("\n"); + + if (best_length == 0) { + printf("No valid excerpt found with top %d words.\n", max_vocab); + printf("The text may require rarer words from the very beginning.\n"); + } else { + printf("LONGEST EXCERPT: %d words\n", best_length); + printf("Position: words %d to %d\n", best_start + 1, best_start + best_length); + printf("\n"); + printf("Excerpt:\n \""); + print_excerpt(best_start, best_length); + printf("\"\n"); + printf("\n"); + + /* Find the rarest word in the excerpt */ + int max_rank_used = 0; + const char *rarest_word = NULL; + for (int i = best_start; i < best_start + best_length; i++) { + if (word_sequence[i]->rank > max_rank_used) { + max_rank_used = word_sequence[i]->rank; + rarest_word = word_sequence[i]->word; + } + } + printf("Rarest word used: %s (#%d)\n", rarest_word, max_rank_used); + + /* Count unique words in excerpt */ + static bool seen_rank[MAX_UNIQUE_WORDS + 1]; + memset(seen_rank, 0, (num_unique_words + 1) * sizeof(bool)); + int unique_count = 0; + for (int i = best_start; i < best_start + best_length; i++) { + if (!seen_rank[word_sequence[i]->rank]) { + seen_rank[word_sequence[i]->rank] = true; + unique_count++; + } + } + printf("Unique words in excerpt: %d\n", unique_count); + } + + printf("\n----------------------------------------------------------------------\n"); +} + int main(int argc, char *argv[]) { if (argc < 2) { - fprintf(stderr, "Usage: %s [max_length] [--dump-vocab [max_rank]]\n", argv[0]); - fprintf(stderr, " max_length: maximum excerpt length to analyze (default: 30)\n"); - fprintf(stderr, " --dump-vocab: output all words with ranks up to max_rank\n"); + fprintf(stderr, "Usage: %s [options]\n", argv[0]); + fprintf(stderr, "\nModes:\n"); + fprintf(stderr, " (default) Find minimum vocab needed for each excerpt length\n"); + fprintf(stderr, " --max-vocab N INVERSE: Find longest excerpt using only top N words\n"); + fprintf(stderr, "\nOptions:\n"); + fprintf(stderr, " max_length Maximum excerpt length to analyze (default: 30)\n"); + fprintf(stderr, " --dump-vocab [N] Output all words with ranks up to N\n"); + fprintf(stderr, "\nExamples:\n"); + fprintf(stderr, " %s book.txt 50 # Analyze excerpts up to 50 words\n", argv[0]); + fprintf(stderr, " %s book.txt --max-vocab 500 # Find longest excerpt with top 500 words\n", argv[0]); return 1; } @@ -340,6 +419,7 @@ int main(int argc, char *argv[]) { int max_length = 30; bool dump_vocab = false; int dump_max_rank = 0; + int max_vocab_mode = 0; /* 0 = normal mode, >0 = inverse mode with this vocab limit */ /* Parse arguments */ for (int i = 2; i < argc; i++) { @@ -348,6 +428,17 @@ int main(int argc, char *argv[]) { if (i + 1 < argc && argv[i + 1][0] != '-') { dump_max_rank = atoi(argv[++i]); } + } else if (strcmp(argv[i], "--max-vocab") == 0) { + if (i + 1 < argc) { + max_vocab_mode = atoi(argv[++i]); + if (max_vocab_mode < 1) { + fprintf(stderr, "Error: --max-vocab requires a positive number\n"); + return 1; + } + } else { + fprintf(stderr, "Error: --max-vocab requires a number\n"); + return 1; + } } else if (argv[i][0] != '-') { max_length = atoi(argv[i]); if (max_length < 1) max_length = 1; @@ -371,7 +462,21 @@ int main(int argc, char *argv[]) { /* Assign ranks by frequency */ assign_ranks(); - /* Find optimal excerpts */ + /* Inverse mode: find longest excerpt with limited vocabulary */ + if (max_vocab_mode > 0) { + find_longest_excerpt(max_vocab_mode); + + /* Dump vocabulary if requested */ + if (dump_vocab) { + if (dump_max_rank == 0) dump_max_rank = max_vocab_mode; + dump_vocabulary(dump_max_rank); + } + + cleanup(); + return 0; + } + + /* Normal mode: find optimal excerpts */ ExcerptResult *results = malloc(max_length * sizeof(ExcerptResult)); if (!results) { fprintf(stderr, "Memory allocation failed\n"); diff --git a/C/vocabulary_curve/vocabulary_curve b/C/vocabulary_curve/vocabulary_curve index 873f3472a0ddea7f9d70bc65bb186b8820c759fd..360863e17a3c039fb64b9219a0128890b6205093 100755 GIT binary patch literal 20936 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