Skip to content

Commit

Permalink
Merge pull request #56 from monkey0722/feture/algorithm
Browse files Browse the repository at this point in the history
Implement Various Data Structures and Algorithms with Node 22 Upgrade
  • Loading branch information
monkey0722 authored Dec 28, 2024
2 parents 4565407 + 843f925 commit 42cc2ee
Show file tree
Hide file tree
Showing 14 changed files with 1,535 additions and 9 deletions.
17 changes: 9 additions & 8 deletions .github/workflows/blank.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,19 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node: [20]
node: [22]
timeout-minutes: 300
steps:
- name: checkout pushed commit
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: run lint and test
uses: actions/setup-node@v1
- name: setup node
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- run: yarn install
- run: yarn typecheck
- run: yarn lint
- run: yarn test
- run: |
yarn install
yarn typecheck
yarn lint
yarn test
2 changes: 1 addition & 1 deletion .node-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
20.18.1
22.12.0
102 changes: 102 additions & 0 deletions algorithms/dp/knapsack.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import {Knapsack} from './knapsack';

describe('Knapsack', () => {
describe('solve (0-1 Knapsack)', () => {
test('should solve the 0-1 knapsack problem correctly', () => {
const items = [
{weight: 2, value: 3},
{weight: 3, value: 4},
{weight: 4, value: 5},
{weight: 5, value: 6},
];
const capacity = 5;
const result = Knapsack.solve(items, capacity);
expect(result.maxValue).toBe(7);
expect(result.selectedItems).toEqual([true, true, false, false]);
});
test('should return zero when capacity is zero', () => {
const items = [
{weight: 2, value: 3},
{weight: 3, value: 4},
];
const capacity = 0;
const result = Knapsack.solve(items, capacity);
expect(result.maxValue).toBe(0);
expect(result.selectedItems).toEqual([false, false]);
});

test('should solve correctly when items have zero weight', () => {
const items = [
{weight: 0, value: 5},
{weight: 2, value: 3},
];
const capacity = 2;
const result = Knapsack.solve(items, capacity);
expect(result.maxValue).toBe(8);
expect(result.selectedItems).toEqual([true, true]);
});
test('should solve correctly when items have zero value', () => {
const items = [
{weight: 1, value: 0},
{weight: 2, value: 0},
];
const capacity = 3;
const result = Knapsack.solve(items, capacity);
expect(result.maxValue).toBe(0);
expect(result.selectedItems).toEqual([false, false]);
});
test('should throw an error for negative weight or value', () => {
const items = [
{weight: -2, value: 3},
{weight: 3, value: 4},
];
const capacity = 5;
expect(() => Knapsack.solve(items, capacity)).toThrowError(
'Item weights and values must be non-negative'
);
});
});

describe('solveFractional (Fractional Knapsack)', () => {
test('should return zero when capacity is zero', () => {
const items = [
{weight: 10, value: 60},
{weight: 20, value: 100},
];
const capacity = 0;
const result = Knapsack.solveFractional(items, capacity);
expect(result.maxValue).toBe(0);
expect(result.fractions).toEqual([0, 0]);
});
test('should solve correctly when items have zero weight', () => {
const items = [
{weight: 0, value: 50},
{weight: 10, value: 60},
];
const capacity = 10;
const result = Knapsack.solveFractional(items, capacity);
expect(result.maxValue).toBe(110);
expect(result.fractions).toEqual([1, 1]);
});
test('should solve correctly when items have zero value', () => {
const items = [
{weight: 5, value: 0},
{weight: 10, value: 0},
];
const capacity = 15;
const result = Knapsack.solveFractional(items, capacity);
expect(result.maxValue).toBe(0);
expect(result.fractions).toEqual([0, 0]);
});
test('should throw an error for negative weight or value', () => {
const items = [
{weight: 5, value: -10},
{weight: 10, value: 50},
];
const capacity = 15;
expect(() => Knapsack.solveFractional(items, capacity)).toThrowError(
'Item weights and values must be non-negative'
);
});
});
});
122 changes: 122 additions & 0 deletions algorithms/dp/knapsack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
interface Item {
weight: number;
value: number;
}

export class Knapsack {
/**
* Solves the 0-1 knapsack problem.
* @param items Array of items, each with a weight and value.
* @param capacity The maximum weight capacity of the knapsack.
* @returns An object containing the maximum value and the selection status of each item.
* @throws {Error} If inputs are invalid.
*/
static solve(
items: Item[],
capacity: number
): {
maxValue: number;
selectedItems: boolean[];
} {
if (capacity < 0) {
throw new Error('Capacity must be non-negative');
}
for (const item of items) {
if (item.weight < 0 || item.value < 0) {
throw new Error('Item weights and values must be non-negative');
}
}

const n = items.length;
const dp: number[][] = Array.from({length: n + 1}, () =>
Array(capacity + 1).fill(0)
);

// Build the DP table
for (let i = 1; i <= n; i++) {
const item = items[i - 1];
for (let w = 0; w <= capacity; w++) {
if (item.weight <= w) {
dp[i][w] = Math.max(
dp[i - 1][w],
dp[i - 1][w - item.weight] + item.value
);
} else {
dp[i][w] = dp[i - 1][w];
}
}
}

// Trace back to find selected items
const selectedItems: boolean[] = Array(n).fill(false);
let remainingWeight = capacity;
for (let i = n; i > 0; i--) {
if (dp[i][remainingWeight] !== dp[i - 1][remainingWeight]) {
selectedItems[i - 1] = true;
remainingWeight -= items[i - 1].weight;
}
}

return {
maxValue: dp[n][capacity],
selectedItems,
};
}

/**
* Solves the fractional knapsack problem.
* @param items Array of items, each with a weight and value.
* @param capacity The maximum weight capacity of the knapsack.
* @returns An object containing the maximum value and the selection fractions of each item.
* @throws {Error} If inputs are invalid.
*/
static solveFractional(
items: Item[],
capacity: number
): {
maxValue: number;
fractions: number[];
} {
if (capacity < 0) {
throw new Error('Capacity must be non-negative');
}
for (const item of items) {
if (item.weight < 0 || item.value < 0) {
throw new Error('Item weights and values must be non-negative');
}
}

const n = items.length;
const sortedItems = items
.map((item, index) => ({
...item,
ratio: item.weight === 0 ? Infinity : item.value / item.weight,
index, // Preserve original index
}))
.sort((a, b) => b.ratio - a.ratio); // Sort by value-to-weight ratio

let remainingCapacity = capacity;
let totalValue = 0;
const fractions = Array(n).fill(0);

for (const item of sortedItems) {
if (item.value === 0 || remainingCapacity <= 0) {
continue; // Skip zero value items or if no capacity is left
}
if (remainingCapacity >= item.weight) {
fractions[item.index] = 1;
totalValue += item.value;
remainingCapacity -= item.weight;
} else {
const fraction = remainingCapacity / item.weight;
fractions[item.index] = fraction;
totalValue += item.value * fraction;
break;
}
}
return {
maxValue: totalValue,
fractions,
};
}
}
57 changes: 57 additions & 0 deletions algorithms/search/bellman-ford-search/bellmanFordSearch.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {BellmanFord} from './bellmanFordSearch';

describe('BellmanFord', () => {
test('should find shortest paths correctly for a valid graph', () => {
const edges = [
{source: 0, target: 1, weight: 4},
{source: 0, target: 2, weight: 5},
{source: 1, target: 2, weight: -3},
{source: 1, target: 3, weight: 6},
{source: 2, target: 3, weight: 1},
];
const result = BellmanFord.findShortestPaths(4, edges, 0);
expect(result).toEqual([0, 4, 1, 2]);
});
test('should detect negative weight cycle and return null', () => {
const edges = [
{source: 0, target: 1, weight: 1},
{source: 1, target: 2, weight: 1},
{source: 2, target: 0, weight: -3},
];
const result = BellmanFord.findShortestPaths(3, edges, 0);
expect(result).toBeNull();
});
test('should return null when reconstructing path for unreachable vertex', () => {
const edges = [
{source: 0, target: 1, weight: 4},
{source: 1, target: 2, weight: 3},
];
const path = BellmanFord.reconstructPath(4, edges, 0, 3);
expect(path).toBeNull();
});
test('should throw error for invalid vertex count', () => {
const edges = [
{source: 0, target: 1, weight: 4},
{source: 1, target: 2, weight: 3},
];
expect(() => BellmanFord.findShortestPaths(0, edges, 0)).toThrowError(
'Number of vertices must be positive'
);
});
test('should throw error for invalid edge vertices', () => {
const edges = [{source: 0, target: 5, weight: 4}];
expect(() => BellmanFord.findShortestPaths(4, edges, 0)).toThrowError(
'Edge contains an invalid vertex'
);
});
test('should handle large graph with no negative weight cycles', () => {
const edges = Array.from({length: 100}, (_, i) => ({
source: i,
target: (i + 1) % 100,
weight: 1,
}));
const result = BellmanFord.findShortestPaths(100, edges, 0);
expect(result).toHaveLength(100);
expect(result![99]).toBe(99);
});
});
Loading

0 comments on commit 42cc2ee

Please sign in to comment.