candidate sets for the fill

This commit is contained in:
2025-05-19 20:48:10 +03:00
parent e80d48db61
commit 5a5e16dbd3
+107
View File
@@ -0,0 +1,107 @@
use crate::dict::Dictionary;
pub fn score_candidate(
word_idx: usize,
dict: &Dictionary,
used_words: &[bool],
placed_texts: &[String],
) -> f64 {
if used_words[word_idx] {
return -1.0;
}
let word = dict.get(word_idx);
let text = &word.text;
let mut score = word.frequency;
// reject words that are too similar to already-placed words
for placed in placed_texts {
if is_related(text, placed) {
return -1.0;
}
}
// bonus for letter variety
let bytes = text.as_bytes();
let mut seen = [false; 26];
let mut unique = 0;
for &b in bytes {
let idx = (b - b'A') as usize;
if !seen[idx] {
seen[idx] = true;
unique += 1;
}
}
let variety = unique as f64 / bytes.len() as f64;
score += variety * 0.1;
// bonus for common letters
let common = b"ETAOINSHRDLU";
let common_count = bytes.iter().filter(|b| common.contains(b)).count();
let common_ratio = common_count as f64 / bytes.len() as f64;
score += common_ratio * 0.05;
score
}
pub fn is_related(a: &str, b: &str) -> bool {
// one is a prefix of the other (SEAL / SEALS, RUN / RUNNING)
if a.len() >= 3 && b.len() >= 3 {
let (short, long) = if a.len() <= b.len() { (a, b) } else { (b, a) };
if long.starts_with(short) {
let suffix = &long[short.len()..];
// common inflection suffixes
if matches!(suffix, "S" | "ES" | "ED" | "ER" | "ING" | "LY" | "TION" | "NESS" | "MENT" | "ABLE" | "ISH" | "IST" | "IZE") {
return true;
}
}
// handle e-dropping: MAKE/MAKING, BAKE/BAKING
if short.len() >= 4 && long.ends_with("ING") {
let stem = &long[..long.len() - 3];
if short.len() == stem.len() + 1 && short.starts_with(stem) && short.ends_with('E') {
return true;
}
}
// consonant doubling: RUN/RUNNING, SIT/SITTING
if long.ends_with("ING") && long.len() >= 6 {
let before_ing = &long[..long.len() - 3];
let bb = before_ing.as_bytes();
if bb.len() >= 4 && bb[bb.len() - 1] == bb[bb.len() - 2] {
let stem = &before_ing[..before_ing.len() - 1];
if short == stem {
return true;
}
}
}
// also ED doubling: STOP/STOPPED
if long.ends_with("ED") && long.len() >= 5 {
let before_ed = &long[..long.len() - 2];
let bb = before_ed.as_bytes();
if bb.len() >= 4 && bb[bb.len() - 1] == bb[bb.len() - 2] {
let stem = &before_ed[..before_ed.len() - 1];
if short == stem {
return true;
}
}
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn related_words() {
assert!(is_related("SEAL", "SEALS"));
assert!(is_related("RUN", "RUNNING"));
assert!(is_related("MAKE", "MAKING"));
assert!(is_related("PLAY", "PLAYED"));
assert!(is_related("PLAY", "PLAYER"));
assert!(is_related("QUICK", "QUICKLY"));
assert!(!is_related("CAT", "DOG"));
assert!(!is_related("SEA", "SEAL"));
assert!(!is_related("THE", "THEM"));
}
}