diff --git a/Maths/Fibonacci.js b/Maths/Fibonacci.js index c1b153f24..fc8102ad2 100644 --- a/Maths/Fibonacci.js +++ b/Maths/Fibonacci.js @@ -1,51 +1,67 @@ -const list = [] +// https://en.wikipedia.org/wiki/Generalizations_of_Fibonacci_numbers#Extension_to_negative_integers +const FibonacciIterative = (num) => { + const isNeg = num < 0 + if (isNeg) num *= -1 + const sequence = [0] -const FibonacciIterative = (nth) => { - const sequence = [] + if (num >= 1) sequence.push(1) + if (num >= 2) sequence.push(isNeg ? -1 : 1) - if (nth >= 1) sequence.push(1) - if (nth >= 2) sequence.push(1) - - for (let i = 2; i < nth; i++) { - sequence.push(sequence[i - 1] + sequence[i - 2]) + for (let i = 2; i < num; i++) { + sequence.push( + isNeg ? sequence[i - 1] - sequence[i] : sequence[i] + sequence[i - 1] + ) } return sequence } -const FibonacciRecursive = (number) => { +const FibonacciGenerator = function * (neg) { + let a = 0 + let b = 1 + yield a + while (true) { + yield b; + [a, b] = neg ? [b, a - b] : [b, a + b] + } +} + +const list = [] +const FibonacciRecursive = (num) => { + const isNeg = num < 0 + if (isNeg) num *= -1 return (() => { switch (list.length) { case 0: - list.push(1) - return FibonacciRecursive(number) + list.push(0) + return FibonacciRecursive(num) case 1: list.push(1) - return FibonacciRecursive(number) - case number: + return FibonacciRecursive(num) + case num + 1: return list default: - list.push(list[list.length - 1] + list[list.length - 2]) - return FibonacciRecursive(number) + list.push(list.at(-1) + list.at(-2)) + return FibonacciRecursive(num) } - })() + })().map((fib, i) => fib * (isNeg ? (-1) ** (i + 1) : 1)) } const dict = new Map() - const FibonacciRecursiveDP = (stairs) => { - if (stairs <= 0) return 0 - if (stairs === 1) return 1 + const isNeg = stairs < 0 + if (isNeg) stairs *= -1 + + if (stairs <= 1) return stairs // Memoize stair count - if (dict.has(stairs)) return dict.get(stairs) + if (dict.has(stairs)) return (isNeg ? (-1) ** (stairs + 1) : 1) * dict.get(stairs) - const res = - FibonacciRecursiveDP(stairs - 1) + FibonacciRecursiveDP(stairs - 2) + const res = FibonacciRecursiveDP(stairs - 1) + FibonacciRecursiveDP(stairs - 2) dict.set(stairs, res) - return res + return (isNeg ? (-1) ** (stairs + 1) : 1) * res } // Algorithms @@ -59,12 +75,16 @@ const FibonacciRecursiveDP = (stairs) => { // a function of the number of input bits // @Satzyakiz -const FibonacciDpWithoutRecursion = (number) => { - const table = [] +const FibonacciDpWithoutRecursion = (num) => { + const isNeg = num < 0 + if (isNeg) num *= -1 + const table = [0] table.push(1) - table.push(1) - for (let i = 2; i < number; ++i) { - table.push(table[i - 1] + table[i - 2]) + table.push(isNeg ? -1 : 1) + for (let i = 2; i < num; ++i) { + table.push( + isNeg ? table[i - 1] - table[i] : table[i] + table[i - 1] + ) } return table } @@ -76,24 +96,31 @@ const copyMatrix = (A) => { } const Identity = (size) => { + const isBigInt = typeof size === 'bigint' + const ZERO = isBigInt ? 0n : 0 + const ONE = isBigInt ? 1n : 1 + size = Number(size) const I = Array(size).fill(null).map(() => Array(size).fill()) return I.map((row, rowIdx) => row.map((_col, colIdx) => { - return rowIdx === colIdx ? 1 : 0 + return rowIdx === colIdx ? ONE : ZERO })) } // A of size (l x m) and B of size (m x n) -// product C will be of size (l x n) +// product C will be of size (l x n). +// both matrices must have same-type numeric values +// either both BigInt or both Number const matrixMultiply = (A, B) => { A = copyMatrix(A) B = copyMatrix(B) + const isBigInt = typeof A[0][0] === 'bigint' const l = A.length const m = B.length const n = B[0].length // Assuming non-empty matrices const C = Array(l).fill(null).map(() => Array(n).fill()) for (let i = 0; i < l; i++) { for (let j = 0; j < n; j++) { - C[i][j] = 0 + C[i][j] = isBigInt ? 0n : 0 for (let k = 0; k < m; k++) { C[i][j] += A[i][k] * B[k][j] } @@ -110,18 +137,25 @@ const matrixMultiply = (A, B) => { // A is a square matrix const matrixExpo = (A, n) => { A = copyMatrix(A) + const isBigInt = typeof n === 'bigint' + const ZERO = isBigInt ? 0n : 0 + const TWO = isBigInt ? 2n : 2 // Just like Binary exponentiation mentioned in ./BinaryExponentiationIterative.js - let result = Identity(A.length) // Identity matrix - while (n > 0) { - if (n % 2 !== 0) result = matrixMultiply(result, A) - n = Math.floor(n / 2) - if (n > 0) A = matrixMultiply(A, A) + let result = Identity((isBigInt ? BigInt : Number)(A.length)) // Identity matrix + while (n > ZERO) { + if (n % TWO !== ZERO) result = matrixMultiply(result, A) + n /= TWO + if (!isBigInt) n = Math.floor(n) + if (n > ZERO) A = matrixMultiply(A, A) } return result } -const FibonacciMatrixExpo = (n) => { +const FibonacciMatrixExpo = (num) => { + const isBigInt = typeof num === 'bigint' + const ZERO = isBigInt ? 0n : 0 + const ONE = isBigInt ? 1n : 1 // F(0) = 0, F(1) = 1 // F(n) = F(n-1) + F(n-2) // Consider below matrix multiplication: @@ -134,23 +168,28 @@ const FibonacciMatrixExpo = (n) => { // or F(n, n-1) = A * A * F(n-2, n-3) // or F(n, n-1) = pow(A, n-1) * F(1, 0) - if (n === 0) return 0 + if (num === ZERO) return num + + const isNeg = num < 0 + if (isNeg) num *= -ONE const A = [ - [1, 1], - [1, 0] + [ONE, ONE], + [ONE, ZERO] ] - const poweredA = matrixExpo(A, n - 1) // A raised to the power n-1 + + const poweredA = matrixExpo(A, num - ONE) // A raised to the power n-1 let F = [ - [1], - [0] + [ONE], + [ZERO] ] F = matrixMultiply(poweredA, F) - return F[0][0] + return F[0][0] * (isNeg ? (-ONE) ** (num + ONE) : ONE) } export { FibonacciDpWithoutRecursion } export { FibonacciIterative } +export { FibonacciGenerator } export { FibonacciRecursive } export { FibonacciRecursiveDP } export { FibonacciMatrixExpo } diff --git a/Maths/test/Fibonacci.test.js b/Maths/test/Fibonacci.test.js index bccd799be..f3dcb98fe 100644 --- a/Maths/test/Fibonacci.test.js +++ b/Maths/test/Fibonacci.test.js @@ -2,30 +2,61 @@ import { FibonacciDpWithoutRecursion, FibonacciRecursiveDP, FibonacciIterative, + FibonacciGenerator, FibonacciRecursive, FibonacciMatrixExpo } from '../Fibonacci' describe('Fibonacci', () => { it('should return an array of numbers for FibonacciIterative', () => { - expect(FibonacciIterative(5)).toEqual( - expect.arrayContaining([1, 1, 2, 3, 5]) + expect(FibonacciIterative(6)).toEqual( + expect.arrayContaining([0, 1, 1, 2, 3, 5, 8]) + ) + expect(FibonacciIterative(-6)).toEqual( + expect.arrayContaining([0, 1, -1, 2, -3, 5, -8]) ) }) + it('should return number for FibonacciGenerator', () => { + const positive = FibonacciGenerator() + expect(positive.next().value).toBe(0) + expect(positive.next().value).toBe(1) + expect(positive.next().value).toBe(1) + expect(positive.next().value).toBe(2) + expect(positive.next().value).toBe(3) + expect(positive.next().value).toBe(5) + expect(positive.next().value).toBe(8) + + const negative = FibonacciGenerator(true) + expect(negative.next().value).toBe(0) + expect(negative.next().value).toBe(1) + expect(negative.next().value).toBe(-1) + expect(negative.next().value).toBe(2) + expect(negative.next().value).toBe(-3) + expect(negative.next().value).toBe(5) + expect(negative.next().value).toBe(-8) + }) + it('should return an array of numbers for FibonacciRecursive', () => { - expect(FibonacciRecursive(5)).toEqual( - expect.arrayContaining([1, 1, 2, 3, 5]) + expect(FibonacciRecursive(6)).toEqual( + expect.arrayContaining([0, 1, 1, 2, 3, 5, 8]) + ) + expect(FibonacciRecursive(-6)).toEqual( + expect.arrayContaining([-0, 1, -1, 2, -3, 5, -8]) ) }) it('should return number for FibonacciRecursiveDP', () => { - expect(FibonacciRecursiveDP(5)).toBe(5) + expect(FibonacciRecursiveDP(6)).toBe(8) + expect(FibonacciRecursiveDP(-6)).toBe(-8) }) it('should return an array of numbers for FibonacciDpWithoutRecursion', () => { - expect(FibonacciDpWithoutRecursion(5)).toEqual( - expect.arrayContaining([1, 1, 2, 3, 5]) + expect(FibonacciDpWithoutRecursion(6)).toEqual( + expect.arrayContaining([0, 1, 1, 2, 3, 5, 8]) + ) + expect(FibonacciDpWithoutRecursion(-6)).toEqual( + expect.arrayContaining([0, 1, -1, 2, -3, 5, -8]) ) }) @@ -36,5 +67,32 @@ describe('Fibonacci', () => { expect(FibonacciMatrixExpo(3)).toBe(2) expect(FibonacciMatrixExpo(4)).toBe(3) expect(FibonacciMatrixExpo(5)).toBe(5) + expect(FibonacciMatrixExpo(6)).toBe(8) + + expect(FibonacciMatrixExpo(-0)).toBe(-0) + expect(FibonacciMatrixExpo(-1)).toBe(1) + expect(FibonacciMatrixExpo(-2)).toBe(-1) + expect(FibonacciMatrixExpo(-3)).toBe(2) + expect(FibonacciMatrixExpo(-4)).toBe(-3) + expect(FibonacciMatrixExpo(-5)).toBe(5) + expect(FibonacciMatrixExpo(-6)).toBe(-8) + }) + + it('should return bigint for FibonacciMatrixExpo', () => { + expect(FibonacciMatrixExpo(0n)).toBe(0n) + expect(FibonacciMatrixExpo(1n)).toBe(1n) + expect(FibonacciMatrixExpo(2n)).toBe(1n) + expect(FibonacciMatrixExpo(3n)).toBe(2n) + expect(FibonacciMatrixExpo(4n)).toBe(3n) + expect(FibonacciMatrixExpo(5n)).toBe(5n) + expect(FibonacciMatrixExpo(6n)).toBe(8n) + + expect(FibonacciMatrixExpo(-0n)).toBe(0n) + expect(FibonacciMatrixExpo(-1n)).toBe(1n) + expect(FibonacciMatrixExpo(-2n)).toBe(-1n) + expect(FibonacciMatrixExpo(-3n)).toBe(2n) + expect(FibonacciMatrixExpo(-4n)).toBe(-3n) + expect(FibonacciMatrixExpo(-5n)).toBe(5n) + expect(FibonacciMatrixExpo(-6n)).toBe(-8n) }) })