-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
515 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
/** | ||
* Order doesn't matter. | ||
*/ | ||
/* | ||
export const combinations = (n: number) => { | ||
}; | ||
*/ | ||
|
||
/** | ||
* Order does matter. | ||
* A Permutation is an ordered Combination. | ||
*/ | ||
/* | ||
export const permutations = (n: number, isRepetitionAllowed: boolean) => { | ||
if(isRepetitionAllowed) { | ||
// n! | ||
} | ||
};*/ | ||
|
||
/** | ||
* Permutations with repetitions: | ||
* - "n" is the number of things to choose from | ||
* - we choose "r" of them | ||
* - order matters | ||
* - repetition is allowed | ||
* | ||
* Formula: | ||
* -------- | ||
* n^r | ||
* | ||
* Intuition: | ||
* ---------- | ||
* In other words, there are n possibilities for the first choice, | ||
* THEN there are n possibilities for the second choice, and so on, | ||
* multiplying each time. | ||
* | ||
* A Permutation is an ordered Combination. | ||
*/ | ||
export const permutationsWithRepetition = (n: number, r: number) => { | ||
if (n < 0 || r < 0) { | ||
throw new Error('Both n and r should be non-negative integers.'); | ||
} | ||
if (!Number.isInteger(n) || !Number.isInteger(r)) { | ||
throw new Error('Both n and r should be integers.'); | ||
} | ||
|
||
return n ** r; | ||
}; | ||
|
||
/** | ||
* Permutations without repetitions: | ||
* - "n" is the number of things to choose from | ||
* - we choose "r" of them | ||
* - order matters | ||
* - no repetitions | ||
* | ||
* Formula: | ||
* -------- | ||
* P(n,r) = n! / (n − r)! | ||
* | ||
* Intuition: | ||
* ---------- | ||
* In this case, we have to reduce the number of available choices each time. | ||
* After choosing, say, number "14" we can't choose it again. | ||
* So, our first choice has 16 possibilities, and our next choice has 15 possibilities, | ||
* then 14, 13, 12, 11, ... etc. | ||
* | ||
* A Permutation is an ordered Combination. | ||
*/ | ||
export const permutationsWithoutRepetition = (n: number, r: number) => { | ||
if (n < 0 || r < 0) { | ||
throw new Error('Both n and r should be non-negative integers.'); | ||
} | ||
if (!Number.isInteger(n) || !Number.isInteger(r)) { | ||
throw new Error('Both n and r should be integers.'); | ||
} | ||
|
||
return n ** r; | ||
}; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
/** | ||
* The simplest and straightforward method | ||
* but can become inefficient for extremely large numbers | ||
* due to growing computational time. | ||
*/ | ||
export const factorialIterative = (n: number, start = 0): number => { | ||
|
||
if (n < 0) { | ||
throw new Error('Factorial is not defined for negative numbers.'); | ||
} | ||
|
||
if (start > n) { | ||
throw new Error('Start cannot be greater than n.'); | ||
} | ||
|
||
if (start < 0) { | ||
throw new Error('Start must be non-negative.'); | ||
} | ||
|
||
if (start === 0) { | ||
if (n === 0) { | ||
return 1; // 0! is defined as 1 | ||
} | ||
else { | ||
start = 1; // Adjust start to 1 to prevent multiplication by zero | ||
} | ||
} | ||
|
||
let result = 1; | ||
for (let i = start; i <= n; i++) { | ||
result *= i; | ||
} | ||
return result; | ||
}; | ||
|
||
/** | ||
* A recursive approach is a classic method for calculating factorials | ||
* but can lead to stack overflow errors | ||
* for very large inputs because of deep recursion. | ||
* However, it's a direct translation | ||
* of the mathematical definition of factorial. | ||
*/ | ||
export const factorialRecursive = (n: number, start = 0): number => { | ||
if (n < 0) { | ||
throw new Error('Factorial is not defined for negative numbers.'); | ||
} | ||
|
||
if (start > n) { | ||
throw new Error('Start cannot be greater than n.'); | ||
} | ||
|
||
if (start < 0) { | ||
throw new Error('Start must be non-negative.'); | ||
} | ||
|
||
if (start === 0) { | ||
if (n === 0) { | ||
return 1; // 0! is defined as 1 | ||
} | ||
else { | ||
start = 1; // Adjust start to 1 to prevent multiplication by zero | ||
} | ||
} | ||
|
||
// Base case: when n reaches start, return start instead of further reducing | ||
if (n === start) return start; | ||
|
||
// Recursive call | ||
return n * factorialRecursive(n - 1, start); | ||
}; | ||
|
||
/** | ||
* Memoization (Top-Down Dynamic Programming). | ||
*/ | ||
export const factorialMemoized = (n: number, start = 0): number => { | ||
if (n < 0) { | ||
throw new Error('Factorial is not defined for negative numbers.'); | ||
} | ||
|
||
if (start > n) { | ||
throw new Error('Start cannot be greater than n.'); | ||
} | ||
|
||
if (start < 0) { | ||
throw new Error('Start must be non-negative.'); | ||
} | ||
|
||
if (start === 0) { | ||
if (n === 0) { | ||
return 1; // 0! is defined as 1 | ||
} | ||
else { | ||
start = 1; // Adjust start to 1 to prevent multiplication by zero | ||
} | ||
} | ||
|
||
const memo = new Map<number, number>(); | ||
|
||
const traverse = (num: number, end: number): number => { | ||
if (num < end) return 1; // Adjust the base case to return 1 if we're below 'end' | ||
// if (num === 0) return 1; | ||
|
||
if (memo.has(num)) return memo.get(num) ?? 1; | ||
|
||
if (num === end) { | ||
memo.set(num, num); | ||
return num; | ||
} | ||
|
||
const result = num * traverse(num - 1, end); | ||
|
||
memo.set(num, result); | ||
|
||
return result; | ||
}; | ||
|
||
return traverse(n, start); | ||
}; | ||
|
||
/** | ||
* Tabulation (Bottom-Up Dynamic Programming). | ||
*/ | ||
export const factorial = (n: number, start = 0): number => { | ||
if (n < 0) { | ||
throw new Error('Factorial is not defined for negative numbers.'); | ||
} | ||
|
||
if (start > n) { | ||
throw new Error('Start cannot be greater than n.'); | ||
} | ||
|
||
if (start < 0) { | ||
throw new Error('Start must be non-negative.'); | ||
} | ||
|
||
if (start === n) { | ||
return n === 0 ? 1 : n; // If start == n and n == 0, return 1 (0!), otherwise return n | ||
} | ||
|
||
if (start === 0) { | ||
start = 1; | ||
} | ||
|
||
const table = []; // new Array(n + 1); | ||
table[0] = 1; | ||
for (let i = 1; i <= n; i++) { | ||
table[i] = table[i - 1] * i; | ||
} | ||
return table[n] / table[start - 1]; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { permutationsWithRepetition } from '../src/main/combinatorics/combinatorics'; | ||
|
||
describe('Combinatorics', () => { | ||
|
||
describe('permutationsWithRepetition()', () => { | ||
it('calculates correct permutations for valid inputs', () => { | ||
expect(permutationsWithRepetition(3, 2)).toBe(9); // 3^2 | ||
expect(permutationsWithRepetition(4, 3)).toBe(64); // 4^3 | ||
expect(permutationsWithRepetition(5, 1)).toBe(5); // 5^1 | ||
expect(permutationsWithRepetition(2, 0)).toBe(1); // 2^0 should be 1 | ||
}); | ||
|
||
it('throws an error for negative values of n or r', () => { | ||
expect(() => permutationsWithRepetition(-1, 2)).toThrow("Both n and r should be non-negative integers."); | ||
expect(() => permutationsWithRepetition(3, -1)).toThrow("Both n and r should be non-negative integers."); | ||
}); | ||
|
||
it('throws an error for non-integer values of n or r', () => { | ||
expect(() => permutationsWithRepetition(3.5, 2)).toThrow("Both n and r should be integers."); | ||
expect(() => permutationsWithRepetition(3, 2.7)).toThrow("Both n and r should be integers."); | ||
}); | ||
|
||
it('handles edge cases with zero correctly', () => { | ||
expect(permutationsWithRepetition(0, 5)).toBe(0); // 0^5 should be 0 | ||
expect(permutationsWithRepetition(5, 0)).toBe(1); // 5^0 should be 1 | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.