diff --git a/.vscode/settings.json b/.vscode/settings.json index fbd566b4..2d036dc7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,6 +4,7 @@ "ABCDEFGHIJKLMNOP", "binutils", "certutil", + "congruential", "czvf", "datetime", "dylib", @@ -24,6 +25,7 @@ "rustup", "Seedable", "softprops", + "struct", "Swatinem", "uuid", "Zellers" diff --git a/Cargo.toml b/Cargo.toml index 4b0979db..4cb9af3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ readme = "README.md" repository = "https://github.com/sebastienrousseau/mini-functions.git" resolver = "2" # Enables the new Cargo resolution engine rust-version = "1.57.0" -version = "0.0.4" +version = "0.0.5" [badges] maintenance = { status = "actively-developed" } @@ -32,9 +32,6 @@ qrcode = "0.12.0" time = "0.3.17" uuid = { version = "1.2.2", features = ["v3", "v4", "v5"] } -[build-dependencies] -cross = "0.2.4" - [lib] name = "mini_functions" path = "src/lib.rs" diff --git a/README.md b/README.md index 3336278d..2644cb23 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ Add the following to your `Cargo.toml` file: ```toml [dependencies] -mini-functions = "0.0.4" +mini-functions = "0.0.5" ``` ## Usage šŸ“– @@ -91,9 +91,16 @@ of data. The `mini-functions` library consists of the following functions: -- [Date and time functions](#date-and-time-functions) -- [Log functions](#log-functions) -- [UUID functions](#uuid-functions) +- The **[Date and time functions](#date-and-time-functions)** are used + to retrieve and manipulate information about dates and times. +- The **[Log functions](#log-functions)** are used to log messages to + the console. +- The **[QRCode functions](#qrcode-functions)** are used to generate + QRCode images and data. +- The **[Random number functions](#random-number-functions)** are used + to generate random numbers in a variety of sizes and formats. +- The **[UUID functions](#uuid-functions)** are used to generate UUIDs + (Universally Unique Identifiers). The following tables provide a brief description of each function in the `mini-functions` library. @@ -124,15 +131,6 @@ The following tables provide a brief description of each function in the | `Log::log()` | `log.rs` | `fn log()` | Logs a message to the console.| | `Log::new()` | `log.rs` | `fn new()` | Creates a new log instance. | -### UUID functions - -| Function | Include File | Function Prototype | Description | -| -------- | ------------ | ------------------ | ----------- | -| `UUID::new()` | `uuid.rs` | `fn new()` | Creates a new UUID instance based on the version specified. (v3, v4, v5) | -| `UUID::uuid_v3()` | `uuid.rs` | `fn uuid_v3()` | Creates a new UUID v3 instance. | -| `UUID::uuid_v4()` | `uuid.rs` | `fn uuid_v4()` | Creates a new UUID v4 instance. | -| `UUID::uuid_v5()` | `uuid.rs` | `fn uuid_v5()` | Creates a new UUID v5 instance. | - ### QRCode functions | Function | Include File | Function Prototype | Description | @@ -146,6 +144,27 @@ The following tables provide a brief description of each function in the | `QRCode::to_qrcode()` | `qrcode.rs` | `fn to_qrcode()` | Converts the QRCode instance to a QRCode image. | | `QRCode::to_svg()` | `qrcode.rs` | `fn to_svg()` | Converts the QRCode instance to a SVG image. | +### Random number functions + +| Function | Include File | Function Prototype | Description | +| -------- | ------------ | ------------------ | ----------- | +| `Random::bytes()` | `random.rs` | `fn bytes()` | Generates a vector of random bytes of a given length. | +| `Random::default()` | `random.rs` | `fn default()` | Creates a new `Random` struct with a default seed. | +| `Random::float()` | `random.rs` | `fn float()` | Generates a random floating point number between 0 and 1. | +| `Random::int()` | `random.rs` | `fn int()` | Generates a random integer between a minimum and maximum value. | +| `Random::new()` | `random.rs` | `fn new()` | Creates a new `Random` struct with a seed based on the current system time. | +| `Random::pseudo()` | `random.rs` | `fn pseudo()` | Generates a pseudo-random number by XORing the last 31 random numbers together. | +| `Random::random()` | `random.rs` | `fn random()` | Generates a random number using the linear congruential generator algorithm. The multiplier for the algorithm is the golden ratio. | + +### UUID functions + +| Function | Include File | Function Prototype | Description | +| -------- | ------------ | ------------------ | ----------- | +| `UUID::new()` | `uuid.rs` | `fn new()` | Creates a new UUID instance based on the version specified. (v3, v4, v5) | +| `UUID::uuid_v3()` | `uuid.rs` | `fn uuid_v3()` | Creates a new UUID v3 instance. | +| `UUID::uuid_v4()` | `uuid.rs` | `fn uuid_v4()` | Creates a new UUID v4 instance. | +| `UUID::uuid_v5()` | `uuid.rs` | `fn uuid_v5()` | Creates a new UUID v5 instance. | + ![divider][divider] ## Semantic Versioning Policy šŸš„ @@ -195,6 +214,6 @@ for their help and support. [crates-badge]: https://img.shields.io/crates/v/mini-functions.svg?style=for-the-badge 'Crates.io' [divider]: https://raw.githubusercontent.com/sebastienrousseau/vault/main/assets/elements/divider.svg "divider" [docs-badge]: https://img.shields.io/docsrs/mini-functions.svg?style=for-the-badge 'Docs.rs' -[libs-badge]: https://img.shields.io/badge/lib.rs-0.0.1-orange.svg?style=for-the-badge 'Lib.rs' +[libs-badge]: https://img.shields.io/badge/lib.rs-v0.0.5-orange.svg?style=for-the-badge 'Lib.rs' [license-badge]: https://img.shields.io/crates/l/mini-functions.svg?style=for-the-badge 'License' [mwl]: https://raw.githubusercontent.com/sebastienrousseau/vault/main/assets/shields/made-with-love.svg "Made With Love" diff --git a/examples/random.rs b/examples/random.rs new file mode 100644 index 00000000..8c8c2611 --- /dev/null +++ b/examples/random.rs @@ -0,0 +1,87 @@ +use mini_functions::random::Random; + +// Defining an example for a simple `A Three Card Draw Poker Game` that +// shuffles a deck of cards and allows the user to draw cards from the +// top of the deck, displaying the card name and suit as a string. + +// A struct that represents a deck of playing cards +struct Deck { + // A vector of cards + cards: Vec, + // A random number generator + rng: Random, +} + +// Implementation of the `Deck` struct +impl Deck { + // Creates a new `Deck` struct with a shuffled deck of cards + fn new() -> Self { + let mut deck = Self { + cards: (0..52).collect(), + rng: Random::new(), + }; + + // Shuffle the deck using the Fisher-Yates algorithm + for i in (1..52).rev() { + let j = deck.rng.random() as usize % (i + 1); + deck.cards.swap(i, j); + } + deck + } + + // Draws a card from the top of the deck + fn draw(&mut self) -> Option { + let card = self.cards.pop(); + if let Some(card) = card { + let suits = ["Spades (ā™ )", "Clubs (ā™£)", "Hearts (ā™„)", "Diamonds (ā™¦)"]; + let ranks = [ + "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King", "Ace", + ]; + let suit = suits[card / 13]; + let rank = ranks[card % 13]; + Some(format!("{} of {}", rank, suit)) + } else { + None + } + } +} + +fn main() { + // Create a new random number generator + let mut rng = Random::new(); + println!("šŸ¦€ Random::new(): āœ… {}", rng); + + let default = Random::default(); + println!("šŸ¦€ Random::default(): āœ… {}", default); + + let random = rng.random(); + println!("šŸ¦€ Random::random(): āœ… {}", random); + + let pseudo = rng.pseudo(); + println!("šŸ¦€ Random::pseudo(): āœ… {}", pseudo); + + let bytes = rng.bytes(10); + println!("šŸ¦€ Random::bytes(): āœ… {:?}", bytes); + + let float = rng.random() as f32 / 0x7FFF as f32; + println!("šŸ¦€ Random::float(): āœ… {}", float); + + let int = rng.random() as usize; + println!("šŸ¦€ Random::int(): āœ… {}", int); + + // Create a new deck of cards and draw three cards + let mut deck = Deck::new(); + + // Draw three cards from the top of the deck and print them to the console + let card1 = deck.draw().unwrap(); + let card2 = deck.draw().unwrap(); + let card3 = deck.draw().unwrap(); + + // Print the cards to the console + println!( + "\nšŸ¦€ Let's play a mini game of `Three Card Draw Poker` to demonstrate the random number generator!\n" + ); + println!("šŸŽ² Deck::draw(): āœ… {}", card1); + println!("šŸŽ² Deck::draw(): āœ… {}", card2); + println!("šŸŽ² Deck::draw(): āœ… {}", card3); +} diff --git a/src/lib.rs b/src/lib.rs index 4ae7c686..cd66db82 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,7 @@ //! Add the following to your `Cargo.toml` file: //! ```toml //! [dependencies] -//! mini_functions = "0.0.4" +//! mini_functions = "0.0.5" //! ``` //! Then, add the following to your crate root: //! ```rust @@ -73,5 +73,8 @@ pub mod log; /// Provides a set of utility functions for generating QR codes pub mod qrcode; +/// Provides a set of utility functions for working with random numbers +pub mod random; + /// Provides a set of utility functions for working with UUIDs pub mod uuid; diff --git a/src/random.rs b/src/random.rs new file mode 100644 index 00000000..c191444c --- /dev/null +++ b/src/random.rs @@ -0,0 +1,91 @@ +//! # Core random number generator +//! +//! This crate provides a random number generator based on the linear congruential generator algorithm with the golden ratio as the multiplier. +//! +//! + +// Copyright Ā© 2022-2023 Mini Functions. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::time::SystemTime; + +/// A random number generator based on the linear congruential generator +/// algorithm with the golden ratio as the multiplier. +#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] +pub struct Random { + // The seed for the random number generator + seed: u32, +} + +impl Random { + /// Creates a new `Random` struct with a seed based on the current + /// system time. + pub fn new() -> Self { + // Get the current system time in milliseconds + let seed = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_millis() as u32; + Self { seed } + } + + /// Generates a random number using the linear congruential + /// generator algorithm. The multiplier for the algorithm is the + /// golden ratio. + pub fn random(&mut self) -> u32 { + // The multiplier for the linear congruential generator + // algorithm + let golden_ratio = 1140071478; + // Update the seed with the next value in the sequence + self.seed = self.seed.wrapping_mul(golden_ratio).wrapping_add(12345); + // Return the upper 15 bits of the seed as the random number + (self.seed >> 16) & 0x7FFF + } + + /// Generates a pseudo-random number by XORing the last 31 random + /// numbers together. + pub fn pseudo(&mut self) -> u32 { + let mut res = self.random(); + let mut rng = Random::default(); + // XOR the last 31 random numbers together to generate the + // pseudo-random number + for _ in 0..31 { + res ^= rng.random(); + } + res + } + + /// Generates a vector of random bytes of a given length. + pub fn bytes(&mut self, len: usize) -> Vec { + let mut res = Vec::with_capacity(len); + let mut rng = Random::default(); + for _ in 0..len { + res.push(rng.random() as u8); + } + res + } + /// Generates a random floating point number between 0 and 1. + pub fn float(&mut self) -> f32 { + let mut rng = Random::default(); + rng.random() as f32 / 0x7FFF as f32 + } + /// Generates a random integer between a minimum and maximum value. + pub fn int(&mut self, min: i32, max: i32) -> i32 { + let mut rng = Random::default(); + (rng.random() as f32 / 0x7FFF as f32 * (max - min) as f32) as i32 + min + } +} + +impl std::fmt::Display for Random { + /// Formats the `Random` struct as a string for display. + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Random {{ seed: {} }}", self.seed) + } +} + +impl Default for Random { + fn default() -> Self { + Self::new() + } +} diff --git a/tests/main.rs b/tests/main.rs index ae450b26..defdadd1 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -1,2 +1,5 @@ mod date; mod log; +mod qrcode; +mod random; +mod uuid; diff --git a/tests/random.rs b/tests/random.rs new file mode 100644 index 00000000..566bc1be --- /dev/null +++ b/tests/random.rs @@ -0,0 +1,147 @@ +#[cfg(test)] +mod tests { + use mini_functions::random::Random; + + #[test] + fn test_new() { + let mut rng = Random::new(); + // We cannot directly access the `seed` field, so we will check + // if the `random` function returns a valid value instead + assert!(rng.random() > 0); + } + + #[test] + fn test_default() { + let mut rng = Random::default(); + assert!(rng.random() > 0); + } + + #[test] + fn test_random() { + let mut rng = Random::new(); + let first = rng.random(); + let second = rng.random(); + assert_ne!(first, second); + } + + #[test] + fn test_bytes() { + let mut rng = Random::new(); + + let bytes = rng.bytes(0); + assert_eq!(bytes.len(), 0); + + let bytes = rng.bytes(10); + assert_eq!(bytes.len(), 10); + + let bytes = rng.bytes(100); + assert_eq!(bytes.len(), 100); + } + + #[test] + fn test_pseudo() { + let mut rng = Random::new(); + let first = rng.pseudo(); + let second = rng.pseudo(); + assert_ne!(first, second); + } + + #[test] + fn test_display() { + let rng = Random::new(); + let s = format!("{}", rng); + assert!(s.starts_with("Random { seed: ")); + assert!(s.ends_with("}")); + } + + #[test] + fn test_random_sum_is_positive() { + let mut rng = Random::new(); + let mut sum = 0; + for _ in 0..100 { + sum += rng.random(); + } + assert!(sum > 0); + } + #[test] + fn test_pseudo_sum_is_positive() { + let mut rng = Random::new(); + let mut sum = 0; + for _ in 0..100 { + sum += rng.pseudo(); + } + assert!(sum > 0); + } + #[test] + fn test_random_is_within_range() { + let mut rng = Random::new(); + for _ in 0..100 { + let random = rng.random(); + assert!(random <= std::u32::MAX); + } + } + #[test] + fn test_pseudo_is_within_range() { + let mut rng = Random::new(); + for _ in 0..100 { + let pseudo = rng.pseudo(); + assert!(pseudo <= std::u32::MAX); + } + } + #[test] + fn integration_test_pseudo_and_random_are_different() { + let mut rng = Random::new(); + let mut pseudo_sum = 0; + let mut random_sum = 0; + for _ in 0..100 { + pseudo_sum += rng.pseudo(); + random_sum += rng.random(); + } + assert_ne!(pseudo_sum, random_sum); + } + + #[test] + fn integration_test_random_values_are_within_range() { + let mut rng = Random::new(); + for _ in 0..100 { + let random = rng.random(); + assert!(random <= std::u32::MAX); + } + } + + #[test] + fn integration_test_pseudo_values_are_within_range() { + let mut rng = Random::new(); + for _ in 0..100 { + let pseudo = rng.pseudo(); + assert!(pseudo <= std::u32::MAX); + } + } + + #[test] + fn integration_test_random_values_are_distributed_evenly() { + let mut rng = Random::new(); + let mut counts = [0; 100]; + for _ in 0..100000 { + let random = rng.random(); + let index = (random % 100) as usize; + counts[index] += 1; + } + for count in counts.iter() { + assert!(*count >= 0); + } + } + #[test] + fn integration_test_pseudo_values_are_distributed_evenly() { + let mut rng = Random::new(); + let mut counts = [0; 100]; + for _ in 0..100000 { + let pseudo = rng.pseudo(); + let index = (pseudo % 100) as usize; + counts[index] += 1; + } + for count in counts.iter() { + assert!(*count >= 0); + } + } +}