diff --git a/benches/stock_functions.rs b/benches/stock_functions.rs index a6d3b2e..46bb1b7 100644 --- a/benches/stock_functions.rs +++ b/benches/stock_functions.rs @@ -1,40 +1,32 @@ -use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use rusty_numbers::{factorial, fibonacci, rec_factorial, rec_fibonacci}; +use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; +use rusty_numbers::{factorial, fibonacci, mem_fibonacci, mem_factorial}; fn bench_factorial(c: &mut Criterion) { let mut group = c.benchmark_group("Factorials"); - // Recursive with lookup table - group.bench_function("factorial", |b| b.iter(|| factorial(34))); - group.bench_function("factorial black box", |b| { - b.iter(|| factorial(black_box(34))) - }); - - // Naive recursive - group.bench_function("rec_factorial", |b| b.iter(|| rec_factorial(34))); - group.bench_function("rec_factorial black box", |b| { - b.iter(|| rec_factorial(black_box(34))) - }); - + for i in [0usize, 5, 10, 15, 20, 25, 30, 34].iter() { + group.bench_with_input(BenchmarkId::new("Recursive memoized", i), i, |b, i| { + b.iter(|| mem_factorial(*i)) + }); + group.bench_with_input(BenchmarkId::new("Recursive naive", i), i, |b, i| { + b.iter(|| factorial(*i)) + }); + } group.finish(); } fn bench_fibonacci(c: &mut Criterion) { let mut group = c.benchmark_group("Fibonacci"); - // Recursive with lookup table - group.bench_function("fibonacci", |b| b.iter(|| fibonacci(86))); - group.bench_function("fibonacci black box", |b| { - b.iter(|| fibonacci(black_box(86))) - }); - - // Naive recursive - group.bench_function("rec_fibonacci", |b| b.iter(|| rec_fibonacci(86))); - group.bench_function("rec_fibonacci black box", |b| { - b.iter(|| rec_fibonacci(black_box(86))) - }); - - group.finish(); + for i in [0usize, 5, 10, 15, 20, 30].iter() { + group.bench_with_input(BenchmarkId::new("Recursive memoized", i), i, |b, i| { + b.iter(|| mem_fibonacci(*i)) + }); + // group.bench_with_input(BenchmarkId::new("Recursive naive", i), i, |b, i| b.iter(|| rec_fibonacci(*i))); + group.bench_with_input(BenchmarkId::new("Iterative", i), i, |b, i| { + b.iter(|| fibonacci(*i)) + }); + } } criterion_group!(benches, bench_factorial, bench_fibonacci); diff --git a/src/lib.rs b/src/lib.rs index 51cab33..ae8bcd8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,36 +25,42 @@ pub mod rational; /// # assert!(invalid.is_none()); /// ``` #[inline] -pub fn fibonacci(n: usize) -> Option { - let mut table: Vec = vec![0, 1, 1, 2, 3, 5]; +pub fn mem_fibonacci(n: usize) -> Option { + let mut table = [0u128; 187]; + table[0] = 0; + table[1] = 1; + table[2] = 1; - _fibonacci(n, &mut table) + _mem_fibonacci(n, &mut table) } /// Actual calculating function for `fibonacci` #[inline] -fn _fibonacci(n: usize, table: &mut Vec) -> Option { - match table.get(n) { - Some(x) => Some(*x), - None => { - let a = _fibonacci(n - 1, table)?; - let b = _fibonacci(n - 2, table)?; +fn _mem_fibonacci(n: usize, table: &mut [u128]) -> Option { + if n > 186 { + return None; + } - // Check for overflow when adding - let attempt = a.checked_add(b); + match table[n] { + // The lookup array starts out zeroed, so a zero + // is a not yet calculated value + 0 => { + let a = _mem_fibonacci(n - 1, table)?; + let b = _mem_fibonacci(n - 2, table)?; - if let Some(current) = attempt { - table.insert(n, current); - } + table[n] = a + b; - attempt - } + Some(table[n]) + }, + x => Some(x) } } /// Calculate a number in the fibonacci sequence, /// using naive recursion /// +/// REALLY SLOW +/// /// Can calculate up to 186 using native unsigned 128 bit integers. #[inline] pub fn rec_fibonacci(n: usize) -> Option { @@ -66,20 +72,24 @@ pub fn rec_fibonacci(n: usize) -> Option { let b = rec_fibonacci(n - 2)?; a.checked_add(b) - }, + } } } +/// Calculate a number in the fibonacci sequence, +/// using iteration +/// +/// Can calculate up to 186 #[inline] -pub fn it_fibonacci(n: usize) -> Option { - let mut a:u128 = 0; - let mut b:u128 = 1; +pub fn fibonacci(n: usize) -> Option { + let mut a: u128 = 0; + let mut b: u128 = 1; match n { 0 => Some(a), 1 => Some(b), _ => { - for _ in 0..n-1 { + for _ in 0..n - 1 { let c: u128 = a.checked_add(b)?; a = b; @@ -107,49 +117,60 @@ pub fn it_fibonacci(n: usize) -> Option { /// # assert!(invalid.is_none()); /// ``` #[inline] -pub fn factorial(n: usize) -> Option { - let mut table: Vec = vec![1, 1, 2]; +pub fn mem_factorial(n: usize) -> Option { + let mut table = [0u128; 35]; + table[0] = 1; + table[1] = 1; + table[2] = 2; - _factorial(n, &mut table) + + _mem_factorial(n, &mut table) } /// Actual Calculation function for factoral #[inline] -fn _factorial(n: usize, table: &mut Vec) -> Option { - match table.get(n) { - // Vec.get returns a Option with a reference to the value, - // so deref and wrap in Some() for proper return type - Some(x) => Some(*x), - None => { +fn _mem_factorial(n: usize, table: &mut [u128]) -> Option { + if n > 34 { + return None + } + + match table[n] { + // Empty spaces in the lookup array are zero-filled + 0 => { // The ? suffix passes along the Option value // to be handled later // See: https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator - let prev = _factorial(n - 1, table)?; + let prev = _mem_factorial(n - 1, table)?; - // Do an overflow-checked multiply - let attempt = (n as u128).checked_mul(prev); + table[n] = (n as u128) * prev; - // If there isn't an overflow, add the result - // to the calculation table - if let Some(current) = attempt { - table.insert(n, current); - } - - attempt // Some(x) if no overflow - } + Some(table[n]) + }, + x => Some(x), } } -/// Calculate the value of a factorial using recursion +/// Calculate the value of a factorial, /// /// Can calculate up to 34! using native unsigned 128 bit integers. +/// +/// Example: +/// ```rust +/// use rusty_numbers::factorial; +/// +/// let valid = factorial(3); // Some(6) +/// # assert_eq!(6, valid.unwrap()); +/// +/// let invalid = factorial(35); // None +/// # assert!(invalid.is_none()); +/// ``` #[inline] -pub fn rec_factorial(n: usize) -> Option { +pub fn factorial(n: usize) -> Option { match n { 0 => Some(1u128), 1 => Some(1u128), _ => { - let prev = rec_factorial(n - 1)?; + let prev = factorial(n - 1)?; (n as u128).checked_mul(prev) } @@ -168,7 +189,7 @@ mod tests { assert_eq!(6, factorial(3).unwrap()); let res = factorial(34); - let res2 = rec_factorial(34); + let res2 = mem_factorial(34); assert!(res.is_some()); assert_eq!(res.unwrap(), res2.unwrap()); @@ -182,8 +203,12 @@ mod tests { assert_eq!(1, fibonacci(1).unwrap()); assert_eq!(1, fibonacci(2).unwrap()); + let res = fibonacci(20); + let res2 = rec_fibonacci(20); + assert_eq!(res, res2); + let res = fibonacci(186); - let res2 = it_fibonacci(186); + let res2 = mem_fibonacci(186); assert!(res.is_some()); assert!(res2.is_some()); assert_eq!(res.unwrap(), res2.unwrap());