commit 5d6a2d668a05074928d9faa6eb80bea31de5f3a3 Author: Nilstrieb Date: Tue Oct 19 21:48:11 2021 +0200 input and random diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cb63b19 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/target +Cargo.lock + +.idea +*.iml \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f653133 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "simple-std" +version = "0.1.0" +edition = "2018" +description = "A simple extension to the Rust standard library for exercises" +keywords = ["beginner", "help"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/README.md b/README.md new file mode 100644 index 0000000..c454caa --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# note: this is for practicing rust only, do not use this in actual production programs! + +simple-std is a little extension to the standard library, +providing additional helpers for getting input or creating random numbers. + +`std` is very useful, but it's lacking for little beginner exercises +(for a good reason), so I made this library to help with that. + +Every function from this library has a little section on why this function isn't in `std`, to help you understand +the reasoning behind including something in `std`. + +# Examples + +Greeting + +```rust +use simple_std::input; + +fn main() { + println!("What is your name?"); + let name = input(); + println!("Hello {}!", name) +} +``` + +Guessing game + +```rust +use simple_std::{input, random_int_range}; + +fn main() { + let number = random_int_range(0..100); + loop { + let input = input().parse::().expect("not a number"); + if input < number { + println!("Higher"); + } else if input > number { + println!("Lower"); + } else { + println!("Correct!"); + break; + } + } +} +``` \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..5c4df6e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,199 @@ +//! +//! # Example: guessing game +//! ``` +//! use simple_std::{input, random_int_range}; +//! +//! fn main() { +//! let number = random_int_range(0..100); +//! loop { +//!# // hack the input function for this to work in the doc test +//!# fn input() -> String { +//!# random_int_range(0..100).to_string() +//!# } +//! let input = input().parse::().expect("not a number"); +//! if input < number { +//! println!("Higher"); +//! } else if input > number { +//! println!("Lower"); +//! } else { +//! println!("Correct!"); +//! break; +//! } +//! } +//! } +//! ``` + +pub use io::input; +pub use random::{random_float, random_int_range}; + +mod io { + /// + /// Reads a single line of input, similar to Pythons `input` function + /// + /// # Example + /// ``` + /// use simple_std::input; + /// + /// println!("What is your name?"); + /// let name = input(); + /// println!("Hello {}!", name) + /// ``` + /// + /// # Why is this not in std? + /// + /// The implementation is fairly simple, just 2 lines, but it has a little complexity to it, + /// that's why there is the simplified version here. + pub fn input() -> String { + let mut buffer = String::new(); + std::io::stdin().read_line(&mut buffer).unwrap(); + buffer + } +} + +mod random { + use std::ops::Range; + + /// + /// Returns a random number from 0 to 1, like Javascript `Math.random` + /// + /// # Example + /// ``` + /// use simple_std::random_float; + /// + /// let number = random_float(); + /// + /// println!("Number between 0 and 1: {}", number); + /// + /// assert!(number < 1.0); + /// assert!(number >= 0.0); + /// ``` + /// + /// # Why is this not in std? + /// + /// Rust aims to be correct, that's why it's major random number library is cryptographically secure, + /// meaning it's randomness can't easily be guessed. And cryptographically secure random number generation + /// is a big task, that's why it has it's own crate. + pub fn random_float() -> f64 { + ((random_u64() >> 11) as f64) / ((1u64 << 53) as f64) + } + + /// + /// Returns an integer number contained in the range + /// + /// # Example + /// ``` + /// use simple_std::random_int_range; + /// + /// let number = random_int_range(0..100); + /// + /// println!("Number between 0 and 100: {}", number); + /// + /// assert!(number < 100); + /// assert!(number >= 0); + /// ``` + /// + /// # Why is this not in std? + /// + /// See [`random_float`] + /// + pub fn random_int_range(range: Range) -> i32 { + let difference = range.end - range.start; + range.start + ((random_u64() as i32).abs() % difference) + } + + /// generates a pseudo-random u32 + fn random_u64() -> u64 { + use std::sync::atomic::{AtomicU64, Ordering}; + + static STATE0: AtomicU64 = AtomicU64::new(0); + static STATE1: AtomicU64 = AtomicU64::new(0); + + if STATE0.load(Ordering::SeqCst) == 0 { + // more or less random initial state + STATE0.store((system_time_random()) as u64, Ordering::SeqCst); + STATE1.store((system_time_random()) as u64, Ordering::SeqCst); + } + + // use xorshift128+ because it's easy https://v8.dev/blog/math-random + + // not a bug + let mut s1 = STATE0.load(Ordering::SeqCst); + let s0 = STATE1.load(Ordering::SeqCst); + + STATE0.store(s0, Ordering::SeqCst); + + s1 ^= s1 << 23; + s1 ^= s1 >> 17; + s1 ^= s0; + s1 ^= s0 >> 26; + + STATE1.store(s1, Ordering::SeqCst); + + s0.wrapping_add(s1) + } + + fn system_time_random() -> u128 { + use std::time::SystemTime; + + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_micros() + ^ SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_nanos() + } + + #[cfg(test)] + mod test { + use crate::{random_float, random_int_range}; + use std::iter::repeat_with; + + #[test] + fn not_equal() { + repeat_with(random_float) + .take(100) + .collect::>() + .windows(2) + .for_each(|win| assert_ne!(win[0], win[1])); + } + + #[test] + fn between_0_1() { + assert!(repeat_with(random_float) + .take(100000) + .all(|n| n >= 0.0 && n < 1.0)) + } + + #[test] + fn distributed() { + assert!(repeat_with(random_float).take(100000).any(|n| n > 0.999)); + assert!(repeat_with(random_float).take(100000).any(|n| n < 0.001)); + } + + #[test] + fn range_in_range() { + [0..10, 5..15, 1000..1004, (-5)..5, (-10)..(-5)] + .iter() + .for_each(|range| { + assert!(repeat_with(|| random_int_range(range.clone())) + .take(10000) + .all(|n| n < range.end && n >= range.start)); + }) + } + + #[test] + fn distributed_range() { + [0..10, 5..15, 1000..1004, (-5)..5, (-10)..(-5)] + .iter() + .for_each(|range| { + range.clone().for_each(|expected| { + assert!(repeat_with(|| random_int_range(range.clone())) + .take(100000) + .any(|n| n == expected)); + }); + }) + } + } +}