more words
This commit is contained in:
90
scripts/find-chunk-words.mjs
Normal file
90
scripts/find-chunk-words.mjs
Normal file
@@ -0,0 +1,90 @@
|
||||
// Finds common English words that can be built from exactly 4 existing tiles
|
||||
// (chunks) in the target-word pool, but aren't target words yet.
|
||||
//
|
||||
// Usage: node scripts/find-chunk-words.mjs [--max-tier=35] [--json]
|
||||
import wl from 'wordlist-english';
|
||||
import { DICTIONARY_SET } from '../src/data/lexicon.js';
|
||||
import { TARGET_WORDS_POOL } from '../src/data/puzzles.js';
|
||||
|
||||
const args = Object.fromEntries(process.argv.slice(2).map(a => a.replace(/^--/, '').split('=')));
|
||||
const MAX_TIER = Number(args['max-tier'] || 35);
|
||||
const TIERS = [10, 20, 35, 40, 50].filter(t => t <= MAX_TIER);
|
||||
|
||||
// Every chunk currently used by a target word
|
||||
const chunks = new Set(TARGET_WORDS_POOL.flatMap(w => w.tiles.map(t => t.toLowerCase())));
|
||||
const existing = new Set(TARGET_WORDS_POOL.map(w => w.word));
|
||||
|
||||
// Common words only (frequency tiers from wordlist-english)
|
||||
const candidates = new Set();
|
||||
for (const t of TIERS) for (const w of wl[`english/${t}`] || []) candidates.add(w);
|
||||
|
||||
// Try to split a word into exactly 4 distinct chunks (each >= 2 letters).
|
||||
// Returns the most balanced split, or null.
|
||||
function split(word) {
|
||||
let best = null;
|
||||
const n = word.length;
|
||||
for (let a = 2; a <= n - 6; a++) {
|
||||
const t1 = word.slice(0, a);
|
||||
if (!chunks.has(t1)) continue;
|
||||
for (let b = a + 2; b <= n - 4; b++) {
|
||||
const t2 = word.slice(a, b);
|
||||
if (!chunks.has(t2)) continue;
|
||||
for (let c = b + 2; c <= n - 2; c++) {
|
||||
const t3 = word.slice(b, c);
|
||||
const t4 = word.slice(c);
|
||||
if (!chunks.has(t3) || !chunks.has(t4)) continue;
|
||||
const tiles = [t1, t2, t3, t4];
|
||||
if (new Set(tiles).size !== 4) continue;
|
||||
const lens = tiles.map(t => t.length);
|
||||
const spread = Math.max(...lens) - Math.min(...lens);
|
||||
if (!best || spread < best.spread) best = { tiles, spread };
|
||||
}
|
||||
}
|
||||
}
|
||||
return best && best.tiles;
|
||||
}
|
||||
|
||||
// Skip inflected forms whose base word exists (walked, happier, quickly, kittens...)
|
||||
const allWords = new Set(wl.english);
|
||||
function isInflection(w) {
|
||||
const bases = [];
|
||||
if (w.endsWith('ies')) bases.push(w.slice(0, -3) + 'y');
|
||||
if (w.endsWith('es')) bases.push(w.slice(0, -2));
|
||||
if (w.endsWith('s')) bases.push(w.slice(0, -1));
|
||||
if (w.endsWith('ed')) bases.push(w.slice(0, -2), w.slice(0, -1), w.slice(0, -3));
|
||||
if (w.endsWith('ied')) bases.push(w.slice(0, -3) + 'y');
|
||||
if (w.endsWith('ing')) bases.push(w.slice(0, -3), w.slice(0, -3) + 'e', w.slice(0, -4));
|
||||
if (w.endsWith('ly')) bases.push(w.slice(0, -2), w.slice(0, -3) + 'e');
|
||||
if (w.endsWith('ily')) bases.push(w.slice(0, -3) + 'y');
|
||||
if (w.endsWith('er')) bases.push(w.slice(0, -2), w.slice(0, -1), w.slice(0, -3));
|
||||
if (w.endsWith('est')) bases.push(w.slice(0, -3), w.slice(0, -2), w.slice(0, -4));
|
||||
if (w.endsWith('ness')) bases.push(w.slice(0, -4));
|
||||
return bases.some(b => b.length >= 3 && allWords.has(b));
|
||||
}
|
||||
|
||||
// Skip near-duplicates of words already in the pool (e.g. "dolphin" vs "dolphins")
|
||||
function isInflectionOfExisting(w) {
|
||||
const stems = [w.replace(/s$/, ''), w.replace(/es$/, ''), w.replace(/ies$/, 'y'), w + 's', w + 'es', w.replace(/y$/, 'ies')];
|
||||
return stems.some(s => s !== w && existing.has(s));
|
||||
}
|
||||
|
||||
const found = [];
|
||||
for (const word of candidates) {
|
||||
if (word.length < 8 || word.length > 14) continue;
|
||||
if (!/^[a-z]+$/.test(word)) continue;
|
||||
if (existing.has(word)) continue;
|
||||
if (word.endsWith('tion')) continue;
|
||||
if (!DICTIONARY_SET.has(word)) continue;
|
||||
if (isInflectionOfExisting(word)) continue;
|
||||
if (!('keep-inflections' in args) && isInflection(word)) continue;
|
||||
const tiles = split(word);
|
||||
if (tiles) found.push({ word, tiles });
|
||||
}
|
||||
|
||||
found.sort((a, b) => a.word.localeCompare(b.word));
|
||||
if ('json' in args) {
|
||||
console.log(JSON.stringify(found));
|
||||
} else {
|
||||
for (const f of found) console.log(`${f.word}|${f.tiles.join('-')}`);
|
||||
console.error(`\n${chunks.size} chunks, ${found.length} new words found (tiers <= ${MAX_TIER})`);
|
||||
}
|
||||
Reference in New Issue
Block a user