Compare commits

...

16 commits

Author SHA1 Message Date
c140264a0c chore: bump to first alpha version
Some checks failed
cargo devel CI / cargo CI (push) Failing after 1m23s
2024-08-07 14:27:52 +02:00
567d02d48b refactor(game): guess only needs a &Word 2024-08-02 17:37:36 +02:00
b6f639da67 Merge branch 'devel' into feat/naive
Some checks failed
cargo devel CI / cargo CI (push) Failing after 2m11s
2024-08-02 17:10:59 +02:00
1b63e4d9cc fix(naive): make the naive solver work with a winrate of seemingly over 98%
Some checks failed
cargo devel CI / cargo CI (push) Failing after 1m58s
2024-08-02 16:58:42 +02:00
d5cf04d89b fix(game): evaluation was not generated correctly in an edge case #18
Some checks failed
cargo devel CI / cargo CI (push) Failing after 1m52s
2024-08-02 16:22:03 +02:00
3c44760a2c feat(naive): make it almost work
Some checks failed
cargo devel CI / cargo CI (push) Failing after 1m56s
2024-08-02 16:13:41 +02:00
f511e158cd feat(naive): state building from responses works
Some checks failed
cargo devel CI / cargo CI (push) Failing after 2m0s
2024-08-02 15:33:41 +02:00
29ec8bf219 docs(game): make it clear what some parameters and methods do
Some checks failed
cargo devel CI / cargo CI (push) Failing after 1m48s
2024-08-02 15:12:07 +02:00
3cd20d775e feat(naive): start moving to state machine for solver internals
Some checks failed
cargo devel CI / cargo CI (push) Has been cancelled
2024-08-02 15:11:46 +02:00
714aac0d86 feat(naive): try to fix naive solver screwing up in less than 1% of cases WIP
Some checks failed
cargo devel CI / cargo CI (push) Failing after 1m50s
2024-08-01 17:25:36 +02:00
c76c1d2d78 refactor(evaluation): add some public methods and trait impls 2024-08-01 15:46:05 +02:00
845fd73040 fix(naive): improve found character indexing #2
Some checks failed
cargo devel CI / cargo CI (push) Failing after 1m53s
2024-07-31 11:00:45 +02:00
4b7e9433b9 feat(naive): don't repeat the same char on the same index, unless it is matched #2
Some checks failed
cargo devel CI / cargo CI (push) Failing after 1m51s
2024-07-31 10:51:14 +02:00
970c1be143 fix(bench): use the n defined by cli
Some checks failed
cargo devel CI / cargo CI (push) Failing after 1m57s
2024-07-31 10:25:03 +02:00
6f72fda4ff feat(naive): don't reuse bad chars #12
Some checks failed
cargo devel CI / cargo CI (push) Failing after 1m53s
2024-07-31 10:16:51 +02:00
1b354299d4 fix(naive): don't hardcode pattern length 2024-07-31 09:34:18 +02:00
11 changed files with 294 additions and 58 deletions

View file

@ -1,6 +1,6 @@
[package] [package]
name = "wordle-analyzer" name = "wordle-analyzer"
version = "0.1.0" version = "0.1.0-alpha.0"
edition = "2021" edition = "2021"
publish = false publish = false
authors = ["Christoph J. Scherr <software@cscherr.de>"] authors = ["Christoph J. Scherr <software@cscherr.de>"]
@ -60,4 +60,7 @@ path = "src/bin/bench/cli.rs"
required-features = ["solve", "cli", "bench", "builtin"] required-features = ["solve", "cli", "bench", "builtin"]
[dev-dependencies] [dev-dependencies]
test-log = { version = "0.2.16", default-features = false, features = ["color", "trace"] } test-log = { version = "0.2.16", default-features = false, features = [
"color",
"trace",
] }

View file

@ -72,7 +72,7 @@ fn main() -> anyhow::Result<()> {
let bench = BuiltinBenchmark::build(&wl, solver, builder, cli.threads)?; let bench = BuiltinBenchmark::build(&wl, solver, builder, cli.threads)?;
trace!("{bench:#?}"); trace!("{bench:#?}");
bench.start(50, &bench.builder())?; bench.start(cli.n, &bench.builder())?;
loop { loop {
sleep(std::time::Duration::from_secs(1)); sleep(std::time::Duration::from_secs(1));

View file

@ -61,7 +61,7 @@ fn main() -> anyhow::Result<()> {
let mut guess: Word; let mut guess: Word;
loop { loop {
guess = get_word(&cli, game.step())?; guess = get_word(&cli, game.step())?;
response = match game.guess(guess, None) { response = match game.guess(&guess, None) {
Ok(r) => r, Ok(r) => r,
Err(err) => match err { Err(err) => match err {
GameError::GuessHasWrongLength(len) => { GameError::GuessHasWrongLength(len) => {

View file

@ -172,7 +172,7 @@ fn help_guess_interactive(cli: Cli) -> anyhow::Result<()> {
eprintln!("{}", style(best_guess.unwrap_err()).red().bold()); eprintln!("{}", style(best_guess.unwrap_err()).red().bold());
continue; continue;
} }
debug!("game state: {game:?}"); trace!("game state: {game:?}");
println!("best guess: {}", best_guess.unwrap()); println!("best guess: {}", best_guess.unwrap());
} }
ReplCommand::Guess { ReplCommand::Guess {
@ -181,14 +181,14 @@ fn help_guess_interactive(cli: Cli) -> anyhow::Result<()> {
} => { } => {
let evaluation_converted: Evaluation = let evaluation_converted: Evaluation =
Evaluation::build(&your_guess, &evalutation)?; Evaluation::build(&your_guess, &evalutation)?;
let guess = game.guess(your_guess, Some(evaluation_converted)); let guess = game.guess(&your_guess, Some(evaluation_converted));
debug!("your guess: {guess:?}"); debug!("your guess: {guess:?}");
if guess.is_err() { if guess.is_err() {
eprintln!("{}", style(guess.unwrap_err()).red().bold()); eprintln!("{}", style(guess.unwrap_err()).red().bold());
continue; continue;
} }
println!("{}", guess.unwrap()); println!("{}", guess.unwrap());
debug!("game state: {game:#?}"); trace!("game state: {game:#?}");
} }
ReplCommand::New => game = builder.build()?, ReplCommand::New => game = builder.build()?,
ReplCommand::Undo { n } => game.undo(n)?, ReplCommand::Undo { n } => game.undo(n)?,
@ -218,7 +218,7 @@ fn play_native_non_interactive(cli: Cli) -> anyhow::Result<()> {
"eng" => BuiltinWList::english(cli.length), "eng" => BuiltinWList::english(cli.length),
_ => BuiltinWList::load(&cli.wordlist, cli.length)?, _ => BuiltinWList::load(&cli.wordlist, cli.length)?,
}; };
debug!("wordlist: {wl}"); trace!("wordlist: {wl}");
let mut builder = game::Game::builder(&wl) let mut builder = game::Game::builder(&wl)
.length(cli.length) .length(cli.length)
.max_steps(cli.max_steps) .max_steps(cli.max_steps)
@ -238,13 +238,13 @@ fn play_native_non_interactive(cli: Cli) -> anyhow::Result<()> {
let solver = cli.solver.to_solver(&wl); let solver = cli.solver.to_solver(&wl);
let mut game = builder.build()?; let mut game = builder.build()?;
debug!("{game:#?}"); trace!("{game:#?}");
let mut response: GuessResponse; let mut response: GuessResponse;
let mut _guess: Word; let mut _guess: Word;
loop { loop {
response = solver.make_a_move(&mut game)?; response = solver.make_a_move(&mut game)?;
debug!("game state: {game:#?}"); trace!("game state: {game:#?}");
println!("{}. guess: {response}", game.step() - 1); println!("{}. guess: {response}", game.step() - 1);
if response.finished() { if response.finished() {

View file

@ -1,7 +1,7 @@
use thiserror::Error; use thiserror::Error;
use crate::bench::report::Report; use crate::bench::report::Report;
use crate::wlist::word::Word; use crate::wlist::word::{Word, WordData};
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>;
@ -62,8 +62,8 @@ pub enum BenchError {
#[derive(Debug, Clone, Error)] #[derive(Debug, Clone, Error)]
pub enum SolverError { pub enum SolverError {
#[error("Wordlist has no matches for the gamestate")] #[error("Wordlist has no matches for the gamestate (solution: {0:?})")]
NoMatches, NoMatches(Option<WordData>),
#[error("Unknown builtin solver")] #[error("Unknown builtin solver")]
UnknownBuiltinSolver, UnknownBuiltinSolver,
} }

View file

@ -68,6 +68,18 @@ impl Evaluation {
} }
Ok(v.into()) Ok(v.into())
} }
pub fn inner(&self) -> &Vec<EvaluationUnit> {
&self.inner
}
pub fn inner_mut(&mut self) -> &mut Vec<EvaluationUnit> {
&mut self.inner
}
pub fn guess(&self) -> Word {
Word::from(self)
}
} }
impl IntoIterator for Evaluation { impl IntoIterator for Evaluation {
@ -87,6 +99,12 @@ impl From<Vec<EvaluationUnit>> for Evaluation {
impl From<Evaluation> for Word { impl From<Evaluation> for Word {
fn from(value: Evaluation) -> Self { fn from(value: Evaluation) -> Self {
Word::from(value.inner.into_iter().map(|v| v.0).collect::<String>()) Word::from(value.inner.iter().map(|v| v.0).collect::<String>())
}
}
impl From<&Evaluation> for Word {
fn from(value: &Evaluation) -> Self {
Word::from(value.inner.iter().map(|v| v.0).collect::<String>())
} }
} }

View file

@ -49,6 +49,16 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
/// # Errors /// # Errors
/// ///
/// No Errors /// No Errors
///
/// # Parameters
///
/// `length` - how many chars the solution has
/// `precompute` - how many chars the solution has
/// `max_steps` - how many tries the player has
/// `precompute` - how many chars the solution has
/// `wlist` - which wordlist to use
/// `generate_solution` - should the game have a randomly generated solution?
pub fn build( pub fn build(
length: usize, length: usize,
precompute: bool, precompute: bool,
@ -91,7 +101,7 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
/// ///
/// This function will return an error if the length of the [Word] is wrong It will also error /// This function will return an error if the length of the [Word] is wrong It will also error
/// if the game is finished. /// if the game is finished.
pub fn guess(&mut self, guess: Word, eval: Option<Evaluation>) -> GameResult<GuessResponse> { pub fn guess(&mut self, guess: &Word, eval: Option<Evaluation>) -> GameResult<GuessResponse> {
if guess.len() != self.length { if guess.len() != self.length {
return Err(GameError::GuessHasWrongLength(guess.len())); return Err(GameError::GuessHasWrongLength(guess.len()));
} }
@ -99,7 +109,7 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
return Err(GameError::TryingToPlayAFinishedGame); return Err(GameError::TryingToPlayAFinishedGame);
} }
if self.wordlist.get_word(&guess).is_none() { if self.wordlist.get_word(&guess).is_none() {
return Err(GameError::WordNotInWordlist(guess)); return Err(GameError::WordNotInWordlist(guess.to_string()));
} }
self.step += 1; self.step += 1;
@ -115,20 +125,22 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
Ok(response) Ok(response)
} }
pub fn evaluate(mut solution: WordData, guess: &Word) -> Evaluation { /// Generates an [Evaluation] for a given solution and guess.
pub(crate) fn evaluate(mut solution: WordData, guess: &Word) -> Evaluation {
let mut evaluation = Vec::new(); let mut evaluation = Vec::new();
let mut status: Status; let mut status: Status;
for (idx, c) in guess.chars().enumerate() { let mut buf = solution.0.clone();
if solution.0.chars().nth(idx) == Some(c) { for ((idx, c_guess), c_sol) in guess.chars().enumerate().zip(solution.0.chars()) {
if c_guess == c_sol {
status = Status::Matched; status = Status::Matched;
solution.0.replace_range(idx..idx + 1, "_"); buf.replace_range(idx..idx + 1, "_");
} else if solution.0.contains(c) { } else if buf.contains(c_guess) {
status = Status::Exists; status = Status::Exists;
solution.0 = solution.0.replacen(c, "_", 1); buf = buf.replacen(c_guess, "_", 1);
} else { } else {
status = Status::None status = Status::None
} }
evaluation.push((c, status)); evaluation.push((c_guess, status));
} }
evaluation.into() evaluation.into()
} }
@ -140,18 +152,25 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
Ok(()) Ok(())
} }
/// get how many characters the words have for this game
pub fn length(&self) -> usize { pub fn length(&self) -> usize {
self.length self.length
} }
/// get the solution for this game, if the game is aware of one.
///
/// Consider that games may also be played on other platforms, so the game might not "know" the
/// solution yet.
pub fn solution(&self) -> Option<&WordData> { pub fn solution(&self) -> Option<&WordData> {
self.solution.as_ref() self.solution.as_ref()
} }
/// get how many guesses have been made already
pub fn step(&self) -> usize { pub fn step(&self) -> usize {
self.step self.step
} }
/// true if the game has finished and no more guesses can be made
pub fn finished(&self) -> bool { pub fn finished(&self) -> bool {
if self.responses().is_empty() { if self.responses().is_empty() {
return false; return false;
@ -159,27 +178,35 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
self.responses().last().unwrap().finished() self.responses().last().unwrap().finished()
} }
/// true if the game has finished and the solution was found
pub fn won(&self) -> bool { pub fn won(&self) -> bool {
if self.responses().is_empty() { if !self.finished() || self.responses().is_empty() {
return false; return false;
} }
self.responses().last().unwrap().won() self.responses().last().unwrap().won()
} }
/// get how many tries the player has
pub fn max_steps(&self) -> usize { pub fn max_steps(&self) -> usize {
self.max_steps self.max_steps
} }
/// get the responses that were already made
pub fn responses(&self) -> &Vec<GuessResponse> { pub fn responses(&self) -> &Vec<GuessResponse> {
&self.responses &self.responses
} }
/// get the most recent response
pub fn last_response(&self) -> Option<&GuessResponse> { pub fn last_response(&self) -> Option<&GuessResponse> {
self.responses().last() self.responses().last()
} }
/// get the [WordList] for this game
pub fn wordlist(&self) -> &WL { pub fn wordlist(&self) -> &WL {
self.wordlist self.wordlist
} }
/// get the [Words](Word) that have already been tried
pub(crate) fn made_guesses(&self) -> Vec<&Word> { pub(crate) fn made_guesses(&self) -> Vec<&Word> {
self.responses.iter().map(|r| r.guess()).collect() self.responses.iter().map(|r| r.guess()).collect()
} }
@ -297,6 +324,14 @@ impl<'wl, WL: WordList> GameBuilder<'wl, WL> {
self self
} }
/// Enable or disable Generation of a solution for this builder
///
/// Default is true
pub fn generate_solution(mut self, generate: bool) -> Self {
self.generate_solution = generate;
self
}
/// Set the solution for the games built by the builder /// Set the solution for the games built by the builder
/// ///
/// If this is [Some], then the solution generated by /// If this is [Some], then the solution generated by

View file

@ -51,7 +51,7 @@ pub trait Solver<'wl, WL: WordList>: Clone + std::fmt::Debug + Sized + Sync {
/// ///
/// This function will return an error if [guess_for](Solver::guess_for) fails. /// This function will return an error if [guess_for](Solver::guess_for) fails.
fn make_a_move(&self, game: &mut Game<'wl, WL>) -> WResult<GuessResponse> { fn make_a_move(&self, game: &mut Game<'wl, WL>) -> WResult<GuessResponse> {
Ok(game.guess(self.guess_for(game)?, None)?) Ok(game.guess(&self.guess_for(game)?, None)?)
} }
/// Play a [Game] and return the last [GuessResponse]. /// Play a [Game] and return the last [GuessResponse].
/// ///

View file

@ -1,11 +1,17 @@
use libpt::log::{info, trace}; use std::collections::HashMap;
use libpt::log::{debug, error, info, trace};
use crate::error::{SolverError, WResult}; use crate::error::{SolverError, WResult};
use crate::game::evaluation::{Evaluation, EvaluationUnit};
use crate::wlist::word::{Word, WordData}; use crate::wlist::word::{Word, WordData};
use crate::wlist::WordList; use crate::wlist::WordList;
use super::{AnyBuiltinSolver, Solver, Status}; use super::{AnyBuiltinSolver, Solver, Status};
mod states;
use states::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct NaiveSolver<'wl, WL> { pub struct NaiveSolver<'wl, WL> {
wl: &'wl WL, wl: &'wl WL,
@ -26,51 +32,82 @@ impl<'wl, WL: WordList> Solver<'wl, WL> for NaiveSolver<'wl, WL> {
/// * Discard all words that don't have the chars that we know from the last guess are in the /// * Discard all words that don't have the chars that we know from the last guess are in the
/// word, but don't know the position of. /// word, but don't know the position of.
fn guess_for(&self, game: &crate::game::Game<WL>) -> WResult<Word> { fn guess_for(&self, game: &crate::game::Game<WL>) -> WResult<Word> {
// HACK: hardcoded length let mut pattern: String = ".".repeat(game.length());
let mut pattern: String = String::from("....."); // indexes we tried for that char and the number of occurences
let mut other_chars: Vec<char> = Vec::new(); let mut state: SolverState = SolverState::new();
let response = game.last_response(); let responses = game.responses().iter().enumerate();
trace!( for (_idx, response) in responses {
"guessing best guess for last response: {response:#?}\n{:#?}", let mut already_found_amounts: HashMap<char, usize> = HashMap::new();
response.map(|a| a.evaluation()) let evaluation: &Evaluation = response.evaluation();
); for (idx, p) in evaluation.clone().into_iter().enumerate() {
if response.is_some() { match p.1 {
for (idx, p) in response Status::Matched => {
.unwrap() pattern.replace_range(idx..idx + 1, &p.0.to_string());
.evaluation()
.clone() state
.into_iter() .char_map_mut()
.enumerate() .entry(p.0)
{ .or_insert(CharInfo::new(game.length()))
if p.1 == Status::Matched { .found_at(idx);
pattern.replace_range(idx..idx + 1, &p.0.to_string()); *already_found_amounts.entry(p.0).or_default() += 1;
} else if p.1 == Status::Exists { }
other_chars.push(p.0) Status::Exists => {
let cinfo = state
.char_map_mut()
.entry(p.0)
.or_insert(CharInfo::new(game.length()));
cinfo.tried_but_failed(idx);
*already_found_amounts.entry(p.0).or_default() += 1;
cinfo.min_occurences(already_found_amounts[&p.0]);
}
Status::None => state
.char_map_mut()
.entry(p.0)
.or_insert(CharInfo::new(game.length()))
.max_occurences(*already_found_amounts.entry(p.0).or_default()),
} }
trace!("absolute frequencies: {already_found_amounts:?}");
} }
} }
trace!("other chars: {:?}", other_chars);
let mut matches: Vec<WordData> = game.wordlist().get_words_matching(pattern)?; debug!("built state from responses: {state:#?}");
// get all words that have the correct chars on the same positions
let mut matches: Vec<WordData> = game.wordlist().get_words_matching(&pattern)?;
if matches.is_empty() { if matches.is_empty() {
return Err(SolverError::NoMatches.into()); error!("no matches even when just considering the known good chars");
return Err(SolverError::NoMatches(game.solution().cloned()).into());
} else {
trace!("found {} basic matches", matches.len())
} }
matches = matches matches = matches
.iter() .iter()
// only words that have not been guessed yet // only words that have not been guessed yet
.filter(|p| !game.made_guesses().contains(&&p.0)) .filter(|p| !game.made_guesses().contains(&&p.0))
// only words that contain the letters we found earlier (that were not matched) .filter(|solution_candidate| {
.filter(|p| { if !game.responses().is_empty()
// TODO: don't repeat unmatched contained chars on the same position twice #2 && !state.has_all_known_contained(&solution_candidate.0)
let mut fits = true; {
for c in other_chars.iter() { trace!("known cont:{:#?}", state.get_all_known_contained());
fits &= p.0.contains(*c); return false;
} }
fits for (idx, c) in solution_candidate.0.char_indices() {
let cinfo = state
.char_map_mut()
.entry(c)
.or_insert(CharInfo::new(game.length()));
if !cinfo.occurences_of_char_possible(&solution_candidate.0, c)
|| cinfo.has_been_tried(idx)
{
return false;
}
}
true
}) })
.map(|v| v.to_owned()) .map(|v| v.to_owned())
.collect(); .collect();
if matches.is_empty() { if matches.is_empty() {
return Err(SolverError::NoMatches.into()); return Err(SolverError::NoMatches(game.solution().cloned()).into());
} }
Ok(matches[0].0.to_owned()) Ok(matches[0].0.to_owned())
} }

143
src/solve/naive/states.rs Normal file
View file

@ -0,0 +1,143 @@
use std::collections::{HashMap, HashSet};
use std::fmt::Debug;
use std::ops::{Range, RangeBounds};
use crate::error::WResult;
use crate::wlist::word::Word;
pub(crate) type CharMap = HashMap<char, CharInfo>;
#[derive(Clone, PartialEq, Eq)]
pub(crate) struct CharInfo {
confirmed_indexes: HashSet<usize>,
bad_indexes: HashSet<usize>,
occurences_amount: Range<usize>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct SolverState {
char_map: CharMap,
}
impl SolverState {
pub fn new() -> Self {
Self {
char_map: HashMap::new(),
}
}
pub fn char_map(&self) -> &CharMap {
&self.char_map
}
pub fn char_map_mut(&mut self) -> &mut CharMap {
&mut self.char_map
}
pub(crate) fn get_all_known_contained(&self) -> Vec<(&char, &CharInfo)> {
self.char_map
.iter()
.filter(|(key, value)| value.part_of_solution())
.collect()
}
pub(crate) fn has_all_known_contained(&self, guess: &Word) -> bool {
for needed_char in self.get_all_known_contained() {
if !guess.contains(*needed_char.0) {
return false;
}
}
true
}
}
impl CharInfo {
pub fn new(word_length: usize) -> Self {
Self {
confirmed_indexes: HashSet::new(),
bad_indexes: HashSet::new(),
occurences_amount: 0..word_length,
}
}
pub fn found_at(&mut self, idx: usize) {
self.confirmed_indexes.insert(idx);
if self.occurences_amount.start < 1 {
self.occurences_amount.start = 1;
}
}
/// tried to guess a char we know exists at this position, but it was incorrect
pub fn tried_but_failed(&mut self, idx: usize) {
self.bad_indexes.insert(idx);
}
pub fn has_been_tried(&self, idx: usize) -> bool {
self.bad_indexes.contains(&idx)
}
#[must_use]
pub fn part_of_solution(&self) -> bool {
self.occurences_amount.start > 0 && self.occurences_amount.end > 0
}
pub fn min_occurences(&mut self, min: usize) {
self.occurences_amount.start = min;
}
pub(crate) fn max_occurences(&mut self, max: usize) {
self.occurences_amount.end = max
}
pub(crate) fn confirmed_indexes(&self) -> &HashSet<usize> {
&self.confirmed_indexes
}
pub(crate) fn confirmed_indexes_mut(&mut self) -> &mut HashSet<usize> {
&mut self.confirmed_indexes
}
pub(crate) fn tried_indexes(&self) -> &HashSet<usize> {
&self.bad_indexes
}
pub(crate) fn tried_indexes_mut(&mut self) -> &mut HashSet<usize> {
&mut self.bad_indexes
}
pub(crate) fn occurences_amount(&self) -> &Range<usize> {
&self.occurences_amount
}
pub(crate) fn occurences_amount_mut(&mut self) -> &mut Range<usize> {
&mut self.occurences_amount
}
pub(crate) fn occurences_of_char_possible(
&self,
solution_candidate: &str,
character: char,
) -> bool {
let occ = solution_candidate
.chars()
.filter(|c| *c == character)
.count();
self.occurences_amount.start <= occ && occ <= self.occurences_amount.end
}
}
impl Debug for CharInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.part_of_solution() {
f.debug_struct("CharInfo")
.field("correct_idxs", &self.confirmed_indexes)
.field("amnt_occ", &self.occurences_amount)
.field("bad_idxs", &self.bad_indexes)
.finish()
} else {
write!(f, "(not in solution)")
}
}
}

View file

@ -93,7 +93,7 @@ pub trait WordList: Clone + std::fmt::Debug + Default + Sync + Display {
} }
buf buf
} }
fn get_words_matching(&self, pattern: String) -> WResult<Vec<WordData>> { fn get_words_matching(&self, pattern: &str) -> WResult<Vec<WordData>> {
let pattern = Regex::new(&pattern).map_err(WordlistError::from)?; let pattern = Regex::new(&pattern).map_err(WordlistError::from)?;
let hay = self.raw_wordlist(); let hay = self.raw_wordlist();
let keys = pattern.captures_iter(&hay); let keys = pattern.captures_iter(&hay);