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 }
libpt = "0.4.2"
rand = "0.8.5"
regex = "1.10.3"
serde = { version = "1.0.197", optional = true, features = ["serde_derive"] }
serde_json = { version = "1.0.114", optional = true }
thiserror = "1.0.58"

View File

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

View File

@ -1,5 +1,7 @@
use thiserror::Error;
use crate::wlist::word::Word;
pub type WResult<T> = std::result::Result<T, Error>;
pub type GameResult<T> = std::result::Result<T, GameError>;
@ -18,12 +20,19 @@ pub enum Error {
// for `FromStr` of `BuiltinSolver`
#[error("Unknown builtin solver")]
UnknownBuiltinSolver,
#[error("pattern matching error")]
Regex{
#[from]
source: regex::Error
}
}
#[derive(Debug, Clone, Error)]
pub enum GameError {
#[error("The guess has the wrong length")]
GuessHasWrongLength,
#[error("The guess has the wrong length ({0})")]
GuessHasWrongLength(usize),
#[error("The game is finished but a guess is being made")]
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.
pub fn guess(&mut self, guess: Word) -> GameResult<GuessResponse> {
if guess.len() != self.length {
return Err(GameError::GuessHasWrongLength);
return Err(GameError::GuessHasWrongLength(guess.len()));
}
if self.finished || self.step > self.max_steps {
return Err(GameError::TryingToPlayAFinishedGame);
}
if self.wordlist.get_word(&guess).is_none() {
return Err(GameError::WordNotInWordlist(guess));
}
self.step += 1;
let mut compare_solution = self.solution.0.clone();
@ -101,7 +104,8 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
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();
Ok(response)
}
@ -128,6 +132,13 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
pub fn responses(&self) -> &Vec<GuessResponse> {
&self.responses
}
pub fn last_response(&self) -> Option<&GuessResponse> {
self.responses().last()
}
pub fn wordlist(&self) -> &WL {
self.wordlist
}
}
/// Build and Configure a [`Game`]

View File

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

View File

@ -3,7 +3,7 @@ use libpt::log::info;
use crate::wlist::word::Word;
use crate::wlist::WordList;
use super::{AnyBuiltinSolver, Solver};
use super::{AnyBuiltinSolver, Solver, Status};
#[derive(Debug, Clone)]
pub struct NaiveSolver<'wl, WL> {
@ -16,7 +16,21 @@ impl<'wl, WL: WordList> Solver<'wl, WL> for NaiveSolver<'wl, WL> {
Ok(Self { wl: wordlist })
}
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
}
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 regex::Regex;
use std::collections::HashMap;
use std::ops::RangeBounds;
@ -14,13 +17,16 @@ pub type AnyWordlist = Box<dyn WordList>;
pub trait WordList: Clone + std::fmt::Debug + Default {
fn solutions(&self) -> ManyWordDatas {
let wmap = self.wordmap();
let wmap = self.wordmap().clone();
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 {
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())
}
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;
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::fmt::write;
use std::hash::Hash;
use libpt::log::debug;
#[cfg(feature = "serde")]
@ -11,7 +12,7 @@ pub type Frequency = f64;
pub type Word = String;
pub type WordData = (Word, Frequency);
pub type ManyWords<'a> = Vec<&'a Word>;
pub type ManyWordDatas<'a> = Vec<(&'a Word, &'a Frequency)>;
pub type ManyWordDatas = Vec<(Word, Frequency)>;
#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@ -66,9 +67,9 @@ impl WordMap {
pub fn inner(&self) -> &HashMap<Word, Frequency> {
&self.inner
}
pub fn get(&self, word: &Word) -> Option<WordData> {
match self.inner.get(word) {
Some(f) => Some((word.clone(), *f)),
pub fn get<I: std::fmt::Display>(&self, word: I) -> Option<WordData> {
match self.inner.get(&word.to_string()) {
Some(f) => Some((word.to_string(), *f)),
None => None,
}
}
@ -104,7 +105,7 @@ impl From<HashMap<Word, Frequency>> for WordMap {
}
}
impl From<WordMap > for HashMap<Word, Frequency>{
impl From<WordMap> for HashMap<Word, Frequency> {
fn from(value: WordMap) -> Self {
value.inner
}