Skip to content

Commit deb2d1f

Browse files
Merge pull request #96 from PresenceOP-Coder/counting
implemented Counting sort
2 parents 73632b0 + 5142e0d commit deb2d1f

File tree

8 files changed

+373
-9
lines changed

8 files changed

+373
-9
lines changed

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
11
{
22
"name": "algo-visualizer",
3-
"private": true,
43
"version": "0.0.0",
4+
"private": true,
5+
"description": "**AlgoVisualizer** is an open-source platform that allows learners and developers to **visualize algorithms** in real-time — from sorting and searching to graph traversal, recursion, and dynamic programming. \r Built with **React + Tailwind CSS**, it aims to make algorithmic concepts more intuitive and interactive.",
6+
"homepage": "https://github.com/PresenceOP-Coder/Algo-Visualizer#readme",
7+
"bugs": {
8+
"url": "https://github.com/PresenceOP-Coder/Algo-Visualizer/issues"
9+
},
10+
"repository": {
11+
"type": "git",
12+
"url": "git+https://github.com/PresenceOP-Coder/Algo-Visualizer.git"
13+
},
14+
"license": "ISC",
15+
"author": "",
516
"type": "module",
17+
"main": "commitlint.config.js",
618
"scripts": {
719
"dev": "vite",
820
"build": "vite build",
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
export function getCountingSortSteps(array) {
2+
const steps = [];
3+
const n = array.length;
4+
5+
if (n === 0) {
6+
return { steps };
7+
}
8+
9+
const max = Math.max(...array);
10+
const count = new Array(max + 1).fill(0);
11+
const output = new Array(n).fill(0);
12+
const initialInput = [...array];
13+
14+
const pushStep = (phase, message, highlight = {}) => {
15+
steps.push({
16+
phase,
17+
input: [...initialInput],
18+
count: [...count],
19+
output: [...output],
20+
message,
21+
highlight
22+
});
23+
};
24+
25+
pushStep(1, "Starting Phase 1: Count Frequencies. Creating 'Count' array of size (max + 1) = " + (max + 1) + ".", {});
26+
27+
// Phase 1: Count frequencies
28+
for (let i = 0; i < n; i++) {
29+
const val = array[i];
30+
count[val]++;
31+
pushStep(1, `Read input[${i}] = ${val}. Incrementing count[${val}] to ${count[val]}.`, {
32+
input: i,
33+
count: val
34+
});
35+
}
36+
37+
pushStep(2, "Starting Phase 2: Calculate Cumulative Sums.", {});
38+
39+
for (let i = 1; i <= max; i++) {
40+
const prevCount = count[i - 1];
41+
count[i] += prevCount;
42+
pushStep(2, `Calculating count[${i}] = count[${i}] + count[${i - 1}] = ${count[i] - prevCount} + ${prevCount} = ${count[i]}.`, {
43+
count: i,
44+
countRead: i - 1
45+
});
46+
}
47+
48+
pushStep(3, "Starting Phase 3: Build Output Array (Iterating input in reverse).", {});
49+
50+
for (let i = n - 1; i >= 0; i--) {
51+
const val = array[i];
52+
pushStep(3, `Reading input[${i}] = ${val}.`, {
53+
input: i
54+
});
55+
56+
const posIndex = count[val] - 1;
57+
pushStep(3, `Position for ${val} is count[${val}] - 1 = ${count[val]} - 1 = ${posIndex}.`, {
58+
input: i,
59+
countRead: val
60+
});
61+
62+
output[posIndex] = val;
63+
pushStep(3, `Placing ${val} at output[${posIndex}].`, {
64+
input: i,
65+
countRead: val,
66+
output: posIndex
67+
});
68+
69+
count[val]--;
70+
pushStep(3, `Decrementing count[${val}] to ${count[val]}.`, {
71+
input: i,
72+
count: val,
73+
output: posIndex
74+
});
75+
}
76+
77+
pushStep(4, "Algorithm Complete. The 'Output' array is now sorted.", {});
78+
79+
return { steps };
80+
}

src/components/dynamic-programming/kmpSearchAlgovisual.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useState, useMemo, useEffect, useRef } from "react";
2-
//import { computeLPSSteps, kmpSearchSteps } from "@/algorithms/string/kmpAlgo";
2+
import { computeLPSSteps, kmpSearchSteps } from "@/algorithms/dynamic-programming/kmpAlgo";
33

44
const LPSDisplay = ({ pattern, lpsState }) => {
55
const { lps, highlight, message } = lpsState;
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
import React, { useState, useMemo, useEffect, useRef } from "react";
2+
import { getCountingSortSteps } from "../../algorithms/sorting/countingSort.js";
3+
4+
const ArrayDisplay = ({ title, array, highlightIndex = -1, readIndex = -1, indicesLabel = "Index" }) => {
5+
return (
6+
<div className="mt-4">
7+
<h3 className="text-xl font-semibold mb-3 text-blue-400">{title}</h3>
8+
<div className="flex flex-wrap gap-2 justify-center">
9+
{array.map((value, index) => {
10+
const isCurrent = index === highlightIndex;
11+
const isReading = index === readIndex;
12+
13+
let cellClass = "bg-gray-700 border-gray-600 text-gray-200 hover:border-gray-500";
14+
if (isCurrent) {
15+
cellClass = "bg-blue-600 border-blue-400 shadow-lg text-white font-bold transform scale-105"; // Write/Current
16+
} else if (isReading) {
17+
cellClass = "bg-yellow-600 border-yellow-400 text-white"; // Read
18+
}
19+
20+
return (
21+
<div
22+
key={index}
23+
className={`p-3 rounded-lg text-center transition-all duration-300 ease-in-out border-2 w-20 flex flex-col justify-center items-center ${cellClass}`}
24+
title={`${indicesLabel} ${index}`}
25+
>
26+
<div className="text-sm font-light text-gray-400">{indicesLabel} {index}</div>
27+
<div className="text-xl mt-1">{value === null ? '?' : value}</div>
28+
</div>
29+
);
30+
})}
31+
</div>
32+
</div>
33+
);
34+
};
35+
36+
37+
const SPEED_OPTIONS = {
38+
"Slow": 1500,
39+
"Medium": 500,
40+
"Fast": 200,
41+
};
42+
43+
export default function CountingSort() {
44+
const [input, setInput] = useState("4, 1, 3, 4, 0, 2, 1, 7");
45+
46+
const [steps, setSteps] = useState([]);
47+
const [currentStep, setCurrentStep] = useState(0);
48+
const [isPlaying, setIsPlaying] = useState(false);
49+
const [speed, setSpeed] = useState(SPEED_OPTIONS["Medium"]);
50+
const timerRef = useRef(null);
51+
52+
const handleCompute = () => {
53+
const parsedArray = input
54+
.split(',')
55+
.map(s => Number(s.trim()))
56+
.filter(n => !isNaN(n) && n >= 0);
57+
58+
if (parsedArray.length === 0 || parsedArray.length > 20) {
59+
alert("Please enter 1 to 20 non-negative numbers, separated by commas.");
60+
return;
61+
}
62+
63+
const maxVal = Math.max(...parsedArray);
64+
if (maxVal > 50) {
65+
alert("The maximum value in the array must be 50 or less for optimal visualization.");
66+
return;
67+
}
68+
69+
setIsPlaying(false);
70+
const { steps: newSteps } = getCountingSortSteps(parsedArray);
71+
setSteps(newSteps);
72+
setCurrentStep(0);
73+
};
74+
75+
useEffect(() => {
76+
handleCompute();
77+
}, []);
78+
79+
useEffect(() => {
80+
if (isPlaying && currentStep < steps.length - 1) {
81+
timerRef.current = setInterval(() => {
82+
setCurrentStep((prevStep) => prevStep + 1);
83+
}, speed);
84+
} else if (currentStep === steps.length - 1) {
85+
setIsPlaying(false);
86+
}
87+
88+
return () => {
89+
clearInterval(timerRef.current);
90+
};
91+
}, [isPlaying, currentStep, steps.length, speed]);
92+
93+
const togglePlay = () => {
94+
if (currentStep === steps.length - 1) {
95+
setCurrentStep(0);
96+
setIsPlaying(true);
97+
} else {
98+
setIsPlaying(!isPlaying);
99+
}
100+
};
101+
102+
const handleNext = () => {
103+
setIsPlaying(false);
104+
if (currentStep < steps.length - 1) setCurrentStep(currentStep + 1);
105+
};
106+
107+
const handlePrev = () => {
108+
setIsPlaying(false);
109+
if (currentStep > 0) setCurrentStep(currentStep - 1);
110+
};
111+
112+
const currentState = useMemo(() => steps[currentStep] || {}, [steps, currentStep]);
113+
const { phase, input: inputArray, count, output, highlight, message } = currentState;
114+
115+
const isFinalStep = phase === 4;
116+
117+
return (
118+
<div className="p-6 min-h-screen bg-gray-900 text-gray-100 font-sans">
119+
<div className="max-w-6xl mx-auto">
120+
<h1 className="text-4xl font-extrabold mb-8 text-indigo-400 text-center drop-shadow-lg">
121+
Counting Sort
122+
</h1>
123+
124+
<details className="mb-8 p-4 rounded-xl bg-gray-800 border border-gray-700 shadow-inner group">
125+
<summary className="cursor-pointer text-xl font-bold text-teal-400 hover:text-teal-300 transition-colors">
126+
❓ What is Counting Sort?
127+
</summary>
128+
<div className="mt-3 p-3 border-t border-gray-700 text-gray-300">
129+
<p className="mb-2">
130+
Counting Sort is a non-comparison algorithm that sorts integers. It works in three phases:
131+
</p>
132+
<ul className="list-disc list-inside ml-4 space-y-2">
133+
<li>
134+
<b>Phase 1 (Frequency):</b> Count the occurrences of each unique element and store it in a `Count` array.
135+
</li>
136+
<li>
137+
<b>Phase 2 (Cumulative Sum):</b> Modify the `Count` array so each element stores the sum of all previous counts. This gives the "ending position" of each element.
138+
</li>
139+
<li>
140+
<b>Phase 3 (Output):</b> Iterate the `Input` array in reverse (for stability), place each element in its correct position in the `Output` array, and decrement its count.
141+
</li>
142+
</ul>
143+
</div>
144+
</details>
145+
146+
<div className="flex flex-col xl:flex-row justify-center items-center gap-5 mb-8 p-6 rounded-xl bg-gray-800 shadow-2xl border border-gray-700">
147+
<div className="flex-1 w-full xl:w-auto">
148+
<label htmlFor="input-array" className="text-gray-300 text-lg mb-2 block">Input Array (non-negative, max val 50):</label>
149+
<input
150+
id="input-array"
151+
type="text"
152+
className="border border-gray-600 p-2 rounded-lg w-full bg-gray-700 text-white text-lg focus:ring-indigo-500 focus:border-indigo-500 transition-colors"
153+
value={input}
154+
onChange={(e) => setInput(e.target.value)}
155+
placeholder="e.g., 4, 1, 3, 4, 0"
156+
/>
157+
</div>
158+
<button
159+
className="bg-indigo-600 hover:bg-indigo-700 text-white font-bold px-8 py-3 rounded-xl transition duration-300 ease-in-out shadow-md transform hover:-translate-y-0.5"
160+
onClick={handleCompute}
161+
>
162+
Re-Visualize
163+
</button>
164+
</div>
165+
166+
{steps.length > 0 ? (
167+
<>
168+
<div className="flex flex-wrap justify-between items-center mb-6 p-4 rounded-xl bg-gray-800 border border-gray-700 shadow-lg">
169+
<button
170+
className={`px-5 py-2 rounded-lg font-semibold text-lg transition duration-200 ease-in-out transform hover:scale-105 ${isPlaying ? "bg-red-600 hover:bg-red-700" : "bg-green-600 hover:bg-green-700"
171+
} text-white shadow-md`}
172+
onClick={togglePlay}
173+
disabled={isFinalStep && isPlaying}
174+
>
175+
{isFinalStep && !isPlaying ? "Replay ▶️" : isPlaying ? "Pause ⏸️" : "Play ▶️"}
176+
</button>
177+
<div className="flex gap-2">
178+
<button
179+
className={`px-3 py-2 rounded-lg font-semibold transition duration-150 ${currentStep > 0 ? "bg-purple-600 hover:bg-purple-700 text-white" : "bg-gray-600 text-gray-400 cursor-not-allowed"}`}
180+
onClick={handlePrev}
181+
disabled={currentStep === 0}
182+
>
183+
&lt; Prev
184+
</button>
185+
<button
186+
className={`px-3 py-2 rounded-lg font-semibold transition duration-150 ${!isFinalStep ? "bg-purple-600 hover:bg-purple-700 text-white" : "bg-gray-600 text-gray-400 cursor-not-allowed"}`}
187+
onClick={handleNext}
188+
disabled={isFinalStep}
189+
>
190+
Next &gt;
191+
</button>
192+
</div>
193+
<div className="flex items-center gap-2">
194+
<label htmlFor="speed-select" className="text-gray-300">Speed:</label>
195+
<select
196+
id="speed-select"
197+
className="border border-gray-600 p-2 rounded-lg bg-gray-700 text-white focus:ring-indigo-500 focus:border-indigo-500"
198+
value={speed}
199+
onChange={(e) => setSpeed(Number(e.target.value))}
200+
>
201+
{Object.entries(SPEED_OPTIONS).map(([label, ms]) => (
202+
<option key={label} value={ms}>{label}</option>
203+
))}
204+
</select>
205+
</div>
206+
</div>
207+
208+
<div className="text-center mb-4">
209+
<p className="text-2xl font-bold text-yellow-400">
210+
Step <b>{currentStep + 1}</b> / <b>{steps.length}</b>
211+
{phase && <span className="ml-4 text-xl text-teal-400">(Phase {phase} / 3)</span>}
212+
</p>
213+
</div>
214+
215+
<div className="border border-gray-700 p-6 rounded-xl bg-gray-800 shadow-2xl">
216+
<div className="mb-6 p-4 rounded-lg bg-gray-700 border-l-4 border-teal-400 shadow-inner min-h-[100px]">
217+
<p className="text-teal-400 font-medium text-md uppercase tracking-wide">Current Action</p>
218+
<p className="text-xl mt-2 text-gray-200 leading-relaxed">
219+
{message || 'Starting computation...'}
220+
</p>
221+
</div>
222+
223+
<ArrayDisplay
224+
title="Input Array"
225+
array={inputArray}
226+
highlightIndex={highlight.input}
227+
indicesLabel="i"
228+
/>
229+
230+
<ArrayDisplay
231+
title="Count Array"
232+
array={count}
233+
highlightIndex={highlight.count}
234+
readIndex={highlight.countRead}
235+
indicesLabel="val"
236+
/>
237+
238+
<ArrayDisplay
239+
title="Output Array"
240+
array={output}
241+
highlightIndex={highlight.output}
242+
indicesLabel="pos"
243+
/>
244+
245+
{isFinalStep && (
246+
<div className="mt-8 p-5 rounded-xl bg-green-900 border-green-700 text-center shadow-lg">
247+
<p className="text-green-400 text-2xl font-extrabold flex items-center justify-center gap-3">
248+
<span role="img" aria-label="confetti">🎉</span> Algorithm Complete!
249+
</p>
250+
</div>
251+
)}
252+
253+
</div>
254+
</>
255+
) : (
256+
<div className="text-center p-12 bg-gray-800 rounded-xl text-gray-400 text-xl shadow-xl border border-gray-700">
257+
<p className="mb-4">Welcome to the Counting Sort Visualizer!</p>
258+
<p>Enter a list of non-negative <b>numbers</b> (max value 50).</p>
259+
<p className="mt-2">Click <b>Re-Visualize</b> to begin.</p>
260+
</div>
261+
)}
262+
</div>
263+
</div>
264+
);
265+
}

src/pages/sorting/CountingSort.jsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import CountingSortVisualizer from "@/components/sorting/CountingSortVisualizer";
2+
import React from "react";
3+
4+
export default function CountingSortPage() {
5+
return <CountingSortVisualizer />;
6+
}

0 commit comments

Comments
 (0)