GithubHelp home page GithubHelp logo

roget's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

roget's Issues

Test cases

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!)

Add a gaming mode to the binary

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 ?

The implementation is really unsound, because of incorrect assumptions.

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.

Correctness::compute branchless optimization

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

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.