Skip to content

Commit

Permalink
factorial and combination
Browse files Browse the repository at this point in the history
  • Loading branch information
mzusin committed Apr 23, 2024
1 parent bfb07d4 commit bad8a2a
Show file tree
Hide file tree
Showing 14 changed files with 515 additions and 21 deletions.
7 changes: 7 additions & 0 deletions dist/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,4 +372,11 @@ declare module 'mz-math' {

export const naturalNumbersSum1ToN: (n: number) => number;
export const naturalNumbersSumMToN: (m: number, n: number) => number;

export const factorialIterative: (n: number, start?: number) => number;
export const factorialRecursive: (n: number, start?: number) => number;
export const factorialMemoized: (n: number, start?: number) => number;
export const factorial: (n: number, start?: number) => number;

export const permutationsWithRepetition: (n: number, r: number) => number;
}
4 changes: 2 additions & 2 deletions dist/mz-math.esm.js

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions dist/mz-math.esm.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/mz-math.min.js

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions dist/mz-math.min.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/mz-math.node.cjs

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions dist/mz-math.node.cjs.map

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion src/index-esm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ export * from './main/circle-ellipse';
export * from './main/sequence';
export * from './main/statistics';
export * from './main/ml';
export * from './main/series';
export * from './main/series';
export * from './main/combinatorics/factorial';
export * from './main/combinatorics/combinatorics';
8 changes: 7 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import * as sequence from './main/sequence';
import * as statistics from './main/statistics';
import * as ml from './main/ml';
import * as series from './main/series';
import * as factorial from './main/combinatorics/factorial';
import * as combinatorics from './main/combinatorics/combinatorics';

const api = {
...vector,
Expand All @@ -44,6 +46,8 @@ const api = {
...statistics,
...ml,
...series,
...factorial,
...combinatorics,
};

declare global {
Expand Down Expand Up @@ -75,4 +79,6 @@ export * from './main/circle-ellipse';
export * from './main/sequence';
export * from './main/statistics';
export * from './main/ml';
export * from './main/series';
export * from './main/series';
export * from './main/combinatorics/factorial';
export * from './main/combinatorics/combinatorics';
81 changes: 81 additions & 0 deletions src/main/combinatorics/combinatorics.ts
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;
};

150 changes: 150 additions & 0 deletions src/main/combinatorics/factorial.ts
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];
};
28 changes: 28 additions & 0 deletions test/combinatorics.test.ts
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
});
});
});
Loading

0 comments on commit bad8a2a

Please sign in to comment.