Skip to content

Commit 42cc2ee

Browse files
authored
Merge pull request #56 from monkey0722/feture/algorithm
Implement Various Data Structures and Algorithms with Node 22 Upgrade
2 parents 4565407 + 843f925 commit 42cc2ee

File tree

14 files changed

+1535
-9
lines changed

14 files changed

+1535
-9
lines changed

.github/workflows/blank.yml

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,19 @@ jobs:
1313
runs-on: ubuntu-latest
1414
strategy:
1515
matrix:
16-
node: [20]
16+
node: [22]
1717
timeout-minutes: 300
1818
steps:
1919
- name: checkout pushed commit
20-
uses: actions/checkout@v2
20+
uses: actions/checkout@v3
2121
with:
2222
ref: ${{ github.event.pull_request.head.sha }}
23-
- name: run lint and test
24-
uses: actions/setup-node@v1
23+
- name: setup node
24+
uses: actions/setup-node@v3
2525
with:
2626
node-version: ${{ matrix.node }}
27-
- run: yarn install
28-
- run: yarn typecheck
29-
- run: yarn lint
30-
- run: yarn test
27+
- run: |
28+
yarn install
29+
yarn typecheck
30+
yarn lint
31+
yarn test

.node-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
20.18.1
1+
22.12.0

algorithms/dp/knapsack.test.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import {Knapsack} from './knapsack';
2+
3+
describe('Knapsack', () => {
4+
describe('solve (0-1 Knapsack)', () => {
5+
test('should solve the 0-1 knapsack problem correctly', () => {
6+
const items = [
7+
{weight: 2, value: 3},
8+
{weight: 3, value: 4},
9+
{weight: 4, value: 5},
10+
{weight: 5, value: 6},
11+
];
12+
const capacity = 5;
13+
const result = Knapsack.solve(items, capacity);
14+
expect(result.maxValue).toBe(7);
15+
expect(result.selectedItems).toEqual([true, true, false, false]);
16+
});
17+
test('should return zero when capacity is zero', () => {
18+
const items = [
19+
{weight: 2, value: 3},
20+
{weight: 3, value: 4},
21+
];
22+
const capacity = 0;
23+
const result = Knapsack.solve(items, capacity);
24+
expect(result.maxValue).toBe(0);
25+
expect(result.selectedItems).toEqual([false, false]);
26+
});
27+
28+
test('should solve correctly when items have zero weight', () => {
29+
const items = [
30+
{weight: 0, value: 5},
31+
{weight: 2, value: 3},
32+
];
33+
const capacity = 2;
34+
const result = Knapsack.solve(items, capacity);
35+
expect(result.maxValue).toBe(8);
36+
expect(result.selectedItems).toEqual([true, true]);
37+
});
38+
test('should solve correctly when items have zero value', () => {
39+
const items = [
40+
{weight: 1, value: 0},
41+
{weight: 2, value: 0},
42+
];
43+
const capacity = 3;
44+
const result = Knapsack.solve(items, capacity);
45+
expect(result.maxValue).toBe(0);
46+
expect(result.selectedItems).toEqual([false, false]);
47+
});
48+
test('should throw an error for negative weight or value', () => {
49+
const items = [
50+
{weight: -2, value: 3},
51+
{weight: 3, value: 4},
52+
];
53+
const capacity = 5;
54+
expect(() => Knapsack.solve(items, capacity)).toThrowError(
55+
'Item weights and values must be non-negative'
56+
);
57+
});
58+
});
59+
60+
describe('solveFractional (Fractional Knapsack)', () => {
61+
test('should return zero when capacity is zero', () => {
62+
const items = [
63+
{weight: 10, value: 60},
64+
{weight: 20, value: 100},
65+
];
66+
const capacity = 0;
67+
const result = Knapsack.solveFractional(items, capacity);
68+
expect(result.maxValue).toBe(0);
69+
expect(result.fractions).toEqual([0, 0]);
70+
});
71+
test('should solve correctly when items have zero weight', () => {
72+
const items = [
73+
{weight: 0, value: 50},
74+
{weight: 10, value: 60},
75+
];
76+
const capacity = 10;
77+
const result = Knapsack.solveFractional(items, capacity);
78+
expect(result.maxValue).toBe(110);
79+
expect(result.fractions).toEqual([1, 1]);
80+
});
81+
test('should solve correctly when items have zero value', () => {
82+
const items = [
83+
{weight: 5, value: 0},
84+
{weight: 10, value: 0},
85+
];
86+
const capacity = 15;
87+
const result = Knapsack.solveFractional(items, capacity);
88+
expect(result.maxValue).toBe(0);
89+
expect(result.fractions).toEqual([0, 0]);
90+
});
91+
test('should throw an error for negative weight or value', () => {
92+
const items = [
93+
{weight: 5, value: -10},
94+
{weight: 10, value: 50},
95+
];
96+
const capacity = 15;
97+
expect(() => Knapsack.solveFractional(items, capacity)).toThrowError(
98+
'Item weights and values must be non-negative'
99+
);
100+
});
101+
});
102+
});

algorithms/dp/knapsack.ts

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
interface Item {
2+
weight: number;
3+
value: number;
4+
}
5+
6+
export class Knapsack {
7+
/**
8+
* Solves the 0-1 knapsack problem.
9+
* @param items Array of items, each with a weight and value.
10+
* @param capacity The maximum weight capacity of the knapsack.
11+
* @returns An object containing the maximum value and the selection status of each item.
12+
* @throws {Error} If inputs are invalid.
13+
*/
14+
static solve(
15+
items: Item[],
16+
capacity: number
17+
): {
18+
maxValue: number;
19+
selectedItems: boolean[];
20+
} {
21+
if (capacity < 0) {
22+
throw new Error('Capacity must be non-negative');
23+
}
24+
for (const item of items) {
25+
if (item.weight < 0 || item.value < 0) {
26+
throw new Error('Item weights and values must be non-negative');
27+
}
28+
}
29+
30+
const n = items.length;
31+
const dp: number[][] = Array.from({length: n + 1}, () =>
32+
Array(capacity + 1).fill(0)
33+
);
34+
35+
// Build the DP table
36+
for (let i = 1; i <= n; i++) {
37+
const item = items[i - 1];
38+
for (let w = 0; w <= capacity; w++) {
39+
if (item.weight <= w) {
40+
dp[i][w] = Math.max(
41+
dp[i - 1][w],
42+
dp[i - 1][w - item.weight] + item.value
43+
);
44+
} else {
45+
dp[i][w] = dp[i - 1][w];
46+
}
47+
}
48+
}
49+
50+
// Trace back to find selected items
51+
const selectedItems: boolean[] = Array(n).fill(false);
52+
let remainingWeight = capacity;
53+
for (let i = n; i > 0; i--) {
54+
if (dp[i][remainingWeight] !== dp[i - 1][remainingWeight]) {
55+
selectedItems[i - 1] = true;
56+
remainingWeight -= items[i - 1].weight;
57+
}
58+
}
59+
60+
return {
61+
maxValue: dp[n][capacity],
62+
selectedItems,
63+
};
64+
}
65+
66+
/**
67+
* Solves the fractional knapsack problem.
68+
* @param items Array of items, each with a weight and value.
69+
* @param capacity The maximum weight capacity of the knapsack.
70+
* @returns An object containing the maximum value and the selection fractions of each item.
71+
* @throws {Error} If inputs are invalid.
72+
*/
73+
static solveFractional(
74+
items: Item[],
75+
capacity: number
76+
): {
77+
maxValue: number;
78+
fractions: number[];
79+
} {
80+
if (capacity < 0) {
81+
throw new Error('Capacity must be non-negative');
82+
}
83+
for (const item of items) {
84+
if (item.weight < 0 || item.value < 0) {
85+
throw new Error('Item weights and values must be non-negative');
86+
}
87+
}
88+
89+
const n = items.length;
90+
const sortedItems = items
91+
.map((item, index) => ({
92+
...item,
93+
ratio: item.weight === 0 ? Infinity : item.value / item.weight,
94+
index, // Preserve original index
95+
}))
96+
.sort((a, b) => b.ratio - a.ratio); // Sort by value-to-weight ratio
97+
98+
let remainingCapacity = capacity;
99+
let totalValue = 0;
100+
const fractions = Array(n).fill(0);
101+
102+
for (const item of sortedItems) {
103+
if (item.value === 0 || remainingCapacity <= 0) {
104+
continue; // Skip zero value items or if no capacity is left
105+
}
106+
if (remainingCapacity >= item.weight) {
107+
fractions[item.index] = 1;
108+
totalValue += item.value;
109+
remainingCapacity -= item.weight;
110+
} else {
111+
const fraction = remainingCapacity / item.weight;
112+
fractions[item.index] = fraction;
113+
totalValue += item.value * fraction;
114+
break;
115+
}
116+
}
117+
return {
118+
maxValue: totalValue,
119+
fractions,
120+
};
121+
}
122+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import {BellmanFord} from './bellmanFordSearch';
2+
3+
describe('BellmanFord', () => {
4+
test('should find shortest paths correctly for a valid graph', () => {
5+
const edges = [
6+
{source: 0, target: 1, weight: 4},
7+
{source: 0, target: 2, weight: 5},
8+
{source: 1, target: 2, weight: -3},
9+
{source: 1, target: 3, weight: 6},
10+
{source: 2, target: 3, weight: 1},
11+
];
12+
const result = BellmanFord.findShortestPaths(4, edges, 0);
13+
expect(result).toEqual([0, 4, 1, 2]);
14+
});
15+
test('should detect negative weight cycle and return null', () => {
16+
const edges = [
17+
{source: 0, target: 1, weight: 1},
18+
{source: 1, target: 2, weight: 1},
19+
{source: 2, target: 0, weight: -3},
20+
];
21+
const result = BellmanFord.findShortestPaths(3, edges, 0);
22+
expect(result).toBeNull();
23+
});
24+
test('should return null when reconstructing path for unreachable vertex', () => {
25+
const edges = [
26+
{source: 0, target: 1, weight: 4},
27+
{source: 1, target: 2, weight: 3},
28+
];
29+
const path = BellmanFord.reconstructPath(4, edges, 0, 3);
30+
expect(path).toBeNull();
31+
});
32+
test('should throw error for invalid vertex count', () => {
33+
const edges = [
34+
{source: 0, target: 1, weight: 4},
35+
{source: 1, target: 2, weight: 3},
36+
];
37+
expect(() => BellmanFord.findShortestPaths(0, edges, 0)).toThrowError(
38+
'Number of vertices must be positive'
39+
);
40+
});
41+
test('should throw error for invalid edge vertices', () => {
42+
const edges = [{source: 0, target: 5, weight: 4}];
43+
expect(() => BellmanFord.findShortestPaths(4, edges, 0)).toThrowError(
44+
'Edge contains an invalid vertex'
45+
);
46+
});
47+
test('should handle large graph with no negative weight cycles', () => {
48+
const edges = Array.from({length: 100}, (_, i) => ({
49+
source: i,
50+
target: (i + 1) % 100,
51+
weight: 1,
52+
}));
53+
const result = BellmanFord.findShortestPaths(100, edges, 0);
54+
expect(result).toHaveLength(100);
55+
expect(result![99]).toBe(99);
56+
});
57+
});

0 commit comments

Comments
 (0)