diff --git a/package-lock.json b/package-lock.json index c952e7e..210d3cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -74,7 +74,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -2454,7 +2453,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "devOptional": true, - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -2464,7 +2462,6 @@ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.2.tgz", "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", "devOptional": true, - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -2500,7 +2497,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2653,7 +2649,6 @@ "url": "https://github.com/sponsors/ai" } ], - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -2817,8 +2812,7 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "peer": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/d3-color": { "version": "3.1.0", @@ -2877,7 +2871,6 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -3095,7 +3088,6 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.38.0.tgz", "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", "dev": true, - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4062,7 +4054,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "peer": true, "engines": { "node": ">=12" }, @@ -4088,7 +4079,6 @@ "url": "https://github.com/sponsors/ai" } ], - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -4126,7 +4116,6 @@ "version": "19.2.0", "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -4135,7 +4124,6 @@ "version": "19.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -4164,7 +4152,6 @@ "version": "9.2.0", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", - "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -4315,8 +4302,7 @@ "node_modules/redux": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", - "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "peer": true + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -4675,7 +4661,6 @@ "version": "7.1.12", "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz", "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", diff --git a/src/algorithms/sorting/radixSort.js b/src/algorithms/sorting/radixSort.js new file mode 100644 index 0000000..442cd4a --- /dev/null +++ b/src/algorithms/sorting/radixSort.js @@ -0,0 +1,47 @@ +// src/algorithms/sorting/radixSort.js +export function* radixSort(array) { + const arr = [...array]; + + // Get the maximum number to determine number of digits + const maxNum = Math.max(...arr); + const maxDigits = Math.floor(Math.log10(maxNum)) + 1; + + // Helper function to get digit at a specific place + const getDigit = (num, place) => Math.floor(num / Math.pow(10, place)) % 10; + + // Counting sort for each digit position + for (let place = 0; place < maxDigits; place++) { + const buckets = Array.from({ length: 10 }, () => []); + + // Yield to show current digit pass + yield { type: "digitPassStart", place }; + + // Place each number in corresponding bucket + for (let i = 0; i < arr.length; i++) { + const digit = getDigit(arr[i], place); + buckets[digit].push(arr[i]); + + yield { type: "bucket", digit, value: arr[i], array: [...arr] }; + } + + // Combine buckets back into array + let index = 0; + for (let b = 0; b < 10; b++) { + for (let value of buckets[b]) { + arr[index++] = value; + yield { type: "rebuild", array: [...arr], bucket: b, value }; + } + } + + // Yield after completing sorting by current digit + yield { type: "digitPassEnd", place, array: [...arr] }; + } + + // Mark all elements as sorted + for (let i = 0; i < arr.length; i++) { + yield { type: "sorted", index: i }; + } + + // Final done state + yield { type: "done", array: arr }; +} diff --git a/src/components/sorting/RadixSortVisualizer.jsx b/src/components/sorting/RadixSortVisualizer.jsx new file mode 100644 index 0000000..174e2ff --- /dev/null +++ b/src/components/sorting/RadixSortVisualizer.jsx @@ -0,0 +1,72 @@ +import React from "react"; + +const COLORS = { + default: "bg-blue-500 shadow-[0_0_10px_#3b82f6]", + comparing: "bg-yellow-400 shadow-[0_0_12px_#facc15]", + bucket: "bg-purple-500 shadow-[0_0_12px_#a855f7]", + swap: "bg-red-500 shadow-[0_0_12px_#ef4444]", + sorted: "bg-green-500 shadow-[0_0_12px_#22c55e]", +}; + +export default function RadixSortVisualizer({ array, highlight }) { + const maxValue = Math.max(...array, 1); + const containerHeight = 288; // matches h-72 + + return ( +
+ {/* Main Array Display */} +
+ {array.map((value, idx) => { + let color = COLORS.default; + + if (highlight?.type === "compare" && highlight.indices?.includes(idx)) + color = COLORS.comparing; + if (highlight?.type === "bucket" && highlight.indices?.includes(idx)) + color = COLORS.bucket; + if (highlight?.type === "swap" && highlight.indices?.includes(idx)) + color = COLORS.swap; + if (highlight?.type === "sorted" && highlight.indices?.includes(idx)) + color = COLORS.sorted; + + const height = Math.max((value / maxValue) * containerHeight, 15); + + return ( +
+ ); + })} +
+ + {/* Current Digit / Pass Info */} + {highlight?.digit !== undefined && ( +
+ Processing Digit Place:{" "} + {highlight.digit} +
+ )} + + {/* Buckets Visualization */} + {highlight?.buckets && ( +
+ {highlight.buckets.map((bucket, i) => ( +
+
Bucket {i}
+
+ {bucket.map((val, j) => ( +
+ ))} +
+
+ ))} +
+ )} +
+ ); +} diff --git a/src/pages/sorting/RadixSort.jsx b/src/pages/sorting/RadixSort.jsx new file mode 100644 index 0000000..1d749a0 --- /dev/null +++ b/src/pages/sorting/RadixSort.jsx @@ -0,0 +1,79 @@ +import React, { useState } from "react"; +import { Toaster } from "react-hot-toast"; +import RadixSortVisualizer from "../../components/sorting/RadixSortVisualizer"; +import { radixSort } from "../../algorithms/sorting/radixSort"; + +export default function RadixSort() { + const [array, setArray] = useState([]); + const [input, setInput] = useState(""); + const [highlight, setHighlight] = useState(null); + const [isRunning, setIsRunning] = useState(false); + + const handleStart = async () => { + if (isRunning || array.length === 0) return; + setIsRunning(true); + const gen = radixSort(array); + for (let step of gen) { + setHighlight(step); + if (step.array) setArray([...step.array]); + await new Promise((r) => setTimeout(r, 500)); + } + setHighlight({ type: "done" }); + setIsRunning(false); + }; + + const handleReset = () => { + setArray([]); + setInput(""); + setHighlight(null); + }; + + const handleInput = (e) => { + setInput(e.target.value); + const numbers = e.target.value + .split(",") + .map((n) => parseInt(n.trim())) + .filter((n) => !isNaN(n) && n >= 0); // only non-negative numbers + setArray(numbers); + }; + + return ( +
+ +

+ Radix Sort Visualizer +

+ + + +
+ + +
+ +
+ +
+
+ ); +} diff --git a/src/pages/sorting/SortingPage.jsx b/src/pages/sorting/SortingPage.jsx index 879dd6f..caaf784 100644 --- a/src/pages/sorting/SortingPage.jsx +++ b/src/pages/sorting/SortingPage.jsx @@ -5,6 +5,7 @@ import BubbleSort from "./BubbleSort"; import InsertionSort from "./InsertionSort"; import QuickSort from "./QuickSort"; import MergeSort from "./MergeSort"; +import RadixSort from "./RadixSort"; export default function SortingPage() { const [selectedAlgo, setSelectedAlgo] = useState(""); @@ -13,8 +14,8 @@ export default function SortingPage() { switch (selectedAlgo) { case "selection": return ; - case "insertion": - return ; + case "insertion": + return ; // You can add more later like: case "bubble": return ; @@ -23,6 +24,8 @@ export default function SortingPage() { // case "merge": return ; case "merge": return ; + case "radix": + return default: return (
@@ -52,6 +55,7 @@ export default function SortingPage() { +