match and get by frequency

This commit is contained in:
Christoph J. Scherr 2024-03-22 17:26:34 +01:00
parent 74ef508841
commit 9beb38a5bb
Signed by: cscherrNT
GPG Key ID: 8E2B45BC51A27EA7
9 changed files with 91 additions and 17 deletions

View File

@ -28,6 +28,7 @@ clap = { version = "4.5.3", features = ["derive"], optional = true }
colored = { version = "2.1.0", optional = false } colored = { version = "2.1.0", optional = false }
libpt = "0.4.2" libpt = "0.4.2"
rand = "0.8.5" rand = "0.8.5"
regex = "1.10.3"
serde = { version = "1.0.197", optional = true, features = ["serde_derive"] } serde = { version = "1.0.197", optional = true, features = ["serde_derive"] }
serde_json = { version = "1.0.114", optional = true } serde_json = { version = "1.0.114", optional = true }
thiserror = "1.0.58" thiserror = "1.0.58"

View File

@ -54,8 +54,8 @@ fn main() -> anyhow::Result<()> {
response = match game.guess(guess) { response = match game.guess(guess) {
Ok(r) => r, Ok(r) => r,
Err(err) => match err { Err(err) => match err {
GameError::GuessHasWrongLength => { GameError::GuessHasWrongLength(len) => {
println!("word length: must be {} long", game.length()); println!("word length: must be {} long but is {}", game.length(), len);
continue; continue;
} }
_ => { _ => {

View File

@ -1,5 +1,7 @@
use thiserror::Error; use thiserror::Error;
use crate::wlist::word::Word;
pub type WResult<T> = std::result::Result<T, Error>; pub type WResult<T> = std::result::Result<T, Error>;
pub type GameResult<T> = std::result::Result<T, GameError>; pub type GameResult<T> = std::result::Result<T, GameError>;
@ -18,12 +20,19 @@ pub enum Error {
// for `FromStr` of `BuiltinSolver` // for `FromStr` of `BuiltinSolver`
#[error("Unknown builtin solver")] #[error("Unknown builtin solver")]
UnknownBuiltinSolver, UnknownBuiltinSolver,
#[error("pattern matching error")]
Regex{
#[from]
source: regex::Error
}
} }
#[derive(Debug, Clone, Error)] #[derive(Debug, Clone, Error)]
pub enum GameError { pub enum GameError {
#[error("The guess has the wrong length")] #[error("The guess has the wrong length ({0})")]
GuessHasWrongLength, GuessHasWrongLength(usize),
#[error("The game is finished but a guess is being made")] #[error("The game is finished but a guess is being made")]
TryingToPlayAFinishedGame, TryingToPlayAFinishedGame,
#[error("Tried to guess a word that is not in the wordlist ({0})")]
WordNotInWordlist(Word),
} }

View File

@ -78,11 +78,14 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
/// if the game is finished. /// if the game is finished.
pub fn guess(&mut self, guess: Word) -> GameResult<GuessResponse> { pub fn guess(&mut self, guess: Word) -> GameResult<GuessResponse> {
if guess.len() != self.length { if guess.len() != self.length {
return Err(GameError::GuessHasWrongLength); return Err(GameError::GuessHasWrongLength(guess.len()));
} }
if self.finished || self.step > self.max_steps { if self.finished || self.step > self.max_steps {
return Err(GameError::TryingToPlayAFinishedGame); return Err(GameError::TryingToPlayAFinishedGame);
} }
if self.wordlist.get_word(&guess).is_none() {
return Err(GameError::WordNotInWordlist(guess));
}
self.step += 1; self.step += 1;
let mut compare_solution = self.solution.0.clone(); let mut compare_solution = self.solution.0.clone();
@ -101,7 +104,8 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
evaluation.push((c, status)); evaluation.push((c, status));
} }
let mut response = GuessResponse::new(guess, evaluation, &self); let response = GuessResponse::new(guess, evaluation, self);
self.responses.push(response.clone());
self.finished = response.finished(); self.finished = response.finished();
Ok(response) Ok(response)
} }
@ -128,6 +132,13 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
pub fn responses(&self) -> &Vec<GuessResponse> { pub fn responses(&self) -> &Vec<GuessResponse> {
&self.responses &self.responses
} }
pub fn last_response(&self) -> Option<&GuessResponse> {
self.responses().last()
}
pub fn wordlist(&self) -> &WL {
self.wordlist
}
} }
/// Build and Configure a [`Game`] /// Build and Configure a [`Game`]

View File

@ -62,6 +62,14 @@ impl GuessResponse {
None None
} }
} }
pub fn evaluation(&self) -> &[(char, Status)] {
&self.evaluation
}
pub fn guess(&self) -> &str {
&self.guess
}
} }
impl Display for GuessResponse { impl Display for GuessResponse {

View File

@ -3,7 +3,7 @@ use libpt::log::info;
use crate::wlist::word::Word; use crate::wlist::word::Word;
use crate::wlist::WordList; use crate::wlist::WordList;
use super::{AnyBuiltinSolver, Solver}; use super::{AnyBuiltinSolver, Solver, Status};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct NaiveSolver<'wl, WL> { pub struct NaiveSolver<'wl, WL> {
@ -16,7 +16,21 @@ impl<'wl, WL: WordList> Solver<'wl, WL> for NaiveSolver<'wl, WL> {
Ok(Self { wl: wordlist }) Ok(Self { wl: wordlist })
} }
fn guess_for(&self, game: &crate::game::Game<WL>) -> Word { fn guess_for(&self, game: &crate::game::Game<WL>) -> Word {
self.wl.rand_word().0 // HACK: hardcoded length
let mut buf: Word = Word::from(".....");
let response = game.last_response();
if response.is_some() {
for (idx, p) in response.unwrap().evaluation().iter().enumerate() {
if p.1 == Status::Matched {
buf.replace_range(idx..idx + 1, &p.0.to_string());
}
}
}
game.wordlist()
.get_words_matching(buf)
.expect("the solution does not exist in the wordlist")[0]
.0
.clone()
} }
} }

View File

@ -19,7 +19,7 @@ impl super::WordList for BuiltinWList {
&self.words &self.words
} }
fn get_word(&self, word: &Word) -> Option<super::WordData> { fn get_word(&self, word: &Word) -> Option<super::WordData> {
self.words.get(&word) self.words.get(word)
} }
} }

View File

@ -1,5 +1,8 @@
use libpt::log::debug;
use rand::seq::IteratorRandom; use rand::seq::IteratorRandom;
use regex::Regex;
use std::collections::HashMap; use std::collections::HashMap;
use std::ops::RangeBounds; use std::ops::RangeBounds;
@ -14,13 +17,16 @@ pub type AnyWordlist = Box<dyn WordList>;
pub trait WordList: Clone + std::fmt::Debug + Default { pub trait WordList: Clone + std::fmt::Debug + Default {
fn solutions(&self) -> ManyWordDatas { fn solutions(&self) -> ManyWordDatas {
let wmap = self.wordmap(); let wmap = self.wordmap().clone();
let threshold = wmap.threshold(); let threshold = wmap.threshold();
wmap.iter().filter(|i| *i.1 > threshold).collect() wmap.iter()
.filter(|i| *i.1 > threshold)
.map(|p| (p.0.clone(), *p.1))
.collect()
} }
fn rand_solution(&self) -> WordData { fn rand_solution(&self) -> WordData {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let sol = *self.solutions().iter().choose(&mut rng).unwrap(); let sol = self.solutions().iter().choose(&mut rng).unwrap().clone();
(sol.0.to_owned(), sol.1.to_owned()) (sol.0.to_owned(), sol.1.to_owned())
} }
fn rand_word(&self) -> WordData { fn rand_word(&self) -> WordData {
@ -62,4 +68,28 @@ pub trait WordList: Clone + std::fmt::Debug + Default {
let n: f64 = cmap.keys().len() as f64; let n: f64 = cmap.keys().len() as f64;
cmap.into_iter().map(|p| (p.0, p.1 as f64 / n)).collect() cmap.into_iter().map(|p| (p.0, p.1 as f64 / n)).collect()
} }
fn raw_wordlist(&self) -> String {
let mut buf = String::new();
for w in self.wordmap().keys() {
buf += &w;
buf += "\n";
}
buf
}
fn get_words_matching(&self, pattern: String) -> WResult<ManyWordDatas> {
let pattern = Regex::new(&pattern)?;
let hay = self.raw_wordlist();
let keys = pattern.captures_iter(&hay);
let mut buf = ManyWordDatas::new();
for k in keys {
debug!("match: {k:?}");
let w: WordData = self.wordmap().get(&k[0]).unwrap();
buf.push(w)
}
// sort by frequency
buf.sort_by(|a, b| {
a.1.partial_cmp(&b.1).unwrap()
});
Ok(buf)
}
} }

View File

@ -1,5 +1,6 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::write; use std::fmt::write;
use std::hash::Hash;
use libpt::log::debug; use libpt::log::debug;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
@ -11,7 +12,7 @@ pub type Frequency = f64;
pub type Word = String; pub type Word = String;
pub type WordData = (Word, Frequency); pub type WordData = (Word, Frequency);
pub type ManyWords<'a> = Vec<&'a Word>; pub type ManyWords<'a> = Vec<&'a Word>;
pub type ManyWordDatas<'a> = Vec<(&'a Word, &'a Frequency)>; pub type ManyWordDatas = Vec<(Word, Frequency)>;
#[derive(Clone)] #[derive(Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@ -66,9 +67,9 @@ impl WordMap {
pub fn inner(&self) -> &HashMap<Word, Frequency> { pub fn inner(&self) -> &HashMap<Word, Frequency> {
&self.inner &self.inner
} }
pub fn get(&self, word: &Word) -> Option<WordData> { pub fn get<I: std::fmt::Display>(&self, word: I) -> Option<WordData> {
match self.inner.get(word) { match self.inner.get(&word.to_string()) {
Some(f) => Some((word.clone(), *f)), Some(f) => Some((word.to_string(), *f)),
None => None, None => None,
} }
} }