Skip to content

Commit

Permalink
Add combinatorics functions (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
kdkasad authored Apr 5, 2024
2 parents c3a03b3 + a4a5045 commit d2f2565
Showing 1 changed file with 83 additions and 6 deletions.
89 changes: 83 additions & 6 deletions src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ along with Numbrs. If not, see <https://www.gnu.org/licenses/>.

//! 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
///
Expand Down Expand Up @@ -69,23 +69,35 @@ 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 {
/// Returns the number of arguments a particular function accepts.
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,
}
}

/// # Evaluate a function with a given set of arguments.
///
/// ## 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<BigRational>) -> Result<Value, EvalError> {
Expand All @@ -99,7 +111,7 @@ impl Function {
}
macro_rules! arg {
() => {
args.pop().unwrap()
args.remove(0)
};
}
Ok(match self {
Expand All @@ -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())
}
Expand Down Expand Up @@ -201,6 +216,68 @@ fn lcm(a: BigRational, b: BigRational) -> Result<BigRational, EvalError> {
Ok(&a * &b / gcd(a, b)?)
}

fn choose(n: BigRational, r: BigRational) -> Result<BigRational, EvalError> {
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<BigRational, EvalError> {
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<BigRational, EvalError> {
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;
Expand Down

0 comments on commit d2f2565

Please sign in to comment.