Files
JavaScript/Maths/Fibonacci.js
Roland Hummel 86d333ee94 feat: Test running overhaul, switch to Prettier & reformat everything (#1407)
* chore: Switch to Node 20 + Vitest

* chore: migrate to vitest mock functions

* chore: code style (switch to prettier)

* test: re-enable long-running test

Seems the switch to Node 20 and Vitest has vastly improved the code's and / or the test's runtime!

see #1193

* chore: code style

* chore: fix failing tests

* Updated Documentation in README.md

* Update contribution guidelines to state usage of Prettier

* fix: set prettier printWidth back to 80

* chore: apply updated code style automatically

* fix: set prettier line endings to lf again

* chore: apply updated code style automatically

---------

Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com>
Co-authored-by: Lars Müller <34514239+appgurueu@users.noreply.github.com>
2023-10-04 02:38:19 +05:30

210 lines
5.4 KiB
JavaScript

// 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]
if (num >= 1) sequence.push(1)
if (num >= 2) sequence.push(isNeg ? -1 : 1)
for (let i = 2; i < num; i++) {
sequence.push(
isNeg ? sequence[i - 1] - sequence[i] : sequence[i] + sequence[i - 1]
)
}
return sequence
}
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(0)
return FibonacciRecursive(num)
case 1:
list.push(1)
return FibonacciRecursive(num)
case num + 1:
return list
default:
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) => {
const isNeg = stairs < 0
if (isNeg) stairs *= -1
if (stairs <= 1) return stairs
// Memoize stair count
if (dict.has(stairs))
return (isNeg ? (-1) ** (stairs + 1) : 1) * dict.get(stairs)
const res =
FibonacciRecursiveDP(stairs - 1) + FibonacciRecursiveDP(stairs - 2)
dict.set(stairs, res)
return (isNeg ? (-1) ** (stairs + 1) : 1) * res
}
// Algorithms
// Calculates Fibonacci(n) such that Fibonacci(n) = Fibonacci(n - 1) + Fibonacci(n - 2)
// Fibonacci(0) = Fibonacci(1) = 1
// Uses a bottom up dynamic programming approach
// Solve each sub-problem once, using results of previous sub-problems
// which are n-1 and n-2 for Fibonacci numbers
// Although this algorithm is linear in space and time as a function
// of the input value n, it is exponential in the size of n as
// a function of the number of input bits
// @Satzyakiz
const FibonacciDpWithoutRecursion = (num) => {
const isNeg = num < 0
if (isNeg) num *= -1
const table = [0]
table.push(1)
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
}
// Using Matrix exponentiation to find n-th fibonacci in O(log n) time
const copyMatrix = (A) => {
return A.map((row) => row.map((cell) => cell))
}
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 ? ONE : ZERO
})
)
}
// A of size (l x m) and B of size (m 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] = isBigInt ? 0n : 0
for (let k = 0; k < m; k++) {
C[i][j] += A[i][k] * B[k][j]
}
}
}
return C
}
/**
* Computes A raised to the power n i.e. pow(A, n) where A is a square matrix
* @param {*} A the square matrix
* @param {*} n the exponent
*/
// 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((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 = (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:
// | F(n) | |1 1| |F(n-1)|
// | | = | | * | |
// |F(n-1)| |1 0| |F(n-2)|
// Let's rewrite it as F(n, n-1) = A * F(n-1, n-2)
// 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 (num === ZERO) return num
const isNeg = num < 0
if (isNeg) num *= -ONE
const A = [
[ONE, ONE],
[ONE, ZERO]
]
const poweredA = matrixExpo(A, num - ONE) // A raised to the power n-1
let F = [[ONE], [ZERO]]
F = matrixMultiply(poweredA, F)
return F[0][0] * (isNeg ? (-ONE) ** (num + ONE) : ONE)
}
/*
Resource : https://math.hmc.edu/funfacts/fibonacci-number-formula/
*/
const sqrt5 = Math.sqrt(5)
const phi = (1 + sqrt5) / 2
const psi = (1 - sqrt5) / 2
const FibonacciUsingFormula = (n) => Math.round((phi ** n - psi ** n) / sqrt5)
export { FibonacciDpWithoutRecursion }
export { FibonacciIterative }
export { FibonacciGenerator }
export { FibonacciRecursive }
export { FibonacciRecursiveDP }
export { FibonacciMatrixExpo }
export { FibonacciUsingFormula }