Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add lcm of n numbers #256

Merged
merged 5 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/math/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ The purpose of the Fibonacci algorithm is to explore the properties and patterns
The GCD (Greatest Common Divisor) of n numbers algorithm is used to find the largest positive integer that divides each of the given n numbers without a remainder.
The purpose of this algorithm is to determine the highest common factor of the given set of numbers. It has applications in various areas of mathematics, including number theory, algebra, and cryptography. The GCD of n numbers algorithm is used in many real-world applications, such as finding the optimal solution to a problem that requires dividing resources among multiple agents. It is also used in computer science for designing efficient algorithms that require determining common factors or multiples of numbers.

## [LCM of N numbers](./src/lcm_of_n_numbers.cairo)
The LCM (Lowest Common Multiple) of n numbers algorithm is used to find the smallest positive integer that is a multiple of each of the given n numbers ([see also](https://numpy.org/doc/stable/reference/generated/numpy.lcm.html)).

## [Perfect Number Algorithm](./src/perfect_number.cairo)
The perfect number algorithm is used to determine whether a given positive integer is a perfect number or not.
A perfect number is a positive integer that is equal to the sum of its proper divisors (excluding itself).
Expand Down
34 changes: 34 additions & 0 deletions src/math/src/lcm_of_n_numbers.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//! # LCM for N numbers
use alexandria_math::gcd_of_n_numbers::gcd_two_numbers;
use core::option::OptionTrait;
use core::traits::Into;
use core::traits::TryInto;

#[derive(Drop, Copy, PartialEq)]
enum LCMError {
EmptyInput,
}

/// Calculate the lowest common multiple for n numbers
/// # Arguments
/// * `n` - The array of numbers to calculate the lcm for
/// # Returns
/// * `Result<T, LCMError>` - The lcm of input numbers
fn lcm<T, +Into<T, u128>, +Into<u128, T>, +Mul<T>, +Div<T>, +Copy<T>, +Drop<T>>(
mut n: Span<T>
) -> Result<T, LCMError> {
// Return empty input error
if n.is_empty() {
return Result::Err(LCMError::EmptyInput);
}
let mut a = *n.pop_front().unwrap();
loop {
match n.pop_front() {
Option::Some(b) => {
let gcd: T = gcd_two_numbers(a.into(), (*b).into()).into();
a = (a * *b) / gcd;
},
Option::None => { break Result::Ok(a); },
};
}
}
1 change: 1 addition & 0 deletions src/math/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod fibonacci;
mod gcd_of_n_numbers;
mod karatsuba;
mod keccak256;
mod lcm_of_n_numbers;
mod mod_arithmetics;
mod perfect_number;
mod sha256;
Expand Down
1 change: 1 addition & 0 deletions src/math/src/tests.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod fast_power_test;
mod fibonacci_test;
mod gcd_of_n_numbers_test;
mod karatsuba_test;
mod lcm_of_n_numbers_test;
mod math_test;
mod mod_arithmetics_test;
mod perfect_number_test;
Expand Down
59 changes: 59 additions & 0 deletions src/math/src/tests/lcm_of_n_numbers_test.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use alexandria_math::lcm_of_n_numbers::{lcm, LCMError};
use core::traits::Into;

// the following trait is not safe, it is only used for testing.
impl u128_to_u32 of Into<u128, u32> {
fn into(self: u128) -> u32 {
self.try_into().unwrap()
}
}

#[test]
#[available_gas(1000000000)]
fn lcm_test() {
let arr = array![2_u128, 4_u128, 6_u128, 8_u128, 10_u128];
assert(lcm(arr.span()).unwrap() == 120, 'invalid result');
}

#[test]
#[available_gas(1000000000)]
fn lcm_test_tryinto() {
let arr = array![2_u32, 4_u32, 6_u32, 8_u32, 10_u32];
assert(lcm(arr.span()).unwrap() == 120, 'invalid result');
}

#[test]
#[available_gas(1000000000)]
fn lcm_test_inverse() {
let arr = array![10_u128, 8_u128, 6_u128, 4_u128, 2_u128];
assert(lcm(arr.span()).unwrap() == 120, 'invalid result');
}

#[test]
#[available_gas(1000000000)]
fn lcm_test_3() {
let arr = array![3_u128, 6_u128, 12_u128, 99_u128];
assert(lcm(arr.span()).unwrap() == 396, 'invalid result');
}

#[test]
#[available_gas(1000000000)]
fn lcm_test_4() {
let arr = array![1_u128, 2_u128, 8_u128, 3_u128];
assert(lcm(arr.span()).unwrap() == 24, 'invalid result');
}


#[test]
#[available_gas(1000000000)]
fn lcm_single_test() {
let arr = array![10_u128];
assert(lcm(arr.span()).unwrap() == 10, 'invalid result');
}

#[test]
#[available_gas(1000000000)]
fn lcm_empty_input_test() {
let mut arr: Array<u128> = array![];
assert(lcm(arr.span()) == Result::Err(LCMError::EmptyInput), 'Empty inputs');
}