generated from PlexSheep/rs-base
Compare commits
No commits in common. "c140264a0cf485f4d64e8e495a8c4bc2b82584c7" and "2fcdc062c8e557fe3582ed70ff6877b94e6294e9" have entirely different histories.
c140264a0c
...
2fcdc062c8
11 changed files with 58 additions and 294 deletions
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "wordle-analyzer"
|
||||
version = "0.1.0-alpha.0"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
authors = ["Christoph J. Scherr <software@cscherr.de>"]
|
||||
|
@ -60,7 +60,4 @@ path = "src/bin/bench/cli.rs"
|
|||
required-features = ["solve", "cli", "bench", "builtin"]
|
||||
|
||||
[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"] }
|
||||
|
|
|
@ -72,7 +72,7 @@ fn main() -> anyhow::Result<()> {
|
|||
let bench = BuiltinBenchmark::build(&wl, solver, builder, cli.threads)?;
|
||||
trace!("{bench:#?}");
|
||||
|
||||
bench.start(cli.n, &bench.builder())?;
|
||||
bench.start(50, &bench.builder())?;
|
||||
|
||||
loop {
|
||||
sleep(std::time::Duration::from_secs(1));
|
||||
|
|
|
@ -61,7 +61,7 @@ fn main() -> anyhow::Result<()> {
|
|||
let mut guess: Word;
|
||||
loop {
|
||||
guess = get_word(&cli, game.step())?;
|
||||
response = match game.guess(&guess, None) {
|
||||
response = match game.guess(guess, None) {
|
||||
Ok(r) => r,
|
||||
Err(err) => match err {
|
||||
GameError::GuessHasWrongLength(len) => {
|
||||
|
|
|
@ -172,7 +172,7 @@ fn help_guess_interactive(cli: Cli) -> anyhow::Result<()> {
|
|||
eprintln!("{}", style(best_guess.unwrap_err()).red().bold());
|
||||
continue;
|
||||
}
|
||||
trace!("game state: {game:?}");
|
||||
debug!("game state: {game:?}");
|
||||
println!("best guess: {}", best_guess.unwrap());
|
||||
}
|
||||
ReplCommand::Guess {
|
||||
|
@ -181,14 +181,14 @@ fn help_guess_interactive(cli: Cli) -> anyhow::Result<()> {
|
|||
} => {
|
||||
let evaluation_converted: Evaluation =
|
||||
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:?}");
|
||||
if guess.is_err() {
|
||||
eprintln!("{}", style(guess.unwrap_err()).red().bold());
|
||||
continue;
|
||||
}
|
||||
println!("{}", guess.unwrap());
|
||||
trace!("game state: {game:#?}");
|
||||
debug!("game state: {game:#?}");
|
||||
}
|
||||
ReplCommand::New => game = builder.build()?,
|
||||
ReplCommand::Undo { n } => game.undo(n)?,
|
||||
|
@ -218,7 +218,7 @@ fn play_native_non_interactive(cli: Cli) -> anyhow::Result<()> {
|
|||
"eng" => BuiltinWList::english(cli.length),
|
||||
_ => BuiltinWList::load(&cli.wordlist, cli.length)?,
|
||||
};
|
||||
trace!("wordlist: {wl}");
|
||||
debug!("wordlist: {wl}");
|
||||
let mut builder = game::Game::builder(&wl)
|
||||
.length(cli.length)
|
||||
.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 mut game = builder.build()?;
|
||||
|
||||
trace!("{game:#?}");
|
||||
debug!("{game:#?}");
|
||||
|
||||
let mut response: GuessResponse;
|
||||
let mut _guess: Word;
|
||||
loop {
|
||||
response = solver.make_a_move(&mut game)?;
|
||||
trace!("game state: {game:#?}");
|
||||
debug!("game state: {game:#?}");
|
||||
println!("{}. guess: {response}", game.step() - 1);
|
||||
|
||||
if response.finished() {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use thiserror::Error;
|
||||
|
||||
use crate::bench::report::Report;
|
||||
use crate::wlist::word::{Word, WordData};
|
||||
use crate::wlist::word::Word;
|
||||
|
||||
pub type WResult<T> = std::result::Result<T, Error>;
|
||||
pub type GameResult<T> = std::result::Result<T, GameError>;
|
||||
|
@ -62,8 +62,8 @@ pub enum BenchError {
|
|||
|
||||
#[derive(Debug, Clone, Error)]
|
||||
pub enum SolverError {
|
||||
#[error("Wordlist has no matches for the gamestate (solution: {0:?})")]
|
||||
NoMatches(Option<WordData>),
|
||||
#[error("Wordlist has no matches for the gamestate")]
|
||||
NoMatches,
|
||||
#[error("Unknown builtin solver")]
|
||||
UnknownBuiltinSolver,
|
||||
}
|
||||
|
|
|
@ -68,18 +68,6 @@ impl Evaluation {
|
|||
}
|
||||
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 {
|
||||
|
@ -99,12 +87,6 @@ impl From<Vec<EvaluationUnit>> for Evaluation {
|
|||
|
||||
impl From<Evaluation> for Word {
|
||||
fn from(value: Evaluation) -> Self {
|
||||
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>())
|
||||
Word::from(value.inner.into_iter().map(|v| v.0).collect::<String>())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,16 +49,6 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
|
|||
/// # 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(
|
||||
length: usize,
|
||||
precompute: bool,
|
||||
|
@ -101,7 +91,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
|
||||
/// 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 {
|
||||
return Err(GameError::GuessHasWrongLength(guess.len()));
|
||||
}
|
||||
|
@ -109,7 +99,7 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
|
|||
return Err(GameError::TryingToPlayAFinishedGame);
|
||||
}
|
||||
if self.wordlist.get_word(&guess).is_none() {
|
||||
return Err(GameError::WordNotInWordlist(guess.to_string()));
|
||||
return Err(GameError::WordNotInWordlist(guess));
|
||||
}
|
||||
self.step += 1;
|
||||
|
||||
|
@ -125,22 +115,20 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
|
|||
Ok(response)
|
||||
}
|
||||
|
||||
/// Generates an [Evaluation] for a given solution and guess.
|
||||
pub(crate) fn evaluate(mut solution: WordData, guess: &Word) -> Evaluation {
|
||||
pub fn evaluate(mut solution: WordData, guess: &Word) -> Evaluation {
|
||||
let mut evaluation = Vec::new();
|
||||
let mut status: Status;
|
||||
let mut buf = solution.0.clone();
|
||||
for ((idx, c_guess), c_sol) in guess.chars().enumerate().zip(solution.0.chars()) {
|
||||
if c_guess == c_sol {
|
||||
for (idx, c) in guess.chars().enumerate() {
|
||||
if solution.0.chars().nth(idx) == Some(c) {
|
||||
status = Status::Matched;
|
||||
buf.replace_range(idx..idx + 1, "_");
|
||||
} else if buf.contains(c_guess) {
|
||||
solution.0.replace_range(idx..idx + 1, "_");
|
||||
} else if solution.0.contains(c) {
|
||||
status = Status::Exists;
|
||||
buf = buf.replacen(c_guess, "_", 1);
|
||||
solution.0 = solution.0.replacen(c, "_", 1);
|
||||
} else {
|
||||
status = Status::None
|
||||
}
|
||||
evaluation.push((c_guess, status));
|
||||
evaluation.push((c, status));
|
||||
}
|
||||
evaluation.into()
|
||||
}
|
||||
|
@ -152,25 +140,18 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// get how many characters the words have for this game
|
||||
pub fn length(&self) -> usize {
|
||||
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> {
|
||||
self.solution.as_ref()
|
||||
}
|
||||
|
||||
/// get how many guesses have been made already
|
||||
pub fn step(&self) -> usize {
|
||||
self.step
|
||||
}
|
||||
|
||||
/// true if the game has finished and no more guesses can be made
|
||||
pub fn finished(&self) -> bool {
|
||||
if self.responses().is_empty() {
|
||||
return false;
|
||||
|
@ -178,35 +159,27 @@ impl<'wl, WL: WordList> Game<'wl, WL> {
|
|||
self.responses().last().unwrap().finished()
|
||||
}
|
||||
|
||||
/// true if the game has finished and the solution was found
|
||||
pub fn won(&self) -> bool {
|
||||
if !self.finished() || self.responses().is_empty() {
|
||||
if self.responses().is_empty() {
|
||||
return false;
|
||||
}
|
||||
self.responses().last().unwrap().won()
|
||||
}
|
||||
|
||||
/// get how many tries the player has
|
||||
pub fn max_steps(&self) -> usize {
|
||||
self.max_steps
|
||||
}
|
||||
|
||||
/// get the responses that were already made
|
||||
pub fn responses(&self) -> &Vec<GuessResponse> {
|
||||
&self.responses
|
||||
}
|
||||
|
||||
/// get the most recent response
|
||||
pub fn last_response(&self) -> Option<&GuessResponse> {
|
||||
self.responses().last()
|
||||
}
|
||||
|
||||
/// get the [WordList] for this game
|
||||
pub fn wordlist(&self) -> &WL {
|
||||
self.wordlist
|
||||
}
|
||||
|
||||
/// get the [Words](Word) that have already been tried
|
||||
pub(crate) fn made_guesses(&self) -> Vec<&Word> {
|
||||
self.responses.iter().map(|r| r.guess()).collect()
|
||||
}
|
||||
|
@ -324,14 +297,6 @@ impl<'wl, WL: WordList> GameBuilder<'wl, WL> {
|
|||
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
|
||||
///
|
||||
/// If this is [Some], then the solution generated by
|
||||
|
|
|
@ -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.
|
||||
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].
|
||||
///
|
||||
|
|
|
@ -1,17 +1,11 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use libpt::log::{debug, error, info, trace};
|
||||
use libpt::log::{info, trace};
|
||||
|
||||
use crate::error::{SolverError, WResult};
|
||||
use crate::game::evaluation::{Evaluation, EvaluationUnit};
|
||||
use crate::wlist::word::{Word, WordData};
|
||||
use crate::wlist::WordList;
|
||||
|
||||
use super::{AnyBuiltinSolver, Solver, Status};
|
||||
|
||||
mod states;
|
||||
use states::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NaiveSolver<'wl, WL> {
|
||||
wl: &'wl WL,
|
||||
|
@ -32,82 +26,51 @@ 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
|
||||
/// word, but don't know the position of.
|
||||
fn guess_for(&self, game: &crate::game::Game<WL>) -> WResult<Word> {
|
||||
let mut pattern: String = ".".repeat(game.length());
|
||||
// indexes we tried for that char and the number of occurences
|
||||
let mut state: SolverState = SolverState::new();
|
||||
let responses = game.responses().iter().enumerate();
|
||||
for (_idx, response) in responses {
|
||||
let mut already_found_amounts: HashMap<char, usize> = HashMap::new();
|
||||
let evaluation: &Evaluation = response.evaluation();
|
||||
for (idx, p) in evaluation.clone().into_iter().enumerate() {
|
||||
match p.1 {
|
||||
Status::Matched => {
|
||||
// HACK: hardcoded length
|
||||
let mut pattern: String = String::from(".....");
|
||||
let mut other_chars: Vec<char> = Vec::new();
|
||||
let response = game.last_response();
|
||||
trace!(
|
||||
"guessing best guess for last response: {response:#?}\n{:#?}",
|
||||
response.map(|a| a.evaluation())
|
||||
);
|
||||
if response.is_some() {
|
||||
for (idx, p) in response
|
||||
.unwrap()
|
||||
.evaluation()
|
||||
.clone()
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
if p.1 == Status::Matched {
|
||||
pattern.replace_range(idx..idx + 1, &p.0.to_string());
|
||||
|
||||
state
|
||||
.char_map_mut()
|
||||
.entry(p.0)
|
||||
.or_insert(CharInfo::new(game.length()))
|
||||
.found_at(idx);
|
||||
*already_found_amounts.entry(p.0).or_default() += 1;
|
||||
}
|
||||
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:?}");
|
||||
} else if p.1 == Status::Exists {
|
||||
other_chars.push(p.0)
|
||||
}
|
||||
}
|
||||
|
||||
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)?;
|
||||
}
|
||||
trace!("other chars: {:?}", other_chars);
|
||||
let mut matches: Vec<WordData> = game.wordlist().get_words_matching(pattern)?;
|
||||
if matches.is_empty() {
|
||||
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())
|
||||
return Err(SolverError::NoMatches.into());
|
||||
}
|
||||
matches = matches
|
||||
.iter()
|
||||
// only words that have not been guessed yet
|
||||
.filter(|p| !game.made_guesses().contains(&&p.0))
|
||||
.filter(|solution_candidate| {
|
||||
if !game.responses().is_empty()
|
||||
&& !state.has_all_known_contained(&solution_candidate.0)
|
||||
{
|
||||
trace!("known cont:{:#?}", state.get_all_known_contained());
|
||||
return false;
|
||||
// only words that contain the letters we found earlier (that were not matched)
|
||||
.filter(|p| {
|
||||
// TODO: don't repeat unmatched contained chars on the same position twice #2
|
||||
let mut fits = true;
|
||||
for c in other_chars.iter() {
|
||||
fits &= p.0.contains(*c);
|
||||
}
|
||||
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
|
||||
fits
|
||||
})
|
||||
.map(|v| v.to_owned())
|
||||
.collect();
|
||||
if matches.is_empty() {
|
||||
return Err(SolverError::NoMatches(game.solution().cloned()).into());
|
||||
return Err(SolverError::NoMatches.into());
|
||||
}
|
||||
Ok(matches[0].0.to_owned())
|
||||
}
|
||||
|
|
|
@ -1,143 +0,0 @@
|
|||
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)")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -93,7 +93,7 @@ pub trait WordList: Clone + std::fmt::Debug + Default + Sync + Display {
|
|||
}
|
||||
buf
|
||||
}
|
||||
fn get_words_matching(&self, pattern: &str) -> WResult<Vec<WordData>> {
|
||||
fn get_words_matching(&self, pattern: String) -> WResult<Vec<WordData>> {
|
||||
let pattern = Regex::new(&pattern).map_err(WordlistError::from)?;
|
||||
let hay = self.raw_wordlist();
|
||||
let keys = pattern.captures_iter(&hay);
|
||||
|
|
Loading…
Add table
Reference in a new issue