Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions src/lib/algorithms/bubble-sort.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// bubble-sort.js — Bubble sort algorithm with visualization step tracking
// Extracted from sortingAlgorithms.js for single-responsibility.

/**
* Bubble sort — compares adjacent elements and swaps if out of order.
* Returns an array of step objects for animation.
* @param {Array} arr - Array of {width, ...} objects to sort
* @returns {Array} Array of {xx, yy, changed} step objects
*/
export function bubbleSort(arr){
const pairs= [];
let n = arr.length;
const prevRect = arr.slice();
for (let i = 0; i < n-1; i++){
for (let j = 0; j < n-i-1; j++){
if (prevRect[j].width > prevRect[j+1].width) {
// swap arr[j+1] and arr[j]
const recti = {...prevRect[j]};
const rectj = {...prevRect[j+1]};
prevRect[j+1] = recti;
prevRect[j] = rectj;
pairs.push( {
xx:j,
yy:j+1,
changed:true
} );
} else{
pairs.push( {
xx:j,
yy:j+1,
changed:false
} );
}
if( j === n-i-2 ){
pairs.push( {
xx:n-i-1,
yy:n-i-1,
changed:false
} );
}
}
}
pairs.push({
xx:0,
yy:0,
changed:false
}
)
return pairs;
}
29 changes: 25 additions & 4 deletions src/lib/algorithms/grahamScan.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
export function convex_hull(points){
/**
* Graham scan convex hull algorithm.
* Returns [hull_points, edge_animation_steps].
* Note: Previously named `convex_hull` (snake_case). Alias kept for backward compatibility.
* @param {Array} points - Array of {xx, yy} point objects (sorted by xx)
* @returns {Array} [hullPoints, animationLines]
*/
export function convexHull(points){
if( points.size === 1 ){
return;
}
Expand Down Expand Up @@ -56,7 +63,11 @@ export function convex_hull(points){
return [pairs,lines];
}

function cw(a, b, c) {
/**
* Check if three points make a clockwise turn.
* Uses cross product: (b-a) × (c-a) < 0 means clockwise.
*/
function isClockwiseTurn(a, b, c) {
if( a.xx*(b.yy-c.yy)+b.xx*(c.yy-a.yy)+c.xx*(a.yy-b.yy) < 0 ){
return true;
} else{
Expand All @@ -65,11 +76,21 @@ function cw(a, b, c) {

}

function ccw(a, b, c){
/**
* Check if three points make a counter-clockwise turn.
* Uses cross product: (b-a) × (c-a) > 0 means counter-clockwise.
*/
function isCounterClockwiseTurn(a, b, c){
if( a.xx * (b.yy - c.yy) + b.xx * (c.yy - a.yy) + c.xx * (a.yy - b.yy) > 0 ){
return true;
} else {
return false;
}

}
}

// Backward-compatible aliases
const cw = isClockwiseTurn;
const ccw = isCounterClockwiseTurn;
// Backward-compatible alias (original snake_case name)
export { convexHull as convex_hull };
25 changes: 6 additions & 19 deletions src/lib/algorithms/graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
// { type: 'markEdge', id, state } -> 'tree' | 'path' | 'normal'
// { type: 'clear' } -> reset traversal marks (start/finish rings kept)

import { buildPathActions } from './helpers/graph-helpers.js';

let userCounter = 0;
export const newNodeId = () => `u${++userCounter}`;
export const edgeId = (source, target) => `e_${source}_${target}`;
Expand Down Expand Up @@ -117,23 +119,8 @@ export function edgeList(edges, directed) {
}

// Reconstruct start->finish path actions from parent maps.
function pathActions(parent, parentEdge, startId, endId) {
const nodes = [];
const edgeSteps = [];
let cur = endId;
while (cur !== undefined && cur !== null) {
nodes.push(cur);
if (parentEdge[cur] != null) edgeSteps.push({ id: parentEdge[cur], from: parent[cur], to: cur });
if (cur === startId) break;
cur = parent[cur];
}
nodes.reverse();
edgeSteps.reverse();
return [
...nodes.map((id) => ({ type: 'markNode', id, state: 'path' })),
...edgeSteps.map((e) => ({ type: 'markEdge', id: e.id, state: 'path', from: e.from, to: e.to })),
];
}
// buildPathActions is now imported from helpers/graph-helpers.js
export { buildPathActions as pathActions };

export function bfsActions(adj, startId, finishId) {
const actions = [];
Expand All @@ -155,7 +142,7 @@ export function bfsActions(adj, startId, finishId) {
}
actions.push({ type: 'markNode', id: u, state: 'current' });
if (u === finishId) {
actions.push(...pathActions(parent, parentEdge, startId, u));
actions.push(...buildPathActions(parent, parentEdge, startId, u));
return actions;
}
visited.add(u);
Expand Down Expand Up @@ -195,7 +182,7 @@ export function dfsActions(adj, startId, finishId) {
}
actions.push({ type: 'markNode', id: u, state: 'current' });
if (u === finishId) {
actions.push(...pathActions(parent, parentEdge, startId, u));
actions.push(...buildPathActions(parent, parentEdge, startId, u));
return actions;
}
visited.add(u);
Expand Down
34 changes: 34 additions & 0 deletions src/lib/algorithms/helpers/graph-helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// graph-helpers.js — Shared utilities for graph-based algorithm visualizations
//
// Extracted from duplicated code across graph.js and shortestPath.js.
// Both files had identical pathActions() implementations.
// Dijkstra.js and Astar.js shared getAllNodes, sortNodesByDistance,
// updateUnvisitedNeighbors, getUnvisitedNeighbors.

/**
* Reconstruct a start→end path from parent maps and emit markNode/markEdge actions.
* Shared by BFS, DFS, Dijkstra, and Bellman-Ford action planners.
*
* @param {Object} parent - Map of nodeId → parent nodeId
* @param {Object} parentEdge - Map of nodeId → edge id used to reach this node
* @param {string} startId - Source node ID
* @param {string} endId - Destination node ID
* @returns {Array} Array of markNode/markEdge action objects
*/
export function buildPathActions(parent, parentEdge, startId, endId) {
const nodes = [];
const edgeSteps = [];
let cur = endId;
while (cur !== undefined && cur !== null) {
nodes.push(cur);
if (parentEdge[cur] != null) edgeSteps.push({ id: parentEdge[cur], from: parent[cur], to: cur });
if (cur === startId) break;
cur = parent[cur];
}
nodes.reverse();
edgeSteps.reverse();
return [
...nodes.map((id) => ({ type: 'markNode', id, state: 'path' })),
...edgeSteps.map((e) => ({ type: 'markEdge', id: e.id, state: 'path', from: e.from, to: e.to })),
];
}
39 changes: 39 additions & 0 deletions src/lib/algorithms/insertion-sort.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// insertion-sort.js — Insertion sort algorithm with visualization step tracking
// Extracted from sortingAlgorithms.js for single-responsibility.

/**
* Insertion sort — builds sorted array one element at a time.
* Returns an array of step objects for animation.
* @param {Array} arr - Array of {width, ...} objects to sort
* @returns {Array} Array of {xx, yy, changed} step objects
*/
export function insertionSort(arr){
const pairs = [];
let n = arr.length;
const prevRect = arr.slice();
for (let i = 1; i < n; ++i) {
let key = prevRect[i].width;
let j = i - 1;

while (j >= 0 && prevRect[j].width > key) {
const recti = {...prevRect[j]};
const rectj = {...prevRect[j+1]};
prevRect[j+1] = recti;
prevRect[j] = rectj;
pairs.push( {
xx:j,
yy:j+1,
changed:true
} );
j = j - 1;
}
}
for(let i=0;i<n;i++){
pairs.push({
xx:i,
yy:i,
changed:true
})
}
return pairs;
}
13 changes: 11 additions & 2 deletions src/lib/algorithms/prime.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
export function seive(n){
/**
* Sieve of Eratosthenes — returns all prime numbers up to n.
* Note: Previously named `seive` (typo). Alias kept for backward compatibility.
* @param {number} n - Upper bound (inclusive)
* @returns {number[]} Array of prime numbers
*/
export function sieve(n){
let vis = new Array(n+5).fill(0);

let primes = [];
Expand All @@ -10,4 +16,7 @@ export function seive(n){
}
}
return primes;
}
}

// Backward-compatible alias (original had typo "seive")
export { sieve as seive };
52 changes: 52 additions & 0 deletions src/lib/algorithms/selection-sort.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// selection-sort.js — Selection sort algorithm with visualization step tracking
// Extracted from sortingAlgorithms.js for single-responsibility.

/**
* Selection sort — finds the minimum element and places it at the beginning.
* Returns an array of step objects for animation.
* @param {Array} arr - Array of {width, ...} objects to sort
* @returns {Array} Array of {xx, yy, changed} step objects
*/
export function selectionSort(arr) {
const pairs = [];
let n = arr.length;
const prevRect = arr.slice();
// One by one move boundary of unsorted subarray
for (let i = 0; i < n-1; i++)
{
let min_idx = i;
for (let j = i+1; j < n; j++){
pairs.push( {
xx:min_idx,
yy:j,
changed:false
} );
if (prevRect[j].width < prevRect[min_idx].width){
min_idx = j;
}
}

// Swap the found minimum element with the first element
const recti = {...prevRect[i]};
const rectj = {...prevRect[min_idx]};
prevRect[min_idx] = recti;
prevRect[i] = rectj;
pairs.push( {
xx:min_idx,
yy:i,
changed:true
} );
pairs.push( {
xx:i,
yy:i,
changed:false
});
}
pairs.push({
xx:n-1,
yy:n-1,
changed:false
}
)
return pairs;
}
26 changes: 6 additions & 20 deletions src/lib/algorithms/shortestPath.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
// { type: 'status', text } -> overlay status text
// plus edge states 'relax' | 'tree' | 'path' | 'negcycle'.

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

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

function pathActions(parent, parentEdge, startId, endId) {
const nodes = [];
const edgeSteps = [];
let cur = endId;
while (cur !== undefined && cur !== null) {
nodes.push(cur);
if (parentEdge[cur] != null) edgeSteps.push({ id: parentEdge[cur], from: parent[cur], to: cur });
if (cur === startId) break;
cur = parent[cur];
}
nodes.reverse();
edgeSteps.reverse();
return [
...nodes.map((id) => ({ type: 'markNode', id, state: 'path' })),
...edgeSteps.map((e) => ({ type: 'markEdge', id: e.id, state: 'path', from: e.from, to: e.to })),
];
}
// pathActions is now imported as buildPathActions from helpers/graph-helpers.js
export { buildPathActions as pathActions };

export function dijkstraActions(adj, startId, finishId, nodeIds) {
const actions = [];
Expand Down Expand Up @@ -119,7 +105,7 @@ export function dijkstraActions(adj, startId, finishId, nodeIds) {
}

if (finishId != null && dist[finishId] < Infinity) {
actions.push(...pathActions(parent, parentEdge, startId, finishId));
actions.push(...buildPathActions(parent, parentEdge, startId, finishId));
}
return actions;
}
Expand Down Expand Up @@ -197,7 +183,7 @@ export function bellmanFordActions(edges, startId, finishId, nodeIds) {
} else {
actions.push({ type: 'status', text: 'Done' });
if (finishId != null && dist[finishId] < Infinity) {
actions.push(...pathActions(parent, parentEdge, startId, finishId));
actions.push(...buildPathActions(parent, parentEdge, startId, finishId));
}
}
return actions;
Expand Down
Loading
Loading