Skip to content

Fibonacci.js overhaul #1049

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

Merged
merged 25 commits into from
Jun 27, 2022
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
131 changes: 85 additions & 46 deletions Maths/Fibonacci.js
Original file line number Diff line number Diff line change
@@ -1,51 +1,67 @@
const list = []

const FibonacciIterative = (nth) => {
const sequence = []

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])
// 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 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
Expand All @@ -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
}
Expand All @@ -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]
}
Expand All @@ -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:
Expand All @@ -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 }
72 changes: 65 additions & 7 deletions Maths/test/Fibonacci.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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])
)
})

Expand All @@ -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)
})
})