diff --git a/src/functions.rs b/src/functions.rs index 06c49b2..f1ad449 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -21,10 +21,10 @@ along with Numbrs. If not, see . //! Mathematical functions -use num::{BigRational, FromPrimitive, Integer, Signed, ToPrimitive, Zero}; +use num::{BigInt, BigRational, FromPrimitive, Integer, One, Signed, ToPrimitive, Zero}; use strum_macros::{Display, EnumString}; -use crate::{ast::Value, eval::EvalError}; +use crate::{ast::Value, eval::EvalError, rat_util_macros::rat}; /// # Mathematical functions /// @@ -69,6 +69,18 @@ pub enum Function { /// ## Least common multiple #[strum(serialize = "lcm")] LCM, + + /// ## Combinatorics choose function + #[strum(serialize = "choose")] + Choose, + + /// ## Combinatorics permutation function + #[strum(serialize = "permute")] + Permute, + + /// ## Factorial function + #[strum(serialize = "factorial")] + Factorial, } impl Function { @@ -76,8 +88,8 @@ impl Function { fn number_of_args(&self) -> usize { use Function::*; match self { - Sine | Cosine | AbsoluteValue | SquareRoot | NaturalLogarithm => 1, - GCD | LCM => 2, + Sine | Cosine | AbsoluteValue | SquareRoot | NaturalLogarithm | Factorial => 1, + GCD | LCM | Choose | Permute => 2, } } @@ -85,7 +97,7 @@ impl Function { /// /// ## Errors /// - /// Returns [`EvalError::InvalidFunctionArguments`] if the number of + /// Returns [`EvalError::NumberOfFunctionArguments`] if the number of /// arguments provided doesn't match the number expected for the function /// being called. pub fn eval(&self, mut args: Vec) -> Result { @@ -99,7 +111,7 @@ impl Function { } macro_rules! arg { () => { - args.pop().unwrap() + args.remove(0) }; } Ok(match self { @@ -110,6 +122,9 @@ impl Function { NaturalLogarithm => ln(arg!())?, GCD => gcd(arg!(), arg!())?, LCM => lcm(arg!(), arg!())?, + Choose => choose(arg!(), arg!())?, + Permute => permute(arg!(), arg!())?, + Factorial => factorial(arg!())?, } .into()) } @@ -201,6 +216,68 @@ fn lcm(a: BigRational, b: BigRational) -> Result { Ok(&a * &b / gcd(a, b)?) } +fn choose(n: BigRational, r: BigRational) -> Result { + if !n.is_integer() { + return Err(EvalError::NonIntegerFunctionArgument( + Function::Choose, + n.into(), + )); + } + if !r.is_integer() { + return Err(EvalError::NonIntegerFunctionArgument( + Function::Choose, + r.into(), + )); + } + Ok(permute(n, r.to_owned())? / factorial(r)?) +} + +fn permute(n: BigRational, r: BigRational) -> Result { + if !n.is_integer() { + return Err(EvalError::NonIntegerFunctionArgument( + Function::Permute, + n.into(), + )); + } + if !r.is_integer() { + return Err(EvalError::NonIntegerFunctionArgument( + Function::Permute, + r.into(), + )); + } + if n < r { + return Ok(rat!(-1)); + } + let n_int = n.to_integer(); + let mut denom: BigInt = &n_int - r.to_integer() + 1; + let mut result = BigInt::one(); + while denom <= n_int { + result *= &denom; + denom += 1; + } + Ok(result.into()) +} + +fn factorial(n: BigRational) -> Result { + if !n.is_integer() { + return Err(EvalError::NonIntegerFunctionArgument( + Function::Permute, + n.into(), + )); + } + if n.is_zero() { + return Ok(rat!(1)); + } + let n_int = n.to_integer(); + let mut result = BigInt::one(); + let mut step = BigInt::one() + 1; + while step <= n_int { + result *= &step; + step += 1; + } + Ok(result.into()) +} + #[cfg(test)] mod tests { use pretty_assertions::assert_eq;