Files
letter-words/Core.Tests/WordleUtilTests.cs
T

637 lines
26 KiB
C#

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using CsvHelper;
using CsvHelper.Configuration;
using MathNet.Numerics.Statistics;
using NUnit.Framework;
namespace Core.Tests
{
[TestFixture]
public class WordleUtilTests
{
private static readonly char[] Alphabet = Enumerable.Range(0, 26).Select(r => Convert.ToChar('A' + r)).ToArray();
[Test]
public void GameTest()
{
var actual = "NAIVE";
var dictionarySearcher = new DictionarySearcher(5);
var frequency = new LetterPositionFrequency(dictionarySearcher, 5);
dictionarySearcher.SortDictionary(frequency);
var wordleState = new WordleUtil.WordleState();
do
{
var results =
dictionarySearcher
.FindWords(wordleState.InWord, wordleState.NotInWord, 5)
.Where(r => Regex.IsMatch(r, WordleUtil.GetRegexRestrictions(wordleState.Criteria)));
//var guess = frequency.ScoreWords(results).OrderBy(f => f.Item2).Last().Item1;
var guess = results.First();
var response = WordleUtil.GetGuessResult(actual, guess);
wordleState.Update(guess, response);
Console.WriteLine(guess);
} while (!wordleState.IsSolved());
}
[Test, Explicit]
public void GameTest_NaiveBayes_WriteOutput()
{
var wordleUtil = new WordleUtil();
var dictionarySearcher = new DictionarySearcher(5);
var wordFrequency = new WordFrequency();
var actual = "NAIVE";
var steps = 0;
var wordleState = new WordleUtil.WordleState();
do
{
var constrainedWords =
dictionarySearcher
.FindWords(wordleState.InWord, wordleState.NotInWord, 5)
.Where(r => Regex.IsMatch(r, WordleUtil.GetRegexRestrictions(wordleState.Criteria)))
.OrderByDescending(w => wordFrequency.GetWordFreq(w))
.ToList();
if (constrainedWords.First() == "NAIVE")
{
constrainedWords.RemoveAt(0);
constrainedWords.Add("NAIVE");
}
if (constrainedWords.Contains("WAIVE"))
{
constrainedWords.Remove("WAIVE");
constrainedWords.Add("WAIVE");
}
var letterCountPercents = GetLetterCountPercents(constrainedWords);
SetWordleConstraints(letterCountPercents, wordleState);
var frequency = new LetterPositionFrequency(constrainedWords.ToList(), 5);
var sortedWords = frequency.SortWords(constrainedWords, wordleUtil);
var guess = sortedWords.First();
var highestCounts =
string.Join("",
Enumerable.Range(0, 5).Select(i => letterCountPercents.Last()[i].OrderByDescending(s => s.Count).First().Letter));
WriteLetterCountPercentsCsv(letterCountPercents, constrainedWords, @"o:\tmp\WordleFreq\" + $"{steps}-{guess}", new[] { highestCounts, guess });
var response = WordleUtil.GetGuessResult(actual, guess);
wordleState.Update(guess, response);
Console.WriteLine(guess);
steps++;
} while (!wordleState.IsSolved());
}
[Test, Explicit]
public void GameTest_NaiveBayes()
{
var wordleUtil = new WordleUtil();
var dictionarySearcher = new DictionarySearcher(5);
var wordFrequency = new WordFrequency();
var actual = "NAIVE";
var steps = 0;
var wordleState = new WordleUtil.WordleState();
do
{
var constrainedWords =
dictionarySearcher
.FindWords(wordleState.InWord, wordleState.NotInWord, 5)
.Where(r => Regex.IsMatch(r, WordleUtil.GetRegexRestrictions(wordleState.Criteria)))
.OrderByDescending(w => wordFrequency.GetWordFreq(w))
.ToList();
var frequency = new LetterPositionFrequency(constrainedWords.ToList(), 5);
var sortedWords = frequency.SortWords(constrainedWords, wordleUtil);
//sortedWords.Reverse();
var guess = sortedWords.First();
var response = WordleUtil.GetGuessResult(actual, guess);
wordleState.Update(guess, response);
Console.WriteLine(guess);
} while (!wordleState.IsSolved());
}
[Test, Explicit]
public void GameTest_NaiveBayes_Reverse()
{
var wordleUtil = new WordleUtil();
var dictionarySearcher = new DictionarySearcher(5);
var wordFrequency = new WordFrequency();
var actual = "NAIVE";
var steps = 0;
var wordleState = new WordleUtil.WordleState();
do
{
var constrainedWords =
dictionarySearcher
.FindWords(wordleState.InWord, wordleState.NotInWord, 5)
.Where(r => Regex.IsMatch(r, WordleUtil.GetRegexRestrictions(wordleState.Criteria)))
.OrderByDescending(w => wordFrequency.GetWordFreq(w))
.ToList();
if (constrainedWords.First() == "NAIVE")
{
constrainedWords.RemoveAt(0);
constrainedWords.Add("NAIVE");
}
if (constrainedWords.Contains("WAIVE"))
{
constrainedWords.Remove("WAIVE");
constrainedWords.Add("WAIVE");
}
var letterCountPercents = GetLetterCountPercents(constrainedWords);
SetWordleConstraints(letterCountPercents, wordleState);
var frequency = new LetterPositionFrequency(constrainedWords.ToList(), 5);
var sortedWords = frequency.SortWords(constrainedWords, wordleUtil);
sortedWords.Reverse();
var bannedWords = new string[] { "TAINT", "AALII", "LAIGH", "SAIDS", "SAINS" };
sortedWords = sortedWords.SkipWhile(bannedWords.Contains).ToList();
var guess = sortedWords.First();
var highestCounts =
string.Join("",
Enumerable.Range(0, 5).Select(i => letterCountPercents.Last()[i].OrderBy(s => s.Count).First().Letter));
WriteLetterCountPercentsCsv(letterCountPercents, constrainedWords, @"o:\tmp\WordleFreqReverse\" + $"{steps}-{guess}", new[] { highestCounts, guess });
var response = WordleUtil.GetGuessResult(actual, guess);
wordleState.Update(guess, response);
Console.WriteLine(guess);
steps++;
} while (!wordleState.IsSolved());
}
private static void SetWordleConstraints(List<LetterCountPercent[][]> letterCountPercents, WordleUtil.WordleState wordleState)
{
foreach (var letter in wordleState.NotInWord)
{
var k = letter - 'A';
foreach (var letterCountPercent in letterCountPercents)
{
for (int j = 0; j < 5; j++)
letterCountPercent[j][k].State = "N";
}
}
for (var j = 0; j < wordleState.Criteria.Length; j++)
{
var criteria = wordleState.Criteria[j];
if (criteria.Correct != null)
{
var k = criteria.Correct.Value - 'A';
foreach (var letterCountPercent in letterCountPercents)
{
letterCountPercent[j][k].State = "C";
}
}
if (!string.IsNullOrEmpty(criteria.OtherPosition))
{
foreach (var letter in criteria.OtherPosition)
{
var k = letter - 'A';
foreach (var letterCountPercent in letterCountPercents)
{
letterCountPercent[j][k].State = "P";
}
}
}
}
}
public class LetterCountPercent
{
public char Letter { get; }
public int Count { get; }
public float Percentage { get; }
/// <summary>
/// C: Correct, P: Present, N: Not Present, G: Guess, A: Active
/// </summary>
public string State { get; set; }
public LetterCountPercent(char letter, int count, double percentage, string state)
{
Letter = letter;
Count = count;
Percentage = (float)percentage;
State = state;
}
}
[Test]
public void NotInWordleList()
{
var dictionarySearcher = new DictionarySearcher(5);
var allFiveLeterWords = dictionarySearcher.FindWords("", "", 5);
var wordFrequency = new WordFrequency();
var wordleUtil = new WordleUtil();
var r =
from w in allFiveLeterWords
where !wordleUtil.Words.Contains(w)
let f = wordFrequency.GetWordFreq(w)
select Tuple.Create(w,f);
foreach (var s in r.OrderByDescending(t => t.Item2).Take(20))
{
Console.WriteLine(s);
}
}
[Test, Explicit]
public void LetterFrequenceStatistics_NoConstraints()
{
var frequency = new WordFrequency();
var wordleUtil = new WordleUtil();
var words = wordleUtil.Words.OrderByDescending(w => frequency.GetWordFreq(w)).ToList();
//var dictionarySearcher = new DictionarySearcher(5);
//var words = dictionarySearcher.GetWords().OrderByDescending(w => frequency.GetWordFreq(w)).ToList();
void MoveToFirst(string w)
{
words.Remove(w);
words.Insert(0, w);
}
MoveToFirst("LATER");
MoveToFirst("AFTER");
MoveToFirst("FIRST");
var results = GetLetterCountPercents(words);
var highestCounts =
string.Join("",
Enumerable.Range(0, 5).Select(i => results.Last()[i].OrderByDescending(s => s.Count).First().Letter));
WriteLetterCountPercentsCsv(results, words, @"o:\tmp\WordleFreq\NoConstraint", new []{ highestCounts, "SORES", "CARES"});
}
[Test, Explicit]
public void LetterFrequenceStatistics_NoConstraints_Reverse()
{
var frequency = new WordFrequency();
var wordleUtil = new WordleUtil();
var words = wordleUtil.Words.OrderByDescending(w => frequency.GetWordFreq(w)).ToList();
//var dictionarySearcher = new DictionarySearcher(5);
//var words = dictionarySearcher.GetWords().OrderByDescending(w => frequency.GetWordFreq(w)).ToList();
void MoveToFirst(string w)
{
words.Remove(w);
words.Insert(0, w);
}
MoveToFirst("LATER");
MoveToFirst("AFTER");
MoveToFirst("FIRST");
var results = GetLetterCountPercents(words);
var highestCounts =
string.Join("",
Enumerable.Range(0, 5).Select(i => results.Last()[i].OrderByDescending(s => s.Count).Last().Letter));
WriteLetterCountPercentsCsv(results, words, @"o:\tmp\WordleFreq\NoConstraint", new[] { highestCounts, "SORES", "CARES" });
}
private static List<LetterCountPercent[][]> GetLetterCountPercents(List<string> words)
{
var freqList = new Dictionary<char, int>[5];
for (var i = 0; i < 5; i++)
freqList[i] = Alphabet.ToDictionary(c => c, j => 0);
var wordCount = 0;
var results = new List<LetterCountPercent[][]> ();
foreach (var word in words)
{
wordCount++;
for (var wordIndex = 0; wordIndex < word.Length; wordIndex++)
{
var c = word[wordIndex];
freqList[wordIndex][c]++;
}
// get percentage
var freqPercents =
freqList.Select(
f => (
from cFreqs in f
select Tuple.Create(cFreqs.Key, cFreqs.Value * 1.0 / wordCount))
.ToDictionary(cp => cp.Item1, cp => cp.Item2))
.ToArray();
// normalize
var maxPercent = freqPercents.Max(fp => fp.Max(p => p.Value));
foreach (var freqPercent in freqPercents)
foreach (var c in freqPercent.Keys.ToArray())
freqPercent[c] /= maxPercent;
var result =
(from letter in Enumerable.Range(0, 5)
let item =
(from freq in freqList[letter]
from freqPercent in freqPercents[letter]
where freq.Key == freqPercent.Key
let state = word[letter] == freq.Key ? "A" : null
select new LetterCountPercent(freq.Key, freq.Value, freqPercent.Value, state)).ToArray()
select item).ToArray();
results.Add(result);
}
return results;
}
private static void WriteLetterCountPercentsCsv(IReadOnlyList<LetterCountPercent[][]> results, IReadOnlyList<string> words, string folder, IList<string> g = null)
{
// hack:
IList<string> guesses = null;
if (g != null)
{
guesses = new List<string>(g);
guesses.Add(guesses.Last());
}
try
{
if (Directory.Exists(folder))
Directory.Delete(folder, true);
}
catch (Exception e)
{
Console.WriteLine(e);
}
Directory.CreateDirectory(folder);
using (var writer = new StreamWriter(Path.Combine(folder, "freq_words.csv")))
{
writer.WriteLine("word");
writer.WriteLine(" ");
writer.WriteLine(" ");
for (int i = 0; i < words.Count; i++)
{
writer.WriteLine(words[i]);
}
writer.WriteLine(" ");
writer.WriteLine(" ");
for (int i = 0; i < (guesses?.Count ?? 0); i++)
{
writer.WriteLine(guesses[i]);
}
}
for (int j = 0; j < 5; j++)
{
using (var writer = new StreamWriter(Path.Combine(folder, "freq_letterCounts_" + j + ".csv")))
{
writer.WriteLine(string.Join(",", Alphabet));
writer.WriteLine(string.Join(",", results[0][j].Select(lcp => 0)));
//writer.WriteLine(string.Join(",", results[0][j].Select(lcp => 0)));
for (var index = 0; index < results.Count + 3 + (guesses?.Count ?? 0); index++)
{
var indexX = index < results.Count ? index : results.Count - 1;
writer.WriteLine(string.Join(",", results[indexX][j].Select(lcp => lcp.Count)));
}
}
}
for (int j = 0; j < 5; j++)
{
using (var writer = new StreamWriter(Path.Combine(folder, "freq_letterPercents_" + j + ".csv")))
{
writer.WriteLine(string.Join(",", Alphabet));
writer.WriteLine(string.Join(",", results[0][j].Select(lcp => 0)));
writer.WriteLine(string.Join(",", results[0][j].Select(lcp => 0)));
for (var index = 0; index < results.Count + 2 + (guesses?.Count ?? 0); index++)
{
var indexX = index < results.Count ? index : results.Count - 1;
writer.WriteLine(string.Join(",", results[indexX][j].Select(lcp => lcp.Percentage)));
}
}
}
for (int j = 0; j < 5; j++)
{
using (var writer = new StreamWriter(Path.Combine(folder, "freq_letterState_" + j + ".csv")))
{
writer.WriteLine(string.Join(",", Alphabet));
writer.WriteLine(string.Join(",", results[0][j].Select(lcp => lcp.State == "A" || string.IsNullOrEmpty(lcp.State) ? " " : lcp.State)));
writer.WriteLine(string.Join(",", results[0][j].Select(lcp => lcp.State == "A" || string.IsNullOrEmpty(lcp.State) ? " " : lcp.State)));
for (var index = 0; index < results.Count; index++)
{
writer.WriteLine(string.Join(",", results[index][j].Select(lcp => string.IsNullOrEmpty(lcp.State) ? " " : lcp.State)));
}
writer.WriteLine(string.Join(",", results[results.Count - 1][j].Select(lcp => lcp.State == "A" || string.IsNullOrEmpty(lcp.State) ? " " : lcp.State)));
writer.WriteLine(string.Join(",", results[results.Count - 1][j].Select(lcp => lcp.State == "A" || string.IsNullOrEmpty(lcp.State) ? " " : lcp.State)));
for (int e = 0; e < (guesses?.Count ?? 0); e++)
{
var letter = guesses[e][j];
writer.WriteLine(string.Join(",", results[results.Count - 1][j].Select(lcp => lcp.Letter == letter ? "G" : lcp.State == "A" || string.IsNullOrEmpty(lcp.State) ? " " : lcp.State)));
}
}
}
}
[Test]
public void NotInDictionary()
{
var dictionarySearcher = new DictionarySearcher(5);
var allFiveLeterWords = dictionarySearcher.FindWords("", "", 5);
var wordFrequency = new WordFrequency();
var wordleUtil = new WordleUtil();
var r =
from w in wordleUtil.Words
where !allFiveLeterWords.Contains(w)
let f = wordFrequency.GetWordFreq(w)
select Tuple.Create(w, f);
foreach (var s in r.OrderByDescending(t => t.Item2).Take(20))
{
Console.WriteLine(s);
}
}
[Test, Explicit]
public void WordleStats()
{
var actual = "GROUP";
var dictionarySearcher = new DictionarySearcher(5);
var allFiveLeterWords = dictionarySearcher.FindWords("", "");
var wordleUtil = new WordleUtil();
var frequency = new LetterPositionFrequency(wordleUtil, 5);
dictionarySearcher.SortDictionary(frequency);
// Console.WriteLine(dictionarySearcher.FindWords("", "", 5).Count());
var solutionSteps = new List<double>();
Parallel.ForEach(allFiveLeterWords, nextWord =>
{
var guess = nextWord;
var response = WordleUtil.GetGuessResult(actual, guess);
var wordleState = new WordleUtil.WordleState();
wordleState.Update(guess, response);
//Console.WriteLine("----");
//Console.WriteLine(guess);
var steps = 1;
while (!wordleState.IsSolved())
{
var results =
dictionarySearcher
.FindWords(wordleState.InWord, wordleState.NotInWord, 5)
.Where(r => Regex.IsMatch(r, WordleUtil.GetRegexRestrictions(wordleState.Criteria)));
guess = results.First();
//guess = frequency.ScoreWords(results).OrderBy(f => f.Item2).Last().Item1;
response = WordleUtil.GetGuessResult(actual, guess);
wordleState.Update(guess, response);
//Console.WriteLine(guess);
steps++;
}
solutionSteps.Add(steps);
});
//Console.WriteLine(string.Join(",", solutionSteps));
Console.WriteLine($"Mean: {solutionSteps.Mean()}, StdDev: {solutionSteps.StandardDeviation()}");
//Console.WriteLine(solutionSteps.Sum() * 1d / solutionSteps.Count );
}
[Test]
public void RepeatedLettersStatistics()
{
var wordleUtil = new WordleUtil();
var re =
from w in wordleUtil.Words
let repeats = WordleUtil.RepeatedLettersCount(w)
group repeats by repeats
into g
select g;
var max = re.Max(r => r.Count());
Console.WriteLine(string.Join(", ", re.Select(r => r.Key + ": " + r.Count() + $" {(r.Count() * 1.0) / max}")));
Console.WriteLine(string.Join(", ", wordleUtil.Words.Where(w => WordleUtil.RepeatedLettersCount(w) == 3)));
Console.WriteLine(string.Join(", ", wordleUtil.Words.Where(w => WordleUtil.RepeatedLettersCount(w) == 2)));
Console.WriteLine(string.Join(", ", wordleUtil.Words.Where(w => WordleUtil.RepeatedLettersCount(w) == 1)));
}
[Test]
public void SolveAllWordleWords_SortedDictionary()
{
var dictionarySearcher = new DictionarySearcher(5);
var wordleUtil = new WordleUtil();
var frequency = new LetterPositionFrequency(wordleUtil, 5);
dictionarySearcher.SortDictionary(frequency);
var solutionSteps = new List<double>();
Parallel.ForEach(wordleUtil.Words, actual =>
{
var steps = 0;
var wordleState = new WordleUtil.WordleState();
do
{
var results =
dictionarySearcher
.FindWords(wordleState.InWord, wordleState.NotInWord, 5)
.Where(r => Regex.IsMatch(r, WordleUtil.GetRegexRestrictions(wordleState.Criteria)));
if (steps <= 1)
results = results.SkipWhile(WordleUtil.RepeatedLetters);
var guess = results.First();
var response = WordleUtil.GetGuessResult(actual, guess);
wordleState.Update(guess, response);
//Console.WriteLine(guess);
steps++;
} while (!wordleState.IsSolved());
solutionSteps.Add(steps);
});
Console.WriteLine($"Mean: {solutionSteps.Mean()}, StdDev: {solutionSteps.StandardDeviation()}");
}
[Test]
public void SolveAllWordleWords_NaiveBayes()
{
var dictionarySearcher = new DictionarySearcher(5);
var wordleUtil = new WordleUtil();
var solutionSteps = new List<double>();
Parallel.ForEach(wordleUtil.Words, actual =>
{
var steps = 0;
var wordleState = new WordleUtil.WordleState();
do
{
var results =
dictionarySearcher
.FindWords(wordleState.InWord, wordleState.NotInWord, 5)
.Where(r => Regex.IsMatch(r, WordleUtil.GetRegexRestrictions(wordleState.Criteria)))
.ToList();
var frequency = new LetterPositionFrequency(results, 5);
results = frequency.SortWords(results, wordleUtil);
var guess = results.First();
var response = WordleUtil.GetGuessResult(actual, guess);
wordleState.Update(guess, response);
//Console.WriteLine(guess);
steps++;
} while (!wordleState.IsSolved());
solutionSteps.Add(steps);
});
Console.WriteLine($"Mean: {solutionSteps.Mean()}, StdDev: {solutionSteps.StandardDeviation()}");
}
}
}