From 21400e36fc56a8e89ed94a02942bcde06211325c Mon Sep 17 00:00:00 2001 From: Oleksii Trekhleb Date: Tue, 8 Dec 2020 09:52:37 +0100 Subject: [PATCH] Simplify Horner's Method code and add the link to it in main READMe. --- README.md | 1 + src/algorithms/math/horner-method/README.md | 21 +++++++-------- .../__test__/classicPolynome.test.js | 14 ++++++++++ .../__test__/hornerMethod.test.js | 27 ++++++++++++------- .../math/horner-method/classicPolynome.js | 16 +++++++++++ .../math/horner-method/hornerMethod.js | 21 +++++++-------- 6 files changed, 68 insertions(+), 32 deletions(-) create mode 100644 src/algorithms/math/horner-method/__test__/classicPolynome.test.js create mode 100644 src/algorithms/math/horner-method/classicPolynome.js diff --git a/README.md b/README.md index 88d3b7ca..a8d1b9e7 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ a set of rules that precisely define a sequence of operations. * `B` [Complex Number](src/algorithms/math/complex-number) - complex numbers and basic operations with them * `B` [Radian & Degree](src/algorithms/math/radian) - radians to degree and backwards conversion * `B` [Fast Powering](src/algorithms/math/fast-powering) + * `B` [Horner's method](src/algorithms/math/horner-method) - polynomial evaluation * `A` [Integer Partition](src/algorithms/math/integer-partition) * `A` [Square Root](src/algorithms/math/square-root) - Newton's method * `A` [Liu Hui π Algorithm](src/algorithms/math/liu-hui) - approximate π calculations based on N-gons diff --git a/src/algorithms/math/horner-method/README.md b/src/algorithms/math/horner-method/README.md index 719257b3..4526e5b1 100644 --- a/src/algorithms/math/horner-method/README.md +++ b/src/algorithms/math/horner-method/README.md @@ -1,21 +1,20 @@ # Horner's Method -In mathematics, Horner's method (or Horner's scheme) is an algorithm for polynomial evaluation. -With this method, it is possible to evaluate a polynomial with only n additions and n multiplications. -Hence, its storage requirements are n times the number of bits of x. +In mathematics, Horner's method (or Horner's scheme) is an algorithm for polynomial evaluation. With this method, it is possible to evaluate a polynomial with only `n` additions and `n` multiplications. Hence, its storage requirements are `n` times the number of bits of `x`. Horner's method can be based on the following identity: -![](https://wikimedia.org/api/rest_v1/media/math/render/svg/2a576e42d875496f8b0f0dda5ebff7c2415532e4) -, which is called Horner's rule. -To solve the right part of the identity above, for a given x, we start by iterating through the polynomial from the inside out, -accumulating each iteration result. After n iterations, with n being the order of the polynomial, the accumulated result gives -us the polynomial evaluation. +![Horner's rule](https://wikimedia.org/api/rest_v1/media/math/render/svg/2a576e42d875496f8b0f0dda5ebff7c2415532e4) -Using the polynomial: -![](http://www.sciweavers.org/tex2img.php?eq=%244x%5E4%20%2B%202x%5E3%20%2B%203x%5E2%2B%20x%5E1%20%2B%203%24&bc=White&fc=Black&im=jpg&fs=12&ff=arev&edit=0), a traditional approach to evaluate it at x = 2, could be representing it as an array [3,1,3,2,4] and iterate over it saving each iteration value at an accumulator, such as acc += pow(x=2,index) * array[index]. In essence, each power of a number (pow) operation is n-1 multiplications. So, in this scenario, a total of 15 operations would have happened, composed of 5 additions, 5 multiplications, and 5 pows. +This identity is called _Horner's rule_. + +To solve the right part of the identity above, for a given `x`, we start by iterating through the polynomial from the inside out, accumulating each iteration result. After `n` iterations, with `n` being the order of the polynomial, the accumulated result gives us the polynomial evaluation. + +**Using the polynomial:** +![Traditional approach](http://www.sciweavers.org/tex2img.php?eq=%244x%5E4%20%2B%202x%5E3%20%2B%203x%5E2%2B%20x%5E1%20%2B%203%24&bc=White&fc=Black&im=jpg&fs=12&ff=arev&edit=0), a traditional approach to evaluate it at `x = 2`, could be representing it as an array `[3, 1, 3, 2, 4]` and iterate over it saving each iteration value at an accumulator, such as `acc += pow(x=2, index) * array[index]`. In essence, each power of a number (`pow`) operation is `n-1` multiplications. So, in this scenario, a total of `14` operations would have happened, composed of `4` additions, `5` multiplications, and `5` pows (we're assuming that each power is calculated by repeated multiplication). + +Now, **using the same scenario but with Horner's rule**, the polynomial can be re-written as ![Horner's rule approach](http://www.sciweavers.org/tex2img.php?eq=%24x%28x%28x%284x%2B2%29%2B3%29%2B1%29%2B3%24&bc=White&fc=Black&im=jpg&fs=12&ff=arev&edit=0), representing it as `[4, 2, 3, 1, 3]` it is possible to save the first iteration as `acc = arr[0] * (x=2) + arr[1]`, and then finish iterations for `acc *= (x=2) + arr[index]`. In the same scenario but using Horner's rule, a total of `10` operations would have happened, composed of only `4` additions and `4` multiplications. -Now, using the same scenario but with Horner's rule, the polynomial can be re-written as ![](http://www.sciweavers.org/tex2img.php?eq=%24x%28x%28x%284x%2B2%29%2B3%29%2B1%29%2B3%24&bc=White&fc=Black&im=jpg&fs=12&ff=arev&edit=0), representing it as [4,2,3,1,3] it is possible to save the first iteration as acc = arr[0]*(x=2) + arr[1], and then finish iterations for acc *= (x=2) + arr[index]. In the same scenario but using Horner's rule, a total of 10 operations would have happened, composed of only 5 additions and 5 multiplications. ## References - [Wikipedia](https://en.wikipedia.org/wiki/Horner%27s_method) diff --git a/src/algorithms/math/horner-method/__test__/classicPolynome.test.js b/src/algorithms/math/horner-method/__test__/classicPolynome.test.js new file mode 100644 index 00000000..8cdf950d --- /dev/null +++ b/src/algorithms/math/horner-method/__test__/classicPolynome.test.js @@ -0,0 +1,14 @@ +import classicPolynome from '../classicPolynome'; + +describe('classicPolynome', () => { + it('should evaluate the polynomial for the specified value of x correctly', () => { + expect(classicPolynome([8], 0.1)).toBe(8); + expect(classicPolynome([2, 4, 2, 5], 0.555)).toBe(7.68400775); + expect(classicPolynome([2, 4, 2, 5], 0.75)).toBe(9.59375); + expect(classicPolynome([1, 1, 1, 1, 1], 1.75)).toBe(20.55078125); + expect(classicPolynome([15, 3.5, 0, 2, 1.42, 0.41], 0.315)).toBe(1.1367300651406251); + expect(classicPolynome([0, 0, 2.77, 1.42, 0.41], 1.35)).toBe(7.375325000000001); + expect(classicPolynome([0, 0, 2.77, 1.42, 2.3311], 1.35)).toBe(9.296425000000001); + expect(classicPolynome([2, 0, 0, 5.757, 5.31412, 12.3213], 3.141)).toBe(697.2731167035034); + }); +}); diff --git a/src/algorithms/math/horner-method/__test__/hornerMethod.test.js b/src/algorithms/math/horner-method/__test__/hornerMethod.test.js index 85d74bbc..177ceb1a 100644 --- a/src/algorithms/math/horner-method/__test__/hornerMethod.test.js +++ b/src/algorithms/math/horner-method/__test__/hornerMethod.test.js @@ -1,14 +1,21 @@ import hornerMethod from '../hornerMethod'; +import classicPolynome from '../classicPolynome'; describe('hornerMethod', () => { - it('should evaluate the polynomial on the specified point correctly', () => { - expect(hornerMethod([8],0.1)).toBe(8); - expect(hornerMethod([2,4,2,5],0.555)).toBe(7.68400775); - expect(hornerMethod([2,4,2,5],0.75)).toBe(9.59375); - expect(hornerMethod([1,1,1,1,1],1.75)).toBe(20.55078125); - expect(hornerMethod([15,3.5,0,2,1.42,0.41],0.315)).toBe(1.136730065140625); - expect(hornerMethod([0,0,2.77,1.42,0.41],1.35)).toBe(7.375325000000001); - expect(hornerMethod([0,0,2.77,1.42,2.3311],1.35)).toBe(9.296425000000001); - expect(hornerMethod([2,0,0,5.757,5.31412,12.3213],3.141)).toBe(697.2731167035034); + it('should evaluate the polynomial for the specified value of x correctly', () => { + expect(hornerMethod([8], 0.1)).toBe(8); + expect(hornerMethod([2, 4, 2, 5], 0.555)).toBe(7.68400775); + expect(hornerMethod([2, 4, 2, 5], 0.75)).toBe(9.59375); + expect(hornerMethod([1, 1, 1, 1, 1], 1.75)).toBe(20.55078125); + expect(hornerMethod([15, 3.5, 0, 2, 1.42, 0.41], 0.315)).toBe(1.136730065140625); + expect(hornerMethod([0, 0, 2.77, 1.42, 0.41], 1.35)).toBe(7.375325000000001); + expect(hornerMethod([0, 0, 2.77, 1.42, 2.3311], 1.35)).toBe(9.296425000000001); + expect(hornerMethod([2, 0, 0, 5.757, 5.31412, 12.3213], 3.141)).toBe(697.2731167035034); }); -}); \ No newline at end of file + + it('should evaluate the same polynomial value as classical approach', () => { + expect(hornerMethod([8], 0.1)).toBe(classicPolynome([8], 0.1)); + expect(hornerMethod([2, 4, 2, 5], 0.555)).toBe(classicPolynome([2, 4, 2, 5], 0.555)); + expect(hornerMethod([2, 4, 2, 5], 0.75)).toBe(classicPolynome([2, 4, 2, 5], 0.75)); + }); +}); diff --git a/src/algorithms/math/horner-method/classicPolynome.js b/src/algorithms/math/horner-method/classicPolynome.js new file mode 100644 index 00000000..1f6aa861 --- /dev/null +++ b/src/algorithms/math/horner-method/classicPolynome.js @@ -0,0 +1,16 @@ +/** + * Returns the evaluation of a polynomial function at a certain point. + * Uses straightforward approach with powers. + * + * @param {number[]} coefficients - i.e. [4, 3, 2] for (4 * x^2 + 3 * x + 2) + * @param {number} xVal + * @return {number} + */ +export default function classicPolynome(coefficients, xVal) { + return coefficients.reverse().reduce( + (accumulator, currentCoefficient, index) => { + return accumulator + currentCoefficient * (xVal ** index); + }, + 0, + ); +} diff --git a/src/algorithms/math/horner-method/hornerMethod.js b/src/algorithms/math/horner-method/hornerMethod.js index 11b029f8..2236170e 100644 --- a/src/algorithms/math/horner-method/hornerMethod.js +++ b/src/algorithms/math/horner-method/hornerMethod.js @@ -1,17 +1,16 @@ /** * Returns the evaluation of a polynomial function at a certain point. * Uses Horner's rule. - * @param {number[]} numbers + * + * @param {number[]} coefficients - i.e. [4, 3, 2] for (4 * x^2 + 3 * x + 2) + * @param {number} xVal * @return {number} */ -export default function hornerMethod(numbers, point) { - // polynomial function is just a constant. - if (numbers.length === 1) { - return numbers[0]; - } - return numbers.reduce((accumulator, currentValue, index) => { - return index === 1 - ? numbers[0] * point + currentValue - : accumulator * point + currentValue; - }); +export default function hornerMethod(coefficients, xVal) { + return coefficients.reduce( + (accumulator, currentCoefficient) => { + return accumulator * xVal + currentCoefficient; + }, + 0, + ); }