Skip to content

Commit f6d0a96

Browse files
Merge branch 'main' into main
2 parents 77043c3 + 1c04e80 commit f6d0a96

File tree

10 files changed

+1419
-3
lines changed

10 files changed

+1419
-3
lines changed

src/App.jsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import SortingPage from "./pages/sorting/SortingPage";
55
import GraphPage from "./pages/graph/GraphPage";
66
import Homepage from "./pages/Homepage.jsx";
77
import DSPage from "./pages/dataStructure/datastructurePage.jsx"
8+
import DynamicProgrammingPage from "./pages/dynamic-programming/DyanmicProgrammingPage.jsx";
89

910
function App() {
1011
return (
@@ -14,6 +15,8 @@ function App() {
1415
{/* <Route path="/graph/union-find" element={<UnionFindPage />} /> */}
1516
<Route path="/sorting" element={<SortingPage />} />
1617
<Route path="/data-structures" element={<DSPage/>}/>
18+
<Route path="/graph" element={<GraphPage />} />
19+
<Route path="/dynamic-programming" element={<DynamicProgrammingPage />} />
1720
</Routes>
1821
</Router>
1922
);
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
const calculateLevenshtein = (s1, s2, costs) => {
2+
const m = s1.length;
3+
const n = s2.length;
4+
const dp = Array(m + 1)
5+
.fill(null)
6+
.map(() => Array(n + 1).fill(null));
7+
8+
for (let i = 0; i <= m; i++) dp[i][0] = i * costs.delete;
9+
for (let j = 0; j <= n; j++) dp[0][j] = j * costs.insert;
10+
11+
for (let i = 1; i <= m; i++) {
12+
for (let j = 1; j <= n; j++) {
13+
if (s1[i - 1] === s2[j - 1]) {
14+
dp[i][j] = dp[i - 1][j - 1];
15+
} else {
16+
dp[i][j] = Math.min(
17+
dp[i - 1][j] + costs.delete,
18+
dp[i][j - 1] + costs.insert,
19+
dp[i - 1][j - 1] + costs.replace
20+
);
21+
}
22+
}
23+
}
24+
return dp;
25+
};
26+
27+
const getExplanation = (s1, s2, i, j, dp, costs) => {
28+
if (i === 0) {
29+
return {
30+
title: `Base case: Converting empty string to "${s2.substring(0, j)}"`,
31+
text: `We need ${j} insertions. Cost = ${j} × ${costs.insert} = ${
32+
j * costs.insert
33+
}`,
34+
};
35+
}
36+
if (j === 0) {
37+
return {
38+
title: `Base case: Converting "${s1.substring(0, i)}" to empty string`,
39+
text: `We need ${i} deletions. Cost = ${i} × ${costs.delete} = ${
40+
i * costs.delete
41+
}`,
42+
};
43+
}
44+
45+
const char1 = s1[i - 1];
46+
const char2 = s2[j - 1];
47+
const match = char1 === char2;
48+
const deletion = dp[i - 1][j] + costs.delete;
49+
const insertion = dp[i][j - 1] + costs.insert;
50+
const substitution = dp[i - 1][j - 1] + (match ? 0 : costs.replace);
51+
52+
let title = `Cell [${i}][${j}]: Comparing '${char1}' with '${char2}'`;
53+
let text = "";
54+
55+
if (match) {
56+
text = `Characters match! We can skip (no operation).\n• Skip (diagonal): ${
57+
dp[i - 1][j - 1]
58+
} + 0 = ${substitution}\n• Delete '${char1}' (down): ${dp[i - 1][j]} + ${
59+
costs.delete
60+
} = ${deletion}\n• Insert '${char2}' (right): ${dp[i][j - 1]} + ${
61+
costs.insert
62+
} = ${insertion}\n✓ Best choice: Skip with cost = ${dp[i][j]}`;
63+
} else {
64+
text = `Characters don't match. We have three options:\n• Replace '${char1}' with '${char2}' (diagonal): ${
65+
dp[i - 1][j - 1]
66+
} + ${costs.replace} = ${substitution}\n• Delete '${char1}' (down): ${
67+
dp[i - 1][j]
68+
} + ${costs.delete} = ${deletion}\n• Insert '${char2}' (right): ${
69+
dp[i][j - 1]
70+
} + ${costs.insert} = ${insertion}\n`;
71+
const minCost = dp[i][j];
72+
let chosenOp = "";
73+
if (
74+
substitution === minCost &&
75+
substitution <= deletion &&
76+
substitution <= insertion
77+
) {
78+
chosenOp = "Replace (diagonal)";
79+
} else if (insertion === minCost && insertion < deletion) {
80+
chosenOp = "Insert (right)";
81+
} else if (deletion === minCost) {
82+
chosenOp = "Delete (down)";
83+
}
84+
text += `✓ Best choice: ${chosenOp} with cost = ${minCost}`;
85+
}
86+
return { title, text };
87+
};
88+
89+
const backtrackOperations = (s1, s2, dp, costs) => {
90+
const operations = [];
91+
const pathCells = [];
92+
let i = s1.length;
93+
let j = s2.length;
94+
95+
pathCells.push([i, j]);
96+
97+
while (i > 0 || j > 0) {
98+
let op;
99+
if (i === 0) {
100+
op = {
101+
op: "insert",
102+
char: s2[j - 1],
103+
pos: i,
104+
cells: [
105+
[i, j],
106+
[i, j - 1],
107+
],
108+
};
109+
operations.unshift(op);
110+
j--;
111+
} else if (j === 0) {
112+
op = {
113+
op: "delete",
114+
char: s1[i - 1],
115+
pos: i,
116+
cells: [
117+
[i, j],
118+
[i - 1, j],
119+
],
120+
};
121+
operations.unshift(op);
122+
i--;
123+
} else if (s1[i - 1] === s2[j - 1]) {
124+
op = {
125+
op: "skip",
126+
char: s1[i - 1],
127+
pos: i,
128+
cells: [
129+
[i, j],
130+
[i - 1, j - 1],
131+
],
132+
};
133+
operations.unshift(op);
134+
i--;
135+
j--;
136+
} else {
137+
const repCost = dp[i - 1][j - 1] + costs.replace;
138+
const insCost = dp[i][j - 1] + costs.insert;
139+
const delCost = dp[i - 1][j] + costs.delete;
140+
141+
if (repCost <= delCost && repCost <= insCost) {
142+
op = {
143+
op: "replace",
144+
from: s1[i - 1],
145+
to: s2[j - 1],
146+
pos: i,
147+
cells: [
148+
[i, j],
149+
[i - 1, j - 1],
150+
],
151+
};
152+
operations.unshift(op);
153+
i--;
154+
j--;
155+
} else if (insCost < delCost) {
156+
op = {
157+
op: "insert",
158+
char: s2[j - 1],
159+
pos: i,
160+
cells: [
161+
[i, j],
162+
[i, j - 1],
163+
],
164+
};
165+
operations.unshift(op);
166+
j--;
167+
} else {
168+
op = {
169+
op: "delete",
170+
char: s1[i - 1],
171+
pos: i,
172+
cells: [
173+
[i, j],
174+
[i - 1, j],
175+
],
176+
};
177+
operations.unshift(op);
178+
i--;
179+
}
180+
}
181+
pathCells.push([i, j]);
182+
}
183+
184+
return { operations, path: pathCells };
185+
};
186+
187+
const processOperations = (ops, s1) => {
188+
let currentString = s1;
189+
return ops
190+
.map((op) => {
191+
const displayString = currentString;
192+
const actualPos = op.pos - (s1.length - currentString.length);
193+
194+
let displayText = "";
195+
if (op.op === "replace") {
196+
displayText = `${displayString}: replace ${
197+
currentString[actualPos - 1]
198+
} with ${op.to} at position ${actualPos}`;
199+
currentString =
200+
currentString.substring(0, actualPos - 1) +
201+
op.to +
202+
currentString.substring(actualPos);
203+
} else if (op.op === "delete") {
204+
displayText = `${displayString}: delete ${
205+
currentString[actualPos - 1]
206+
} at position ${actualPos}`;
207+
currentString =
208+
currentString.substring(0, actualPos - 1) +
209+
currentString.substring(actualPos);
210+
} else if (op.op === "insert") {
211+
displayText = `${displayString}: insert ${op.char} at position ${actualPos}`;
212+
currentString =
213+
currentString.substring(0, actualPos) +
214+
op.char +
215+
currentString.substring(actualPos);
216+
} else if (op.op === "skip") {
217+
displayText = `${displayString}: don't change ${op.char} at position ${actualPos}`;
218+
}
219+
220+
return { ...op, displayText, isSkip: op.op === "skip" };
221+
})
222+
.concat([
223+
{
224+
displayText: `${currentString}: (final result)`,
225+
isFinal: true,
226+
cells: [],
227+
},
228+
]);
229+
};
230+
231+
export {
232+
calculateLevenshtein,
233+
getExplanation,
234+
backtrackOperations,
235+
processOperations,
236+
};
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// src/algorithms/graph/floydWarshall.js
2+
3+
// 🧠 Generator that yields every visualization step
4+
export function* floydWarshallSteps(edges, nodeCount) {
5+
// Initialize distance matrix with Infinity
6+
const dist = Array.from({ length: nodeCount }, () =>
7+
Array(nodeCount).fill(Infinity)
8+
);
9+
10+
// Distance from a node to itself is 0
11+
for (let i = 0; i < nodeCount; i++) {
12+
dist[i][i] = 0;
13+
}
14+
15+
// Set initial distances from edges (use minimum weight for each pair)
16+
for (let edge of edges) {
17+
const from = edge.from - 1;
18+
const to = edge.to - 1;
19+
if (edge.weight < dist[from][to]) {
20+
dist[from][to] = edge.weight;
21+
}
22+
// For undirected graphs, uncomment the next line
23+
// if (edge.weight < dist[to][from]) dist[to][from] = edge.weight;
24+
}
25+
26+
yield {
27+
type: "init",
28+
dist: dist.map((row) => [...row]),
29+
message: "Initial distance matrix created",
30+
};
31+
32+
// Floyd-Warshall algorithm
33+
for (let k = 0; k < nodeCount; k++) {
34+
yield {
35+
type: "intermediate",
36+
k,
37+
dist: dist.map((row) => [...row]),
38+
message: `Using node ${k + 1} as intermediate`,
39+
};
40+
41+
for (let i = 0; i < nodeCount; i++) {
42+
for (let j = 0; j < nodeCount; j++) {
43+
if (i === j) continue;
44+
45+
const oldDist = dist[i][j];
46+
const newDist = dist[i][k] + dist[k][j];
47+
48+
if (newDist < oldDist) {
49+
dist[i][j] = newDist;
50+
yield {
51+
type: "update",
52+
k,
53+
i,
54+
j,
55+
oldDist,
56+
newDist,
57+
dist: dist.map((row) => [...row]),
58+
message: `Updated dist[${i + 1}][${j + 1}] from ${
59+
oldDist === Infinity ? "∞" : oldDist
60+
} to ${newDist}`,
61+
};
62+
} else {
63+
yield {
64+
type: "compare",
65+
k,
66+
i,
67+
j,
68+
oldDist,
69+
newDist,
70+
dist: dist.map((row) => [...row]),
71+
message: `No update: dist[${i + 1}][${j + 1}] = ${
72+
oldDist === Infinity ? "∞" : oldDist
73+
}${newDist === Infinity ? "∞" : newDist}`,
74+
};
75+
}
76+
}
77+
}
78+
}
79+
80+
// Check for negative cycles
81+
let hasNegativeCycle = false;
82+
for (let i = 0; i < nodeCount; i++) {
83+
if (dist[i][i] < 0) {
84+
hasNegativeCycle = true;
85+
break;
86+
}
87+
}
88+
89+
yield {
90+
type: "done",
91+
dist: dist.map((row) => [...row]),
92+
hasNegativeCycle,
93+
message: hasNegativeCycle
94+
? "Algorithm complete - Negative cycle detected!"
95+
: "Algorithm complete - All shortest paths found",
96+
};
97+
}
98+
99+
// Helper function to get shortest path between two nodes
100+
export function getShortestPath(dist, predecessor, start, end) {
101+
if (dist[start][end] === Infinity) {
102+
return null; // No path exists
103+
}
104+
105+
const path = [];
106+
let current = start;
107+
path.push(current);
108+
109+
while (current !== end) {
110+
current = predecessor[current][end];
111+
if (current === undefined || current === null) break;
112+
path.push(current);
113+
}
114+
115+
return path.length > 1 && path[path.length - 1] === end ? path : null;
116+
}
117+
118+
// Helper function to format distance matrix for display
119+
export function formatDistanceMatrix(dist) {
120+
return dist.map((row) =>
121+
row.map((val) => (val === Infinity ? "∞" : val.toString()))
122+
);
123+
}

0 commit comments

Comments
 (0)