Skip to content

Commit 1b64ba6

Browse files
authored
Fibonacci.js overhaul (#1049)
1 parent 95a8ec0 commit 1b64ba6

File tree

2 files changed

+150
-53
lines changed

2 files changed

+150
-53
lines changed

Maths/Fibonacci.js

+85-46
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,67 @@
1-
const list = []
2-
3-
const FibonacciIterative = (nth) => {
4-
const sequence = []
5-
6-
if (nth >= 1) sequence.push(1)
7-
if (nth >= 2) sequence.push(1)
8-
9-
for (let i = 2; i < nth; i++) {
10-
sequence.push(sequence[i - 1] + sequence[i - 2])
1+
// https://en.wikipedia.org/wiki/Generalizations_of_Fibonacci_numbers#Extension_to_negative_integers
2+
const FibonacciIterative = (num) => {
3+
const isNeg = num < 0
4+
if (isNeg) num *= -1
5+
const sequence = [0]
6+
7+
if (num >= 1) sequence.push(1)
8+
if (num >= 2) sequence.push(isNeg ? -1 : 1)
9+
10+
for (let i = 2; i < num; i++) {
11+
sequence.push(
12+
isNeg ? sequence[i - 1] - sequence[i] : sequence[i] + sequence[i - 1]
13+
)
1114
}
1215

1316
return sequence
1417
}
1518

16-
const FibonacciRecursive = (number) => {
19+
const FibonacciGenerator = function * (neg) {
20+
let a = 0
21+
let b = 1
22+
yield a
23+
while (true) {
24+
yield b;
25+
[a, b] = neg ? [b, a - b] : [b, a + b]
26+
}
27+
}
28+
29+
const list = []
30+
const FibonacciRecursive = (num) => {
31+
const isNeg = num < 0
32+
if (isNeg) num *= -1
1733
return (() => {
1834
switch (list.length) {
1935
case 0:
20-
list.push(1)
21-
return FibonacciRecursive(number)
36+
list.push(0)
37+
return FibonacciRecursive(num)
2238
case 1:
2339
list.push(1)
24-
return FibonacciRecursive(number)
25-
case number:
40+
return FibonacciRecursive(num)
41+
case num + 1:
2642
return list
2743
default:
28-
list.push(list[list.length - 1] + list[list.length - 2])
29-
return FibonacciRecursive(number)
44+
list.push(list.at(-1) + list.at(-2))
45+
return FibonacciRecursive(num)
3046
}
31-
})()
47+
})().map((fib, i) => fib * (isNeg ? (-1) ** (i + 1) : 1))
3248
}
3349

3450
const dict = new Map()
35-
3651
const FibonacciRecursiveDP = (stairs) => {
37-
if (stairs <= 0) return 0
38-
if (stairs === 1) return 1
52+
const isNeg = stairs < 0
53+
if (isNeg) stairs *= -1
54+
55+
if (stairs <= 1) return stairs
3956

4057
// Memoize stair count
41-
if (dict.has(stairs)) return dict.get(stairs)
58+
if (dict.has(stairs)) return (isNeg ? (-1) ** (stairs + 1) : 1) * dict.get(stairs)
4259

43-
const res =
44-
FibonacciRecursiveDP(stairs - 1) + FibonacciRecursiveDP(stairs - 2)
60+
const res = FibonacciRecursiveDP(stairs - 1) + FibonacciRecursiveDP(stairs - 2)
4561

4662
dict.set(stairs, res)
4763

48-
return res
64+
return (isNeg ? (-1) ** (stairs + 1) : 1) * res
4965
}
5066

5167
// Algorithms
@@ -59,12 +75,16 @@ const FibonacciRecursiveDP = (stairs) => {
5975
// a function of the number of input bits
6076
// @Satzyakiz
6177

62-
const FibonacciDpWithoutRecursion = (number) => {
63-
const table = []
78+
const FibonacciDpWithoutRecursion = (num) => {
79+
const isNeg = num < 0
80+
if (isNeg) num *= -1
81+
const table = [0]
6482
table.push(1)
65-
table.push(1)
66-
for (let i = 2; i < number; ++i) {
67-
table.push(table[i - 1] + table[i - 2])
83+
table.push(isNeg ? -1 : 1)
84+
for (let i = 2; i < num; ++i) {
85+
table.push(
86+
isNeg ? table[i - 1] - table[i] : table[i] + table[i - 1]
87+
)
6888
}
6989
return table
7090
}
@@ -76,24 +96,31 @@ const copyMatrix = (A) => {
7696
}
7797

7898
const Identity = (size) => {
99+
const isBigInt = typeof size === 'bigint'
100+
const ZERO = isBigInt ? 0n : 0
101+
const ONE = isBigInt ? 1n : 1
102+
size = Number(size)
79103
const I = Array(size).fill(null).map(() => Array(size).fill())
80104
return I.map((row, rowIdx) => row.map((_col, colIdx) => {
81-
return rowIdx === colIdx ? 1 : 0
105+
return rowIdx === colIdx ? ONE : ZERO
82106
}))
83107
}
84108

85109
// A of size (l x m) and B of size (m x n)
86-
// product C will be of size (l x n)
110+
// product C will be of size (l x n).
111+
// both matrices must have same-type numeric values
112+
// either both BigInt or both Number
87113
const matrixMultiply = (A, B) => {
88114
A = copyMatrix(A)
89115
B = copyMatrix(B)
116+
const isBigInt = typeof A[0][0] === 'bigint'
90117
const l = A.length
91118
const m = B.length
92119
const n = B[0].length // Assuming non-empty matrices
93120
const C = Array(l).fill(null).map(() => Array(n).fill())
94121
for (let i = 0; i < l; i++) {
95122
for (let j = 0; j < n; j++) {
96-
C[i][j] = 0
123+
C[i][j] = isBigInt ? 0n : 0
97124
for (let k = 0; k < m; k++) {
98125
C[i][j] += A[i][k] * B[k][j]
99126
}
@@ -110,18 +137,25 @@ const matrixMultiply = (A, B) => {
110137
// A is a square matrix
111138
const matrixExpo = (A, n) => {
112139
A = copyMatrix(A)
140+
const isBigInt = typeof n === 'bigint'
141+
const ZERO = isBigInt ? 0n : 0
142+
const TWO = isBigInt ? 2n : 2
113143

114144
// Just like Binary exponentiation mentioned in ./BinaryExponentiationIterative.js
115-
let result = Identity(A.length) // Identity matrix
116-
while (n > 0) {
117-
if (n % 2 !== 0) result = matrixMultiply(result, A)
118-
n = Math.floor(n / 2)
119-
if (n > 0) A = matrixMultiply(A, A)
145+
let result = Identity((isBigInt ? BigInt : Number)(A.length)) // Identity matrix
146+
while (n > ZERO) {
147+
if (n % TWO !== ZERO) result = matrixMultiply(result, A)
148+
n /= TWO
149+
if (!isBigInt) n = Math.floor(n)
150+
if (n > ZERO) A = matrixMultiply(A, A)
120151
}
121152
return result
122153
}
123154

124-
const FibonacciMatrixExpo = (n) => {
155+
const FibonacciMatrixExpo = (num) => {
156+
const isBigInt = typeof num === 'bigint'
157+
const ZERO = isBigInt ? 0n : 0
158+
const ONE = isBigInt ? 1n : 1
125159
// F(0) = 0, F(1) = 1
126160
// F(n) = F(n-1) + F(n-2)
127161
// Consider below matrix multiplication:
@@ -134,23 +168,28 @@ const FibonacciMatrixExpo = (n) => {
134168
// or F(n, n-1) = A * A * F(n-2, n-3)
135169
// or F(n, n-1) = pow(A, n-1) * F(1, 0)
136170

137-
if (n === 0) return 0
171+
if (num === ZERO) return num
172+
173+
const isNeg = num < 0
174+
if (isNeg) num *= -ONE
138175

139176
const A = [
140-
[1, 1],
141-
[1, 0]
177+
[ONE, ONE],
178+
[ONE, ZERO]
142179
]
143-
const poweredA = matrixExpo(A, n - 1) // A raised to the power n-1
180+
181+
const poweredA = matrixExpo(A, num - ONE) // A raised to the power n-1
144182
let F = [
145-
[1],
146-
[0]
183+
[ONE],
184+
[ZERO]
147185
]
148186
F = matrixMultiply(poweredA, F)
149-
return F[0][0]
187+
return F[0][0] * (isNeg ? (-ONE) ** (num + ONE) : ONE)
150188
}
151189

152190
export { FibonacciDpWithoutRecursion }
153191
export { FibonacciIterative }
192+
export { FibonacciGenerator }
154193
export { FibonacciRecursive }
155194
export { FibonacciRecursiveDP }
156195
export { FibonacciMatrixExpo }

Maths/test/Fibonacci.test.js

+65-7
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,61 @@ import {
22
FibonacciDpWithoutRecursion,
33
FibonacciRecursiveDP,
44
FibonacciIterative,
5+
FibonacciGenerator,
56
FibonacciRecursive,
67
FibonacciMatrixExpo
78
} from '../Fibonacci'
89

910
describe('Fibonacci', () => {
1011
it('should return an array of numbers for FibonacciIterative', () => {
11-
expect(FibonacciIterative(5)).toEqual(
12-
expect.arrayContaining([1, 1, 2, 3, 5])
12+
expect(FibonacciIterative(6)).toEqual(
13+
expect.arrayContaining([0, 1, 1, 2, 3, 5, 8])
14+
)
15+
expect(FibonacciIterative(-6)).toEqual(
16+
expect.arrayContaining([0, 1, -1, 2, -3, 5, -8])
1317
)
1418
})
1519

20+
it('should return number for FibonacciGenerator', () => {
21+
const positive = FibonacciGenerator()
22+
expect(positive.next().value).toBe(0)
23+
expect(positive.next().value).toBe(1)
24+
expect(positive.next().value).toBe(1)
25+
expect(positive.next().value).toBe(2)
26+
expect(positive.next().value).toBe(3)
27+
expect(positive.next().value).toBe(5)
28+
expect(positive.next().value).toBe(8)
29+
30+
const negative = FibonacciGenerator(true)
31+
expect(negative.next().value).toBe(0)
32+
expect(negative.next().value).toBe(1)
33+
expect(negative.next().value).toBe(-1)
34+
expect(negative.next().value).toBe(2)
35+
expect(negative.next().value).toBe(-3)
36+
expect(negative.next().value).toBe(5)
37+
expect(negative.next().value).toBe(-8)
38+
})
39+
1640
it('should return an array of numbers for FibonacciRecursive', () => {
17-
expect(FibonacciRecursive(5)).toEqual(
18-
expect.arrayContaining([1, 1, 2, 3, 5])
41+
expect(FibonacciRecursive(6)).toEqual(
42+
expect.arrayContaining([0, 1, 1, 2, 3, 5, 8])
43+
)
44+
expect(FibonacciRecursive(-6)).toEqual(
45+
expect.arrayContaining([-0, 1, -1, 2, -3, 5, -8])
1946
)
2047
})
2148

2249
it('should return number for FibonacciRecursiveDP', () => {
23-
expect(FibonacciRecursiveDP(5)).toBe(5)
50+
expect(FibonacciRecursiveDP(6)).toBe(8)
51+
expect(FibonacciRecursiveDP(-6)).toBe(-8)
2452
})
2553

2654
it('should return an array of numbers for FibonacciDpWithoutRecursion', () => {
27-
expect(FibonacciDpWithoutRecursion(5)).toEqual(
28-
expect.arrayContaining([1, 1, 2, 3, 5])
55+
expect(FibonacciDpWithoutRecursion(6)).toEqual(
56+
expect.arrayContaining([0, 1, 1, 2, 3, 5, 8])
57+
)
58+
expect(FibonacciDpWithoutRecursion(-6)).toEqual(
59+
expect.arrayContaining([0, 1, -1, 2, -3, 5, -8])
2960
)
3061
})
3162

@@ -36,5 +67,32 @@ describe('Fibonacci', () => {
3667
expect(FibonacciMatrixExpo(3)).toBe(2)
3768
expect(FibonacciMatrixExpo(4)).toBe(3)
3869
expect(FibonacciMatrixExpo(5)).toBe(5)
70+
expect(FibonacciMatrixExpo(6)).toBe(8)
71+
72+
expect(FibonacciMatrixExpo(-0)).toBe(-0)
73+
expect(FibonacciMatrixExpo(-1)).toBe(1)
74+
expect(FibonacciMatrixExpo(-2)).toBe(-1)
75+
expect(FibonacciMatrixExpo(-3)).toBe(2)
76+
expect(FibonacciMatrixExpo(-4)).toBe(-3)
77+
expect(FibonacciMatrixExpo(-5)).toBe(5)
78+
expect(FibonacciMatrixExpo(-6)).toBe(-8)
79+
})
80+
81+
it('should return bigint for FibonacciMatrixExpo', () => {
82+
expect(FibonacciMatrixExpo(0n)).toBe(0n)
83+
expect(FibonacciMatrixExpo(1n)).toBe(1n)
84+
expect(FibonacciMatrixExpo(2n)).toBe(1n)
85+
expect(FibonacciMatrixExpo(3n)).toBe(2n)
86+
expect(FibonacciMatrixExpo(4n)).toBe(3n)
87+
expect(FibonacciMatrixExpo(5n)).toBe(5n)
88+
expect(FibonacciMatrixExpo(6n)).toBe(8n)
89+
90+
expect(FibonacciMatrixExpo(-0n)).toBe(0n)
91+
expect(FibonacciMatrixExpo(-1n)).toBe(1n)
92+
expect(FibonacciMatrixExpo(-2n)).toBe(-1n)
93+
expect(FibonacciMatrixExpo(-3n)).toBe(2n)
94+
expect(FibonacciMatrixExpo(-4n)).toBe(-3n)
95+
expect(FibonacciMatrixExpo(-5n)).toBe(5n)
96+
expect(FibonacciMatrixExpo(-6n)).toBe(-8n)
3997
})
4098
})

0 commit comments

Comments
 (0)