From a39203c699e6760a9c0f167d8d77da8431238ff2 Mon Sep 17 00:00:00 2001 From: William Perron Date: Mon, 3 Apr 2023 09:10:25 -0400 Subject: [PATCH] add fraction maths (issue #291) --- Cargo.lock | 4 + Cargo.toml | 3 +- fraction-math/Cargo.toml | 8 ++ fraction-math/src/main.rs | 227 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 241 insertions(+), 1 deletion(-) create mode 100644 fraction-math/Cargo.toml create mode 100644 fraction-math/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 46139b2..6d9bf1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,6 +12,10 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "fraction-math" +version = "0.1.0" + [[package]] name = "getrandom" version = "0.2.8" diff --git a/Cargo.toml b/Cargo.toml index 9ea4962..0f09fb6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "balance", + "fraction-math", "repeated-groups", - "scramble", + "scramble" ] diff --git a/fraction-math/Cargo.toml b/fraction-math/Cargo.toml new file mode 100644 index 0000000..600255b --- /dev/null +++ b/fraction-math/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "fraction-math" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/fraction-math/src/main.rs b/fraction-math/src/main.rs new file mode 100644 index 0000000..e968cf3 --- /dev/null +++ b/fraction-math/src/main.rs @@ -0,0 +1,227 @@ +use std::{collections::BTreeSet, fmt::Display, str::FromStr}; + +/// Write a function that can do the 4 basic operations (add, subtract, multiply +/// and divide) on two fractions. Return the most simplified form of the result. +/// You can assume a non-zero denominator in the input, and don’t use any +/// built-in implementations in your language of choice, if you can! +/// +/// Example: +/// +/// ``` +/// > fractionMath("3/4", "add", "3/4") +/// > "3/2" +/// +/// > fractionMath("1/8", "multiply", "2/2") +/// > "1/8" +/// ``` + +fn main() { + println!( + "{}", + fraction_math("2/5".to_owned(), Op::Add, "2/4".to_owned()) + ); +} + +enum Op { + Add, + Sub, + Multiply, + Divide, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +struct Fraction { + numerator: isize, + denominator: isize, +} + +impl Fraction { + fn simplify(self) -> Self { + let numerator = self.numerator; + let denominator = self.denominator; + + let num_factors = factors(numerator); + let den_factors = factors(denominator); + + let (mut i, mut j) = (num_factors.len() - 1, den_factors.len() - 1); + + while i < usize::MAX && j < usize::MAX { + if num_factors[i] == den_factors[j] { + let common = num_factors[i]; + + return Self { + numerator: numerator / common, + denominator: denominator / common, + }; + } + + if i > j { + i -= 1; + } else { + j -= 1; + } + } + + // there's no way to simplify this fraction, so we just return it + self.clone() + } + + fn to_denominator(&mut self, denom: isize) { + if denom != self.denominator { + let multiplier = denom / self.denominator; + self.numerator *= multiplier; + self.denominator *= multiplier; + } + } +} + +impl Display for Fraction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}/{}", self.numerator, self.denominator) + } +} + +impl FromStr for Fraction { + type Err = String; + + fn from_str(s: &str) -> Result { + if let Some(i) = s.find('/') { + let numerator = s.get(0..i).unwrap().parse().unwrap(); + let denominator = s.get(i + 1..).unwrap().parse().unwrap(); + + Ok(Self { + numerator, + denominator, + }) + } else { + Err("no separator found".to_string()) + } + } +} + +fn common_denominator(a: isize, b: isize) -> isize { + if a == b { + a + } else if a % b == 0 { + a + } else if b % a == 0 { + b + } else { + a * b + } +} + +fn factors(n: isize) -> Vec { + let mut set = BTreeSet::new(); + + for i in 1..10 { + if n % i == 0 { + let j = n / i; + set.insert(i); + set.insert(j); + } + } + + set.into_iter().collect() +} + +fn fraction_math(lhs: String, op: Op, rhs: String) -> Fraction { + let mut lhs = Fraction::from_str(&lhs).unwrap(); + let mut rhs = Fraction::from_str(&rhs).unwrap(); + + match op { + Op::Add => { + let common = common_denominator(lhs.denominator, rhs.denominator); + lhs.to_denominator(common); + rhs.to_denominator(common); + + let frac = Fraction { + numerator: lhs.numerator + rhs.numerator, + denominator: common, + }; + frac.simplify() + } + Op::Sub => { + let common = common_denominator(lhs.denominator, rhs.denominator); + lhs.to_denominator(common); + rhs.to_denominator(common); + + let frac = Fraction { + numerator: lhs.numerator - rhs.numerator, + denominator: common, + }; + frac.simplify() + } + Op::Multiply => Fraction { + numerator: lhs.numerator * rhs.numerator, + denominator: lhs.denominator * rhs.denominator, + } + .simplify(), + Op::Divide => Fraction { + numerator: lhs.numerator * rhs.denominator, + denominator: lhs.denominator * rhs.numerator, + } + .simplify(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_simplify() { + let initial = Fraction { + numerator: 6, + denominator: 9, + }; + + assert_eq!( + Fraction { + numerator: 2, + denominator: 3 + }, + initial.simplify() + ); + + let prime = Fraction { + numerator: 7, + denominator: 13, + }; + + assert_eq!(prime.clone(), prime.simplify()); + } + + #[test] + fn test_factors() { + assert_eq!(vec![1, 2, 3, 4, 6, 12], factors(12)); + } + + #[test] + fn test_fraction_addition() { + let result = fraction_math("2/5".to_owned(), Op::Add, "2/4".to_owned()); + + assert_eq!(Fraction::from_str("9/10").unwrap(), result); + } + + #[test] + fn test_fraction_subtraction() { + let result = fraction_math("3/5".to_owned(), Op::Sub, "2/4".to_owned()); + + assert_eq!(Fraction::from_str("1/10").unwrap(), result); + } + + #[test] + fn test_fraction_multiplication() { + let result = fraction_math("3/5".to_owned(), Op::Multiply, "2/4".to_owned()); + + assert_eq!(Fraction::from_str("3/10").unwrap(), result); + } + + #[test] + fn test_fraction_division() { + let result = fraction_math("1/2".to_owned(), Op::Divide, "3/4".to_owned()); + + assert_eq!(Fraction::from_str("2/3").unwrap(), result); + } +}