| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576 |
- #!/usr/bin/env python3
- """Generate word-bank.json and word-bank-ru.json with Gaussian-distributed targets.
- Reads frequency-sorted word lists (most common first) from raw/ and produces
- word banks with 100 Gaussian-picked targets per language.
- Gaussian parameters:
- mean = 0 (index of the most frequent word)
- sigma = 100 (index 100 is at 1σ → ~68% of picks land in the first 100 words)
- Target IDs are assigned in ascending index order, so ID 1 is the most common
- selected word and ID 100 is the least common.
- """
- import json
- from pathlib import Path
- import numpy as np
- RAW_DIR = Path(__file__).parent
- DATA_DIR = RAW_DIR.parent / "data"
- SEED = 42
- TARGET_COUNT = 100
- SIGMA = 100
- def load_dictionary(path: Path) -> list[str]:
- """Read frequency-sorted word list (one word per line). Preserves order."""
- words = [line.strip() for line in path.read_text(encoding="utf-8").splitlines() if line.strip()]
- return words
- def select_target_indices(rng: np.random.Generator, dict_len: int, count: int) -> list[int]:
- """Gaussian sample indices, de-duplicating via rejection.
- Sorted ascending so ID 1 = most common word."""
- selected: set[int] = set()
- while len(selected) < count:
- samples = np.abs(np.floor(rng.normal(loc=0, scale=SIGMA, size=count * 2)))
- for s in samples:
- idx = int(s)
- if 0 <= idx < dict_len:
- selected.add(idx)
- if len(selected) >= count:
- break
- return sorted(selected)
- def generate_bank(dict_path: Path, output_path: Path) -> None:
- rng = np.random.default_rng(SEED)
- guessable = load_dictionary(dict_path)
- indices = select_target_indices(rng, len(guessable), TARGET_COUNT)
- targets = [{"word": guessable[idx]} for i, idx in enumerate(indices)]
- bank = {"guessable": guessable, "targets": targets}
- output_path.write_text(
- json.dumps(bank, ensure_ascii=False, indent=2) + "\n",
- encoding="utf-8",
- )
- print(f"Wrote {output_path}: {len(guessable)} guessable, {len(targets)} targets")
- # Sanity checks
- target_words = [t["word"] for t in targets]
- assert all(w in guessable for w in target_words), "Target word not in guessable!"
- assert len(set(target_words)) == TARGET_COUNT, f"Duplicate target words ({len(set(target_words))} unique)!"
- print(f" Top 5: {[targets[i]['word'] for i in range(5)]}")
- def main():
- generate_bank(
- RAW_DIR / "english-5letter-freq-sortfreq-wordsonly.txt",
- DATA_DIR / "word-bank.json",
- )
- generate_bank(
- RAW_DIR / "russian-5letter-freq-sortfreq-wordsonly.txt",
- DATA_DIR / "word-bank-ru.json",
- )
- print("Done. Run 'npm run solve' to validate solvability.")
- if __name__ == "__main__":
- main()
|