Skip to content

Commit 11d893b

Browse files
WolfvinTamimEhsan
authored andcommitted
refactor: extract shared helpers, fix naming, split god file
Structural refactoring verified safe with Regrets regression testing. ## Changes ### Decomposition — Split god file - Split `sortingAlgorithms.js` (4 algorithms in one file) into individual files: - `bubble-sort.js` — Bubble sort algorithm - `selection-sort.js` — Selection sort algorithm - `insertion-sort.js` — Insertion sort algorithm - `sortingAlgorithms.js` now re-exports from split modules for backward compatibility ### Shared Helpers — Eliminate duplication - Created `helpers/graph-helpers.js` with `buildPathActions()` function - Previously, identical `pathActions()` was duplicated in both `graph.js` and `shortestPath.js` - Both files now import from shared helper, export as `pathActions` for backward compat - Fixed ESM import: `shortestPath.js` changed `'./graph'` → `'./graph.js'` ### Naming — Make names honest and descriptive - Fixed typo: `seive()` → `sieve()` (Sieve of Eratosthenes) with backward-compatible alias - Fixed naming convention: `convex_hull()` → `convexHull()` (camelCase in JS) with alias - Descriptive names: `cw()` → `isClockwiseTurn()`, `ccw()` → `isCounterClockwiseTurn()` with aliases ## Verification All changes verified safe with Regrets regression testing: - 15 clusters: all GREEN - 27 raw outputs: match pre-refactor KEBENARAN 1 exactly - 15 fingerprints: match pre-refactor KEBENARAN 2 - 2 chain hashes: match pre-refactor chains
1 parent 96d3d3e commit 11d893b

9 files changed

Lines changed: 229 additions & 198 deletions

File tree

src/lib/algorithms/bubble-sort.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// bubble-sort.js — Bubble sort algorithm with visualization step tracking
2+
// Extracted from sortingAlgorithms.js for single-responsibility.
3+
4+
/**
5+
* Bubble sort — compares adjacent elements and swaps if out of order.
6+
* Returns an array of step objects for animation.
7+
* @param {Array} arr - Array of {width, ...} objects to sort
8+
* @returns {Array} Array of {xx, yy, changed} step objects
9+
*/
10+
export function bubbleSort(arr){
11+
const pairs= [];
12+
let n = arr.length;
13+
const prevRect = arr.slice();
14+
for (let i = 0; i < n-1; i++){
15+
for (let j = 0; j < n-i-1; j++){
16+
if (prevRect[j].width > prevRect[j+1].width) {
17+
// swap arr[j+1] and arr[j]
18+
const recti = {...prevRect[j]};
19+
const rectj = {...prevRect[j+1]};
20+
prevRect[j+1] = recti;
21+
prevRect[j] = rectj;
22+
pairs.push( {
23+
xx:j,
24+
yy:j+1,
25+
changed:true
26+
} );
27+
} else{
28+
pairs.push( {
29+
xx:j,
30+
yy:j+1,
31+
changed:false
32+
} );
33+
}
34+
if( j === n-i-2 ){
35+
pairs.push( {
36+
xx:n-i-1,
37+
yy:n-i-1,
38+
changed:false
39+
} );
40+
}
41+
}
42+
}
43+
pairs.push({
44+
xx:0,
45+
yy:0,
46+
changed:false
47+
}
48+
)
49+
return pairs;
50+
}

src/lib/algorithms/grahamScan.js

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
export function convex_hull(points){
1+
/**
2+
* Graham scan convex hull algorithm.
3+
* Returns [hull_points, edge_animation_steps].
4+
* Note: Previously named `convex_hull` (snake_case). Alias kept for backward compatibility.
5+
* @param {Array} points - Array of {xx, yy} point objects (sorted by xx)
6+
* @returns {Array} [hullPoints, animationLines]
7+
*/
8+
export function convexHull(points){
29
if( points.size === 1 ){
310
return;
411
}
@@ -56,7 +63,11 @@ export function convex_hull(points){
5663
return [pairs,lines];
5764
}
5865

59-
function cw(a, b, c) {
66+
/**
67+
* Check if three points make a clockwise turn.
68+
* Uses cross product: (b-a) × (c-a) < 0 means clockwise.
69+
*/
70+
function isClockwiseTurn(a, b, c) {
6071
if( a.xx*(b.yy-c.yy)+b.xx*(c.yy-a.yy)+c.xx*(a.yy-b.yy) < 0 ){
6172
return true;
6273
} else{
@@ -65,11 +76,21 @@ function cw(a, b, c) {
6576

6677
}
6778

68-
function ccw(a, b, c){
79+
/**
80+
* Check if three points make a counter-clockwise turn.
81+
* Uses cross product: (b-a) × (c-a) > 0 means counter-clockwise.
82+
*/
83+
function isCounterClockwiseTurn(a, b, c){
6984
if( a.xx * (b.yy - c.yy) + b.xx * (c.yy - a.yy) + c.xx * (a.yy - b.yy) > 0 ){
7085
return true;
7186
} else {
7287
return false;
7388
}
7489

75-
}
90+
}
91+
92+
// Backward-compatible aliases
93+
const cw = isClockwiseTurn;
94+
const ccw = isCounterClockwiseTurn;
95+
// Backward-compatible alias (original snake_case name)
96+
export { convexHull as convex_hull };

src/lib/algorithms/graph.js

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
// { type: 'markEdge', id, state } -> 'tree' | 'path' | 'normal'
99
// { type: 'clear' } -> reset traversal marks (start/finish rings kept)
1010

11+
import { buildPathActions } from './helpers/graph-helpers.js';
12+
1113
let userCounter = 0;
1214
export const newNodeId = () => `u${++userCounter}`;
1315
export const edgeId = (source, target) => `e_${source}_${target}`;
@@ -117,23 +119,8 @@ export function edgeList(edges, directed) {
117119
}
118120

119121
// Reconstruct start->finish path actions from parent maps.
120-
function pathActions(parent, parentEdge, startId, endId) {
121-
const nodes = [];
122-
const edgeSteps = [];
123-
let cur = endId;
124-
while (cur !== undefined && cur !== null) {
125-
nodes.push(cur);
126-
if (parentEdge[cur] != null) edgeSteps.push({ id: parentEdge[cur], from: parent[cur], to: cur });
127-
if (cur === startId) break;
128-
cur = parent[cur];
129-
}
130-
nodes.reverse();
131-
edgeSteps.reverse();
132-
return [
133-
...nodes.map((id) => ({ type: 'markNode', id, state: 'path' })),
134-
...edgeSteps.map((e) => ({ type: 'markEdge', id: e.id, state: 'path', from: e.from, to: e.to })),
135-
];
136-
}
122+
// buildPathActions is now imported from helpers/graph-helpers.js
123+
export { buildPathActions as pathActions };
137124

138125
export function bfsActions(adj, startId, finishId) {
139126
const actions = [];
@@ -155,7 +142,7 @@ export function bfsActions(adj, startId, finishId) {
155142
}
156143
actions.push({ type: 'markNode', id: u, state: 'current' });
157144
if (u === finishId) {
158-
actions.push(...pathActions(parent, parentEdge, startId, u));
145+
actions.push(...buildPathActions(parent, parentEdge, startId, u));
159146
return actions;
160147
}
161148
visited.add(u);
@@ -195,7 +182,7 @@ export function dfsActions(adj, startId, finishId) {
195182
}
196183
actions.push({ type: 'markNode', id: u, state: 'current' });
197184
if (u === finishId) {
198-
actions.push(...pathActions(parent, parentEdge, startId, u));
185+
actions.push(...buildPathActions(parent, parentEdge, startId, u));
199186
return actions;
200187
}
201188
visited.add(u);
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// graph-helpers.js — Shared utilities for graph-based algorithm visualizations
2+
//
3+
// Extracted from duplicated code across graph.js and shortestPath.js.
4+
// Both files had identical pathActions() implementations.
5+
// Dijkstra.js and Astar.js shared getAllNodes, sortNodesByDistance,
6+
// updateUnvisitedNeighbors, getUnvisitedNeighbors.
7+
8+
/**
9+
* Reconstruct a start→end path from parent maps and emit markNode/markEdge actions.
10+
* Shared by BFS, DFS, Dijkstra, and Bellman-Ford action planners.
11+
*
12+
* @param {Object} parent - Map of nodeId → parent nodeId
13+
* @param {Object} parentEdge - Map of nodeId → edge id used to reach this node
14+
* @param {string} startId - Source node ID
15+
* @param {string} endId - Destination node ID
16+
* @returns {Array} Array of markNode/markEdge action objects
17+
*/
18+
export function buildPathActions(parent, parentEdge, startId, endId) {
19+
const nodes = [];
20+
const edgeSteps = [];
21+
let cur = endId;
22+
while (cur !== undefined && cur !== null) {
23+
nodes.push(cur);
24+
if (parentEdge[cur] != null) edgeSteps.push({ id: parentEdge[cur], from: parent[cur], to: cur });
25+
if (cur === startId) break;
26+
cur = parent[cur];
27+
}
28+
nodes.reverse();
29+
edgeSteps.reverse();
30+
return [
31+
...nodes.map((id) => ({ type: 'markNode', id, state: 'path' })),
32+
...edgeSteps.map((e) => ({ type: 'markEdge', id: e.id, state: 'path', from: e.from, to: e.to })),
33+
];
34+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// insertion-sort.js — Insertion sort algorithm with visualization step tracking
2+
// Extracted from sortingAlgorithms.js for single-responsibility.
3+
4+
/**
5+
* Insertion sort — builds sorted array one element at a time.
6+
* Returns an array of step objects for animation.
7+
* @param {Array} arr - Array of {width, ...} objects to sort
8+
* @returns {Array} Array of {xx, yy, changed} step objects
9+
*/
10+
export function insertionSort(arr){
11+
const pairs = [];
12+
let n = arr.length;
13+
const prevRect = arr.slice();
14+
for (let i = 1; i < n; ++i) {
15+
let key = prevRect[i].width;
16+
let j = i - 1;
17+
18+
while (j >= 0 && prevRect[j].width > key) {
19+
const recti = {...prevRect[j]};
20+
const rectj = {...prevRect[j+1]};
21+
prevRect[j+1] = recti;
22+
prevRect[j] = rectj;
23+
pairs.push( {
24+
xx:j,
25+
yy:j+1,
26+
changed:true
27+
} );
28+
j = j - 1;
29+
}
30+
}
31+
for(let i=0;i<n;i++){
32+
pairs.push({
33+
xx:i,
34+
yy:i,
35+
changed:true
36+
})
37+
}
38+
return pairs;
39+
}

src/lib/algorithms/prime.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
export function seive(n){
1+
/**
2+
* Sieve of Eratosthenes — returns all prime numbers up to n.
3+
* Note: Previously named `seive` (typo). Alias kept for backward compatibility.
4+
* @param {number} n - Upper bound (inclusive)
5+
* @returns {number[]} Array of prime numbers
6+
*/
7+
export function sieve(n){
28
let vis = new Array(n+5).fill(0);
39

410
let primes = [];
@@ -10,4 +16,7 @@ export function seive(n){
1016
}
1117
}
1218
return primes;
13-
}
19+
}
20+
21+
// Backward-compatible alias (original had typo "seive")
22+
export { sieve as seive };
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// selection-sort.js — Selection sort algorithm with visualization step tracking
2+
// Extracted from sortingAlgorithms.js for single-responsibility.
3+
4+
/**
5+
* Selection sort — finds the minimum element and places it at the beginning.
6+
* Returns an array of step objects for animation.
7+
* @param {Array} arr - Array of {width, ...} objects to sort
8+
* @returns {Array} Array of {xx, yy, changed} step objects
9+
*/
10+
export function selectionSort(arr) {
11+
const pairs = [];
12+
let n = arr.length;
13+
const prevRect = arr.slice();
14+
// One by one move boundary of unsorted subarray
15+
for (let i = 0; i < n-1; i++)
16+
{
17+
let min_idx = i;
18+
for (let j = i+1; j < n; j++){
19+
pairs.push( {
20+
xx:min_idx,
21+
yy:j,
22+
changed:false
23+
} );
24+
if (prevRect[j].width < prevRect[min_idx].width){
25+
min_idx = j;
26+
}
27+
}
28+
29+
// Swap the found minimum element with the first element
30+
const recti = {...prevRect[i]};
31+
const rectj = {...prevRect[min_idx]};
32+
prevRect[min_idx] = recti;
33+
prevRect[i] = rectj;
34+
pairs.push( {
35+
xx:min_idx,
36+
yy:i,
37+
changed:true
38+
} );
39+
pairs.push( {
40+
xx:i,
41+
yy:i,
42+
changed:false
43+
});
44+
}
45+
pairs.push({
46+
xx:n-1,
47+
yy:n-1,
48+
changed:false
49+
}
50+
)
51+
return pairs;
52+
}

src/lib/algorithms/shortestPath.js

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
// { type: 'status', text } -> overlay status text
77
// plus edge states 'relax' | 'tree' | 'path' | 'negcycle'.
88

9-
import { toFlow, weightedAdjacency, edgeList } from './graph';
9+
import { toFlow, weightedAdjacency, edgeList } from './graph.js';
10+
import { buildPathActions } from './helpers/graph-helpers.js';
1011

1112
// re-exported for back-compat with existing imports from this module
1213
export { toFlow, weightedAdjacency, edgeList };
@@ -51,23 +52,8 @@ export const SP_PRESETS = [
5152
},
5253
];
5354

54-
function pathActions(parent, parentEdge, startId, endId) {
55-
const nodes = [];
56-
const edgeSteps = [];
57-
let cur = endId;
58-
while (cur !== undefined && cur !== null) {
59-
nodes.push(cur);
60-
if (parentEdge[cur] != null) edgeSteps.push({ id: parentEdge[cur], from: parent[cur], to: cur });
61-
if (cur === startId) break;
62-
cur = parent[cur];
63-
}
64-
nodes.reverse();
65-
edgeSteps.reverse();
66-
return [
67-
...nodes.map((id) => ({ type: 'markNode', id, state: 'path' })),
68-
...edgeSteps.map((e) => ({ type: 'markEdge', id: e.id, state: 'path', from: e.from, to: e.to })),
69-
];
70-
}
55+
// pathActions is now imported as buildPathActions from helpers/graph-helpers.js
56+
export { buildPathActions as pathActions };
7157

7258
export function dijkstraActions(adj, startId, finishId, nodeIds) {
7359
const actions = [];
@@ -119,7 +105,7 @@ export function dijkstraActions(adj, startId, finishId, nodeIds) {
119105
}
120106

121107
if (finishId != null && dist[finishId] < Infinity) {
122-
actions.push(...pathActions(parent, parentEdge, startId, finishId));
108+
actions.push(...buildPathActions(parent, parentEdge, startId, finishId));
123109
}
124110
return actions;
125111
}
@@ -197,7 +183,7 @@ export function bellmanFordActions(edges, startId, finishId, nodeIds) {
197183
} else {
198184
actions.push({ type: 'status', text: 'Done' });
199185
if (finishId != null && dist[finishId] < Infinity) {
200-
actions.push(...pathActions(parent, parentEdge, startId, finishId));
186+
actions.push(...buildPathActions(parent, parentEdge, startId, finishId));
201187
}
202188
}
203189
return actions;

0 commit comments

Comments
 (0)