From 30ef6a30e11b674510669f0c663d255cb96634ae Mon Sep 17 00:00:00 2001 From: Oleksii Trekhleb Date: Fri, 11 Dec 2020 08:37:06 +0100 Subject: [PATCH] Add prime factors calculation. --- README.md | 2 +- src/algorithms/math/prime-factors/README.md | 34 ++++---- .../__test__/primeFactors.test.js | 87 +++++++++++++++++++ .../__test__/primefactors.test.js | 40 --------- .../math/prime-factors/primeFactors.js | 42 +++++++++ .../math/prime-factors/primefactors.js | 44 ---------- 6 files changed, 147 insertions(+), 102 deletions(-) create mode 100644 src/algorithms/math/prime-factors/__test__/primeFactors.test.js delete mode 100644 src/algorithms/math/prime-factors/__test__/primefactors.test.js create mode 100644 src/algorithms/math/prime-factors/primeFactors.js delete mode 100644 src/algorithms/math/prime-factors/primefactors.js diff --git a/README.md b/README.md index 3008a6ab..3886d0bb 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ a set of rules that precisely define a sequence of operations. * `B` [Bit Manipulation](src/algorithms/math/bits) - set/get/update/clear bits, multiplication/division by two, make negative etc. * `B` [Factorial](src/algorithms/math/factorial) * `B` [Fibonacci Number](src/algorithms/math/fibonacci) - classic and closed-form versions - * `B` [Prime Factors](src/algorithms/math/prime-factors) - finding distinct prime-factor count using both accurate & Hardy-Ramanujan's Algorithm + * `B` [Prime Factors](src/algorithms/math/prime-factors) - finding prime factors and counting them using Hardy-Ramanujan's theorem * `B` [Primality Test](src/algorithms/math/primality-test) (trial division method) * `B` [Euclidean Algorithm](src/algorithms/math/euclidean-algorithm) - calculate the Greatest Common Divisor (GCD) * `B` [Least Common Multiple](src/algorithms/math/least-common-multiple) (LCM) diff --git a/src/algorithms/math/prime-factors/README.md b/src/algorithms/math/prime-factors/README.md index 6b855d37..a0f1d153 100644 --- a/src/algorithms/math/prime-factors/README.md +++ b/src/algorithms/math/prime-factors/README.md @@ -1,34 +1,34 @@ # Prime Factors -Prime factors are basically those prime numbers which multiply together to give the orignal number. For ex: 39 will have prime factors as 3 and 13 which are also prime numbers. Another example is 15 whose prime factors are 3 and 5. +**Prime number** is a whole number greater than `1` that **cannot** be made by multiplying other whole numbers. The first few prime numbers are: `2`, `3`, `5`, `7`, `11`, `13`, `17`, `19` and so on. -#### Method for finding the prime factors and their count accurately +If we **can** make it by multiplying other whole numbers it is a **Composite Number**. -The approach is to basically keep on dividing the natural number 'n' by indexes from i = 2 to i = n by prime indexes only. This is ensured by an 'if' check. Then value of 'n' keeps on overriding by (n/i). -The time complexity till now is O(n) in worst case since the loop run from index i = 2 to i = n even when no index 'i' is left to be divided by 'n' other than n itself. This time complexity can be reduced to O(sqrt(n)) from O(n). This optimisation is acheivable when loop is ran from i = 2 to i = sqrt(n). Now, we go only till O(sqrt(n)) because when 'i' becomes greater than sqrt(n), we now have the confirmation there is no index 'i' left which can divide 'n' completely other than n itself. +![Composite numbers](https://www.mathsisfun.com/numbers/images/prime-composite.svg) -##### Optimised Time Complexity: O(sqrt(n)) +_Image source: [Math is Fun](https://www.mathsisfun.com/prime-factorization.html)_ +**Prime factors** are those [prime numbers](https://en.wikipedia.org/wiki/Prime_number) which multiply together to give the original number. For example `39` will have prime factors of `3` and `13` which are also prime numbers. Another example is `15` whose prime factors are `3` and `5`. -#### Hardy-Ramanujan formula for approximate calculation of prime-factor count +![Factors](https://www.mathsisfun.com/numbers/images/factor-2x3.svg) -In 1917, a theorem was formulated by G.H Hardy and Srinivasa Ramanujan which approximately tells the total count of distinct prime factors of most 'n' natural numbers. -The fomula is given by ln(ln(n)). +_Image source: [Math is Fun](https://www.mathsisfun.com/prime-factorization.html)_ -#### Code Explaiation +## Finding the prime factors and their count accurately -There are on 4 functions used: +The approach is to keep on dividing the natural number `n` by indexes from `i = 2` to `i = n` (by prime indexes only). The value of `n` is being overridden by `(n / i)` on each iteration. -- getPrimeFactors : returns array containing all distinct prime factors for given input n. +The time complexity till now is `O(n)` in the worst case scenario since the loop runs from index `i = 2` to `i = n`. This time complexity can be reduced from `O(n)` to `O(sqrt(n))`. The optimisation is achievable when loop runs from `i = 2` to `i = sqrt(n)`. Now, we go only till `O(sqrt(n))` because when `i` becomes greater than `sqrt(n)`, we have the confirmation that there is no index `i` left which can divide `n` completely other than `n` itself. -- getPrimeFactorsCount: returns accurate total count of distinct prime factors of given input n. +## Hardy-Ramanujan formula for approximate calculation of prime-factor count -- hardyRamanujanApprox: returns approximate total count of distinct prime factors of given input n using Hardy-Ramanujan formula. +In 1917, a theorem was formulated by G.H Hardy and Srinivasa Ramanujan which states that the normal order of the number `ω(n)` of distinct prime factors of a number `n` is `log(log(n))`. -- errorPercent : returns %age of error in approximation using formula to that of accurate result. The formula used is: **[Modulus(accurate_val - approximate_val) / accurate_val ] * 100**. This shows deviation from accurate result. - +Roughly speaking, this means that most numbers have about this number of distinct prime factors. ## References -- [Youtube](https://www.youtube.com/watch?v=6PDtgHhpCHo) -- [Wikipedia](https://en.wikipedia.org/wiki/Hardy%E2%80%93Ramanujan_theorem) \ No newline at end of file +- [Prime numbers on Math is Fun](https://www.mathsisfun.com/prime-factorization.html) +- [Prime numbers on Wikipedia](https://en.wikipedia.org/wiki/Prime_number) +- [Hardy–Ramanujan theorem on Wikipedia](https://en.wikipedia.org/wiki/Hardy%E2%80%93Ramanujan_theorem) +- [Prime factorization of a number on Youtube](https://www.youtube.com/watch?v=6PDtgHhpCHo&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8&index=82) diff --git a/src/algorithms/math/prime-factors/__test__/primeFactors.test.js b/src/algorithms/math/prime-factors/__test__/primeFactors.test.js new file mode 100644 index 00000000..92c94d1f --- /dev/null +++ b/src/algorithms/math/prime-factors/__test__/primeFactors.test.js @@ -0,0 +1,87 @@ +import { + primeFactors, + hardyRamanujan, +} from '../primeFactors'; + +/** + * Calculates the error between exact and approximate prime factor counts. + * @param {number} exactCount + * @param {number} approximateCount + * @returns {number} - approximation error (percentage). + */ +function approximationError(exactCount, approximateCount) { + return (Math.abs((exactCount - approximateCount) / exactCount) * 100); +} + +describe('primeFactors', () => { + it('should find prime factors', () => { + expect(primeFactors(1)).toEqual([]); + expect(primeFactors(2)).toEqual([2]); + expect(primeFactors(3)).toEqual([3]); + expect(primeFactors(4)).toEqual([2, 2]); + expect(primeFactors(14)).toEqual([2, 7]); + expect(primeFactors(40)).toEqual([2, 2, 2, 5]); + expect(primeFactors(54)).toEqual([2, 3, 3, 3]); + expect(primeFactors(100)).toEqual([2, 2, 5, 5]); + expect(primeFactors(156)).toEqual([2, 2, 3, 13]); + expect(primeFactors(273)).toEqual([3, 7, 13]); + expect(primeFactors(300)).toEqual([2, 2, 3, 5, 5]); + expect(primeFactors(980)).toEqual([2, 2, 5, 7, 7]); + expect(primeFactors(1000)).toEqual([2, 2, 2, 5, 5, 5]); + expect(primeFactors(52734)).toEqual([2, 3, 11, 17, 47]); + expect(primeFactors(343434)).toEqual([2, 3, 7, 13, 17, 37]); + expect(primeFactors(456745)).toEqual([5, 167, 547]); + expect(primeFactors(510510)).toEqual([2, 3, 5, 7, 11, 13, 17]); + expect(primeFactors(8735463)).toEqual([3, 3, 11, 88237]); + expect(primeFactors(873452453)).toEqual([149, 1637, 3581]); + }); + + it('should give approximate prime factors count using Hardy-Ramanujan theorem', () => { + expect(hardyRamanujan(2)).toBeCloseTo(-0.366, 2); + expect(hardyRamanujan(4)).toBeCloseTo(0.326, 2); + expect(hardyRamanujan(40)).toBeCloseTo(1.305, 2); + expect(hardyRamanujan(156)).toBeCloseTo(1.6193, 2); + expect(hardyRamanujan(980)).toBeCloseTo(1.929, 2); + expect(hardyRamanujan(52734)).toBeCloseTo(2.386, 2); + expect(hardyRamanujan(343434)).toBeCloseTo(2.545, 2); + expect(hardyRamanujan(456745)).toBeCloseTo(2.567, 2); + expect(hardyRamanujan(510510)).toBeCloseTo(2.575, 2); + expect(hardyRamanujan(8735463)).toBeCloseTo(2.771, 2); + expect(hardyRamanujan(873452453)).toBeCloseTo(3.024, 2); + }); + + it('should give correct deviation between exact and approx counts', () => { + expect(approximationError(primeFactors(2).length, hardyRamanujan(2))) + .toBeCloseTo(136.651, 2); + + expect(approximationError(primeFactors(4).length, hardyRamanujan(2))) + .toBeCloseTo(118.325, 2); + + expect(approximationError(primeFactors(40).length, hardyRamanujan(2))) + .toBeCloseTo(109.162, 2); + + expect(approximationError(primeFactors(156).length, hardyRamanujan(2))) + .toBeCloseTo(109.162, 2); + + expect(approximationError(primeFactors(980).length, hardyRamanujan(2))) + .toBeCloseTo(107.330, 2); + + expect(approximationError(primeFactors(52734).length, hardyRamanujan(52734))) + .toBeCloseTo(52.274, 2); + + expect(approximationError(primeFactors(343434).length, hardyRamanujan(343434))) + .toBeCloseTo(57.578, 2); + + expect(approximationError(primeFactors(456745).length, hardyRamanujan(456745))) + .toBeCloseTo(14.420, 2); + + expect(approximationError(primeFactors(510510).length, hardyRamanujan(510510))) + .toBeCloseTo(63.201, 2); + + expect(approximationError(primeFactors(8735463).length, hardyRamanujan(8735463))) + .toBeCloseTo(30.712, 2); + + expect(approximationError(primeFactors(873452453).length, hardyRamanujan(873452453))) + .toBeCloseTo(0.823, 2); + }); +}); diff --git a/src/algorithms/math/prime-factors/__test__/primefactors.test.js b/src/algorithms/math/prime-factors/__test__/primefactors.test.js deleted file mode 100644 index 7a1ba963..00000000 --- a/src/algorithms/math/prime-factors/__test__/primefactors.test.js +++ /dev/null @@ -1,40 +0,0 @@ -import primefactors from '../primefactors'; - -describe('prime-factors', () => { - it('should give prime factors', () => { - expect(primefactors.getPrimeFactors(510510)).toEqual([2, 3, 5, 7, 11, 13, 17]); - expect(primefactors.getPrimeFactors(343434)).toEqual([2, 3, 7, 13, 17, 37]); - expect(primefactors.getPrimeFactors(456745)).toEqual([5, 167, 547]); - expect(primefactors.getPrimeFactors(8735463)).toEqual([3, 11, 88237]); - expect(primefactors.getPrimeFactors(873452453)).toEqual([149, 1637, 3581]); - expect(primefactors.getPrimeFactors(52734)).toEqual([2, 3, 11, 17, 47]); - }); - - it('should give prime factors count accurately', () => { - expect(primefactors.getPrimeFactorsCount(primefactors.getPrimeFactors(510510))).toEqual(7); - expect(primefactors.getPrimeFactorsCount(primefactors.getPrimeFactors(343434))).toEqual(6); - expect(primefactors.getPrimeFactorsCount(primefactors.getPrimeFactors(456745))).toEqual(3); - expect(primefactors.getPrimeFactorsCount(primefactors.getPrimeFactors(8735463))).toEqual(3); - expect(primefactors.getPrimeFactorsCount(primefactors.getPrimeFactors(873452453))).toEqual(3); - expect(primefactors.getPrimeFactorsCount(primefactors.getPrimeFactors(52734))).toEqual(5); - }); - - it('should give prime factors count approximately using Hardy-Ramanujan-Approx', () => { - expect(primefactors.hardyRamanujanApprox(510510)).toBeCloseTo(2.5759018900,5); - expect(primefactors.hardyRamanujanApprox(343434)).toBeCloseTo(2.54527635538,5); - expect(primefactors.hardyRamanujanApprox(456745)).toBeCloseTo(2.5673987036,5); - expect(primefactors.hardyRamanujanApprox(8735463)).toBeCloseTo(2.771519494900,5); - expect(primefactors.hardyRamanujanApprox(873452453)).toBeCloseTo(3.0247066455016,5); - expect(primefactors.hardyRamanujanApprox(52734)).toBeCloseTo(2.386284094835,5); - }); - - it('should give error percentage of deviation of Hardy-Ramanujan-Approx prime-factors count from accurate prime-factors count', () => { - expect(primefactors.errorPercent(primefactors.getPrimeFactorsCount(primefactors.getPrimeFactors(510510)),primefactors.hardyRamanujanApprox(510510))).toBeCloseTo(63.20140157059997,5); - expect(primefactors.errorPercent(primefactors.getPrimeFactorsCount(primefactors.getPrimeFactors(343434)),primefactors.hardyRamanujanApprox(343434))).toBeCloseTo(57.5787274,5); - expect(primefactors.errorPercent(primefactors.getPrimeFactorsCount(primefactors.getPrimeFactors(456745)),primefactors.hardyRamanujanApprox(456745))).toBeCloseTo(14.420043212851,5); - expect(primefactors.errorPercent(primefactors.getPrimeFactorsCount(primefactors.getPrimeFactors(8735463)),primefactors.hardyRamanujanApprox(8735463))).toBeCloseTo(7.61601683663378,5); - expect(primefactors.errorPercent(primefactors.getPrimeFactorsCount(primefactors.getPrimeFactors(873452453)),primefactors.hardyRamanujanApprox(873452453))).toBeCloseTo(0.8235548500,5); - expect(primefactors.errorPercent(primefactors.getPrimeFactorsCount(primefactors.getPrimeFactors(52734)),primefactors.hardyRamanujanApprox(52734))).toBeCloseTo(52.27431810328,5); - }); -}); - diff --git a/src/algorithms/math/prime-factors/primeFactors.js b/src/algorithms/math/prime-factors/primeFactors.js new file mode 100644 index 00000000..691436c4 --- /dev/null +++ b/src/algorithms/math/prime-factors/primeFactors.js @@ -0,0 +1,42 @@ +/** + * Finds prime factors of a number. + * + * @param {number} n - the number that is going to be split into prime factors. + * @returns {number[]} - array of prime factors. + */ +export function primeFactors(n) { + // Clone n to avoid function arguments override. + let nn = n; + + // Array that stores the all the prime factors. + const factors = []; + + // Running the loop till sqrt(n) instead of n to optimise time complexity from O(n) to O(sqrt(n)). + for (let factor = 2; factor <= Math.sqrt(nn); factor += 1) { + // Check that factor divides n without a reminder. + while (nn % factor === 0) { + // Overriding the value of n. + nn /= factor; + // Saving the factor. + factors.push(factor); + } + } + + // The ultimate reminder should be a last prime factor, + // unless it is not 1 (since 1 is not a prime number). + if (nn !== 1) { + factors.push(nn); + } + + return factors; +} + +/** + * Hardy-Ramanujan approximation of prime factors count. + * + * @param {number} n + * @returns {number} - approximate number of prime factors. + */ +export function hardyRamanujan(n) { + return Math.log(Math.log(n)); +} diff --git a/src/algorithms/math/prime-factors/primefactors.js b/src/algorithms/math/prime-factors/primefactors.js deleted file mode 100644 index d951a371..00000000 --- a/src/algorithms/math/prime-factors/primefactors.js +++ /dev/null @@ -1,44 +0,0 @@ -export default { - - getPrimeFactors : (n) => { - let factorsArray = []; // an array where all the prime factors will be stored - - //over here optimisation is made by running loop till sqrt(n) instead of n - for (let i = 2 ; i <= Math.sqrt(n); i++){ - if(n % i === 0){ // if check to ensure i completely divides n - let count = 0; // This count keeps track of number of times i divides n - while(n % i === 0){ - n = n/i; // override the value of n - count++; // count value updated - } - factorsArray.push(i); // array gets populated - } - } - if(n !== 1){ // finally we cannot push 1 to array since it cannot be a prime-factor - factorsArray.push(n); - } - - return factorsArray; - }, - - //returns accurate prime-factors count - getPrimeFactorsCount : (factorsArray) => { - return factorsArray.length; - }, - - - //returns Hardy-Ramanujan Approximation of prime-factors count - hardyRamanujanApprox : (n) => { - return Math.log(Math.log(n)); - }, - - //returns %age of error in approximation using formula to that of accurate result. - errorPercent : (exactFactorCount,approximateFactorCount) => { - let diff = exactFactorCount-approximateFactorCount > 0 ? exactFactorCount-approximateFactorCount: -(exactFactorCount-approximateFactorCount); - return (diff/exactFactorCount * 100); - } - - -} - -