jonhoo / roget Goto Github PK
View Code? Open in Web Editor NEWWordle Solver inspired by 3blue1brown
License: Apache License 2.0
Wordle Solver inspired by 3blue1brown
License: Apache License 2.0
Hey, I created a repository for wordle tests. You mentioned wanting to improve the tests for the matcher. Can also be useful for benchmarking purposes.
I tried to have it contain the minimum amount of test cases for every possible checking condition.
(I'm newly learning Rust and your streams are very helpful. I generated the files with Rust as well. Tears were involved also the borrow checker is a bully!)
Hello,
I would be happy to see a mode that allows to "play" the game, like 3b1b has show in his video.
It would give a first guess, then ask for the colors, repeating this until all the colors are green or more than 6 guesses have been given.
Asking the human the colors could be done in Correctness::compute()
, instead of computing it from a known answer. Maybe using Option<&str>
instead of &str
for the answer, in order to select wether to ask to the console or solve internally ?
First of all, I would like to thank Jon very much for his fantastic YouTube channel which allowed me to very quickly learn programming in Rust. This project, in particular, is the perfect kind of project for me in learning a programming language: Tackle a practical challenging problem, and optimize the code as much as possible.
Unfortunately, Jon became focused on optimizing his code without verifying that his logic is sound. The program becomes faster and faster and more and more wrong. First, he didn't realize that he forced his solver to only work in hard mode (every guess has to be a candidate of the right answer based on the correctness patterns received). Second, memoization can only be done for the specific history of guesses and the corresponding received correctness patterns since the first guess, but he thought that he could localize it. And from there it spiraled all the way down that it became impossible for me to track his reasoning throughout his live-coding video.
So, I decided to re-do roget from the bottom up, following only the mathematically sound ideas and the good programming patterns. It was first intended to be just a private project to learn Rust, but I decided to publish it on GitHub. You can find it here. It can be regarded as a fork of an early version of roget, but I typed it from scratch while following Jon's recorded video, making my own adjustments here and there.
Ironically for Jon, he missed an opportunity to gain a quadratic speedup by decoupling the for loop iterating through guess candidates and the for loop iterating through correctness patterns, observing that every guess-answer pair corresponds to exactly one correctness pattern. This dramatic improvement is wholly sound and doesn't change the guessing behavior at all. You can check out the implementation of this in rogerthat.
I was thinking about a possible branchless implementation for the function Correctness::compute
and I wrote this:
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Correctness {
Wrong = 0,
Misplaced = 1,
Correct = 2,
}
impl Correctness {
// Performs equality check
// Example:
// Input:
// answer = 01101 00101 01000 00010 01111
// guess = 01101 01111 01011 00010 01010
// result = 11111 00000 00000 11111 00000
fn check_for_eq(answer: u32, guess: u32) -> u32 {
let mut result = !(answer ^ guess);
result &= (result >> 1) & (result >> 2) & (result >> 3) & (result >> 4) & 0b00001_00001_00001_00001_00001u32;
result |= (result << 1) | (result << 2) | (result << 3) | (result << 4);
result
}
pub fn compute(answer: &str, guess: &str) -> [Correctness; 5] {
const TO_NUMBER: u8 = 'a' as u8 - 1;
let answer = answer.as_bytes();
let guess = guess.as_bytes();
// Assuming answer and guess are lowercase and use only alphabetic characters
// we can rappresent them using 5bits per letter (in this case 'a' = 1)
let mut answer = (answer[0] - TO_NUMBER) as u32 |
(((answer[1] - TO_NUMBER) as u32) << 5) |
(((answer[2] - TO_NUMBER) as u32) << 10) |
(((answer[3] - TO_NUMBER) as u32) << 15) |
(((answer[4] - TO_NUMBER) as u32) << 20);
let mut guess = (guess[0] - TO_NUMBER) as u32 |
(((guess[1] - TO_NUMBER) as u32) << 5) |
(((guess[2] - TO_NUMBER) as u32) << 10) |
(((guess[3] - TO_NUMBER) as u32) << 15) |
(((guess[4] - TO_NUMBER) as u32) << 20);
let green = Self::check_for_eq(answer, guess);
// Removing green letters:
// setting used letters from guess to 11111
guess |= green;
// setting used letters from answer to 00000
answer &= !green;
// To detect yellow letters we can shift guess 4 times and check the equality
let mut yellow = 0u32;
for _ in 0..4 {
guess = (guess >> 5) | (guess << 20);
let n = Self::check_for_eq(answer, guess);
// setting used letters from guess to 11111
guess |= n;
// setting used letters from answer to 00000
answer &= !n;
// yellow must be moved with guess to avoid overwrites
yellow = n | (yellow >> 5) | (yellow << 20);
}
// moving yellow to its original position
yellow = (yellow >> 5) | (yellow << 20);
let result = (green & 0b00010_00010_00010_00010_00010u32) | yellow & 0b00001_00001_00001_00001_00001;
// Safety: ((result >> (n * 5)) & 0b11111) as u8, where: 0 <= n < 5
// - Can only give 0, 1 or 2 as result
// - Enum is #[repr(u8)]
[
unsafe { std::mem::transmute((result & 0b11111) as u8) },
unsafe { std::mem::transmute(((result >> 5) & 0b11111) as u8) },
unsafe { std::mem::transmute(((result >> 10) & 0b11111) as u8) },
unsafe { std::mem::transmute(((result >> 15) & 0b11111) as u8) },
unsafe { std::mem::transmute(((result >> 20) & 0b11111) as u8) },
]
}
}
I tested it in release with this:
fn main() {
let b0 = benchmark(|| Correctness::compute("abcde", "fghij"));
let b1 = benchmark(|| jonhoo::Correctness::compute("abcde", "fghij"));
println!("{} ns/op", b0);
println!("{} ns/op", b1);
println!("b1/b0 = {}", b1 / b0);
}
// ns/op
fn benchmark<T, F: Fn() -> T>(f: F) -> f64 {
const ITERATIONS: usize = 1000000000;
let now = Instant::now();
for _ in 0..ITERATIONS {
execute_it_pls(f());
}
let el = now.elapsed().as_millis();
el as f64 / (ITERATIONS as f64 / 1000000.0)
}
fn execute_it_pls<T>(dummy: T) -> T {
let ptr = (&dummy) as *const _;
unsafe { asm!("/* {0} */", in(reg) ptr) }
dummy
}
And it seems to be about 33 times faster on my PC:
0.207 ns/op
6.902 ns/op
b1/b0 = 33.34299516908213
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.