-
Numbers: 1 to {currentStep.n}, Select {currentStep.k}
-
- {Array.from({ length: currentStep.n }, (_, i) => i + 1).map((val) => (
-
- {val}
-
- ))}
-
-
-
Current Combination ({currentStep.comb.length}/{currentStep.k})
-
- {currentStep.comb.length > 0 ? (
- currentStep.comb.map((val, idx) => (
+
+
+
Numbers: 1 to {currentStep.n}, Select {currentStep.k}
+
+ {Array.from({ length: currentStep.n }, (_, i) => i + 1).map((val) => (
{val}
- ))
- ) : (
-
Empty
- )}
+ ))}
+
+
+
Current Combination ({currentStep.comb.length}/{currentStep.k})
+
+ {currentStep.comb.length > 0 ? (
+ currentStep.comb.map((val, idx) => (
+
+ {val}
+
+ ))
+ ) : (
+
Empty
+ )}
+
+
+
Result (res) - Total: {currentStep.res.length}
+
+ {currentStep.res.length > 0 ? (
+ currentStep.res.map((comb, idx) => (
+
+ [{comb.join(', ')}]
+
+ ))
+ ) : (
+
No combinations found yet.
+ )}
+
-
Result (res) - Total: {currentStep.res.length}
-
- {currentStep.res.length > 0 ? (
- currentStep.res.map((comb, idx) => (
-
- [{comb.join(', ')}]
-
- ))
- ) : (
-
No combinations found yet.
- )}
+
+
Algorithm Logic
+
+ {currentStep.message}
+
-
-
{currentStep.message}
-
-
-
-
-
+
-
+
);
diff --git a/src/components/visualizations/algorithms/ContainerWithMostWaterVisualization.tsx b/src/components/visualizations/algorithms/ContainerWithMostWaterVisualization.tsx
index 064976d..c4be5b4 100644
--- a/src/components/visualizations/algorithms/ContainerWithMostWaterVisualization.tsx
+++ b/src/components/visualizations/algorithms/ContainerWithMostWaterVisualization.tsx
@@ -1,23 +1,10 @@
-import { useState, useMemo } from 'react';
-import { motion, AnimatePresence } from 'framer-motion';
+import { useEffect, useState } from 'react';
+import { Card } from '@/components/ui/card';
import { VisualizationLayout } from '../shared/VisualizationLayout';
import { SimpleStepControls } from '../shared/SimpleStepControls';
import { VariablePanel } from '../shared/VariablePanel';
-import { AnimatedCodeEditor } from "../shared/AnimatedCodeEditor";
-import { Card } from '@/components/ui/card';
-import {
- Zap,
- Target,
- ArrowRight,
- ArrowLeft,
- Repeat,
- CheckCircle2,
- Info,
- Maximize,
- MoveHorizontal,
- Droplets,
- ArrowDown
-} from 'lucide-react';
+import { VisualizationCodePanel } from '../shared/VisualizationCodePanel';
+import type { StepLineNumberMap, VisualizationLanguageMap } from '@/types/visualization';
interface Step {
left: number;
@@ -26,396 +13,415 @@ interface Step {
width: number | '-';
currentHeight: number | '-';
currentArea: number | '-';
- message: string;
- lineNumber: number;
- phase: 'init' | 'loop' | 'calc-width' | 'calc-height' | 'calc-area' | 'update-max' | 'move' | 'done';
- insight?: string;
- isNewMax?: boolean;
+ explanation: string;
+ pseudoStep: string;
+ highlights: number[];
}
+const languages: VisualizationLanguageMap = {
+ typescript: `function maxArea(height: number[]): number {
+ let left = 0, right = height.length - 1;
+ let maxArea = 0;
+ while (left < right) {
+ const width = right - left;
+ const h = Math.min(height[left], height[right]);
+ const area = width * h;
+ maxArea = Math.max(maxArea, area);
+ if (height[left] < height[right]) {
+ left++;
+ } else {
+ right--;
+ }
+ }
+ return maxArea;
+}`,
+ python: `def maxArea(height: List[int]) -> int:
+ left, right = 0, len(height) - 1
+ max_area = 0
+ while left < right:
+ width = right - left
+ h = min(height[left], height[right])
+ area = width * h
+ max_area = max(max_area, area)
+ if height[left] < height[right]:
+ left += 1
+ else:
+ right -= 1
+ return max_area`,
+ java: `public static class Solution {
+ public int maxArea(int[] height) {
+ int left = 0, right = height.length - 1;
+ int maxArea = 0;
+ while (left < right) {
+ int width = right - left;
+ int h = Math.min(height[left], height[right]);
+ int area = width * h;
+ maxArea = Math.max(maxArea, area);
+ if (height[left] < height[right]) {
+ left++;
+ } else {
+ right--;
+ }
+ }
+ return maxArea;
+ }
+}`,
+ cpp: `class Solution {
+public:
+ int maxArea(vector
& height) {
+ int left = 0, right = height.size() - 1;
+ int maxArea = 0;
+ while (left < right) {
+ int width = right - left;
+ int h = min(height[left], height[right]);
+ int area = width * h;
+ maxArea = max(maxArea, area);
+ if (height[left] < height[right]) {
+ left++;
+ } else {
+ right--;
+ }
+ }
+ return maxArea;
+ }
+};`
+};
+
export const ContainerWithMostWaterVisualization = () => {
+ const [steps, setSteps] = useState([]);
+ const [stepLineNumbers, setStepLineNumbers] = useState({
+ typescript: [],
+ python: [],
+ java: [],
+ cpp: []
+ });
const [currentStepIndex, setCurrentStepIndex] = useState(0);
const heights = [1, 8, 6, 2, 5, 4, 8, 3, 7];
const maxHeight = Math.max(...heights);
- const code = `function maxArea(height: number[]): number {
- let left = 0, right = height.length - 1;
- let maxArea = 0;
-
- while (left < right) {
- const width = right - left;
- const h = Math.min(height[left], height[right]);
- const area = width * h;
+ useEffect(() => {
+ const generatedSteps: Step[] = [];
+ const stepLines: StepLineNumberMap = {
+ typescript: [],
+ python: [],
+ java: [],
+ cpp: []
+ };
- maxArea = Math.max(maxArea, area);
+ const addLines = (ts: number, py: number, java: number, cpp: number) => {
+ stepLines.typescript!.push(ts);
+ stepLines.python!.push(py);
+ stepLines.java!.push(java);
+ stepLines.cpp!.push(cpp);
+ };
- if (height[left] < height[right]) {
- left++;
- } else {
- right--;
- }
- }
- return maxArea;
-}`;
+ // Function Entry
+ generatedSteps.push({
+ left: 0,
+ right: heights.length - 1,
+ maxArea: 0,
+ width: '-',
+ currentHeight: '-',
+ currentArea: '-',
+ explanation: "Find the container that holds the most water using the two-pointer approach.",
+ pseudoStep: "FUNCTION maxArea(height)",
+ highlights: []
+ });
+ addLines(1, 1, 2, 3);
- const steps: Step[] = useMemo(() => {
- const s: Step[] = [];
let left = 0;
let right = heights.length - 1;
- let maxArea = 0;
- // Phase: Initialization
- s.push({
+ // Pointer Init
+ generatedSteps.push({
left,
right,
- maxArea,
+ maxArea: 0,
width: '-',
currentHeight: '-',
currentArea: '-',
- message: "Initialize pointers mapping to the ends of the array. Our goal is to maximize the area between these lines.",
- lineNumber: 2,
- phase: 'init',
- insight: "The area is limited by the shorter line, so we'll start with the maximum possible width."
+ explanation: `Initialize pointers to outer boundary lines: left = ${left}, right = ${right}.`,
+ pseudoStep: "SET left = 0, right = height.length - 1",
+ highlights: [left, right]
});
+ addLines(2, 2, 3, 4);
- s.push({
+ let maxArea = 0;
+
+ // maxArea Init
+ generatedSteps.push({
left,
right,
maxArea,
width: '-',
currentHeight: '-',
currentArea: '-',
- message: `Initialize maxArea to 0.`,
- lineNumber: 3,
- phase: 'init'
+ explanation: `Initialize maxArea to ${maxArea}.`,
+ pseudoStep: "SET maxArea = 0",
+ highlights: []
});
+ addLines(3, 3, 4, 5);
while (left < right) {
// Loop Check
- s.push({
+ generatedSteps.push({
left,
right,
maxArea,
width: '-',
currentHeight: '-',
currentArea: '-',
- message: `Condition left (${left}) < right (${right}) is true. Proceed to calculate area.`,
- lineNumber: 5,
- phase: 'loop'
+ explanation: `Loop check: left (${left}) < right (${right}). Proceed to compute container capacity.`,
+ pseudoStep: "WHILE left < right",
+ highlights: [left, right]
});
+ addLines(4, 4, 5, 6);
const width = right - left;
- s.push({
+
+ // Width calculation
+ generatedSteps.push({
left,
right,
maxArea,
width,
currentHeight: '-',
currentArea: '-',
- message: `Calculate width: right (${right}) - left (${left}) = ${width}.`,
- lineNumber: 6,
- phase: 'calc-width'
+ explanation: `Calculate container width: right (${right}) - left (${left}) = ${width}.`,
+ pseudoStep: "SET width = right - left",
+ highlights: [left, right]
});
+ addLines(5, 5, 6, 7);
const h = Math.min(heights[left], heights[right]);
- s.push({
+
+ // Height calculation
+ generatedSteps.push({
left,
right,
maxArea,
width,
currentHeight: h,
currentArea: '-',
- message: `Calculate height: min(${heights[left]}, ${heights[right]}) = ${h}.`,
- lineNumber: 7,
- phase: 'calc-height',
- insight: "The water level cannot exceed the shorter of the two lines."
+ explanation: `Calculate container height (limited by the shorter line): min(heights[left], heights[right]) = min(${heights[left]}, ${heights[right]}) = ${h}.`,
+ pseudoStep: "SET h = min(height[left], height[right])",
+ highlights: [left, right]
});
+ addLines(6, 6, 7, 8);
const area = width * h;
- s.push({
+
+ // Area calculation
+ generatedSteps.push({
left,
right,
maxArea,
width,
currentHeight: h,
currentArea: area,
- message: `Current area: width (${width}) × height (${h}) = ${area}.`,
- lineNumber: 8,
- phase: 'calc-area'
+ explanation: `Calculate current container area: width (${width}) * height (${h}) = ${area}.`,
+ pseudoStep: "SET area = width * h",
+ highlights: [left, right]
});
+ addLines(7, 7, 8, 9);
- const isNewMax = area > maxArea;
+ const oldMaxArea = maxArea;
maxArea = Math.max(maxArea, area);
- s.push({
+
+ // maxArea Update
+ generatedSteps.push({
left,
right,
maxArea,
width,
currentHeight: h,
currentArea: area,
- message: isNewMax
- ? `New maximum area found! Updated maxArea to ${maxArea}.`
- : `Current area (${area}) is not greater than maxArea (${maxArea}).`,
- lineNumber: 10,
- phase: 'update-max',
- isNewMax,
- insight: isNewMax ? "We've found a larger container!" : "No improvement over the global best."
+ explanation: area > oldMaxArea
+ ? `Current area ${area} is greater than maxArea (${oldMaxArea}). Update maxArea = ${maxArea}.`
+ : `Current area ${area} is not greater than maxArea (${oldMaxArea}). Keep maxArea = ${maxArea}.`,
+ pseudoStep: "SET maxArea = max(maxArea, area)",
+ highlights: [left, right]
});
+ addLines(8, 8, 9, 10);
+
+ // Pointer decision check
+ generatedSteps.push({
+ left,
+ right,
+ maxArea,
+ width,
+ currentHeight: h,
+ currentArea: area,
+ explanation: `Decide which pointer to move: Is heights[left] (${heights[left]}) < heights[right] (${heights[right]})?`,
+ pseudoStep: "IF height[left] < height[right]",
+ highlights: [left, right]
+ });
+ addLines(9, 9, 10, 11);
if (heights[left] < heights[right]) {
- s.push({
+ left++;
+ generatedSteps.push({
left,
right,
maxArea,
- width,
- currentHeight: h,
- currentArea: area,
- message: `heights[left] (${heights[left]}) < heights[right] (${heights[right]}). Move the shorter pointer inward to potentially find a taller line.`,
- lineNumber: 13,
- phase: 'move',
- insight: "To possibly find a larger area with a smaller width, we must increase the height. Only moving the shorter line can do this."
+ width: '-',
+ currentHeight: '-',
+ currentArea: '-',
+ explanation: `Since the left line is shorter, we shift the left pointer right to try and find a taller line. Set left = ${left}.`,
+ pseudoStep: "SET left = left + 1",
+ highlights: [left, right]
});
- left++;
+ addLines(10, 10, 11, 12);
} else {
- s.push({
+ right--;
+ generatedSteps.push({
left,
right,
maxArea,
- width,
- currentHeight: h,
- currentArea: area,
- message: `heights[right] (${heights[right]}) ≤ heights[left] (${heights[left]}). Move the shorter pointer inward.`,
- lineNumber: 15,
- phase: 'move',
- insight: "Moving the taller line would only reduce the width without being able to increase the height (which is already limited by the shorter line)."
+ width: '-',
+ currentHeight: '-',
+ currentArea: '-',
+ explanation: `Since the right line is shorter or equal, we shift the right pointer left to try and find a taller line. Set right = ${right}.`,
+ pseudoStep: "SET right = right - 1",
+ highlights: [left, right]
});
- right--;
+ addLines(12, 12, 13, 14);
}
}
- s.push({
+ // Done
+ generatedSteps.push({
left,
right,
maxArea,
- width: 0,
- currentHeight: 0,
- currentArea: 0,
- message: `Execution complete. Maximum area found is ${maxArea}.`,
- lineNumber: 18,
- phase: 'done',
- insight: "The Two-Pointer approach allows us to find the maximum area in O(n) time by optimally reducing the search space."
+ width: '-',
+ currentHeight: '-',
+ currentArea: '-',
+ explanation: `Pointers met. Loop terminated. The maximum container area found is ${maxArea}.`,
+ pseudoStep: "RETURN maxArea",
+ highlights: []
});
+ addLines(15, 13, 16, 17);
- return s;
+ setSteps(generatedSteps);
+ setStepLineNumbers(stepLines);
}, []);
- const currentStep = steps[currentStepIndex] || steps[0];
+ if (steps.length === 0) return null;
+
+ const currentStep = steps[currentStepIndex];
+ const pseudoSteps = steps.map(s => s.pseudoStep);
return (
+ }
leftContent={
-
-
- {/* Header Info */}
-
-
-
+
+
+
+
Container Volume Maximizer
-
-
-
- Phase: {currentStep.phase.replace('-', ' ')}
-
-
-
-
- {/* Pointer & Line Visualization */}
-
- {/* X-Axis Label */}
-
- x = 0
- x-axis (index)
- x = {heights.length - 1}
-
- {/* Overlay for Water Fill to ensure perfect alignment */}
-
-
- {currentStep.currentArea !== '-' && currentStep.width !== '-' && (
-
-
-
- )}
-
-
+ {/* Water Visualizer Box */}
+
+ {/* Visual Area Overlay */}
+ {currentStep.currentArea !== '-' && currentStep.width !== '-' && currentStep.left < currentStep.right && (
+
+ )}
+
+ {/* Bars */}
+
+ {heights.map((h, i) => {
+ const isLeft = i === currentStep.left;
+ const isRight = i === currentStep.right;
+ const isPointer = isLeft || isRight;
+ const barHeight = (h / maxHeight) * 100;
- {heights.map((h, i) => {
- const isLeft = i === currentStep.left;
- const isRight = i === currentStep.right;
- const isMember = isLeft || isRight;
- const barHeight = (h / maxHeight) * 100;
-
- return (
-
- {/* Floating Pointers Pinpointing Line Tops */}
-
- {isMember && (
-
-
- {isLeft ? 'LEFT' : 'RIGHT'}
+ return (
+
+ {/* Pointer Label */}
+ {isPointer && (
+
+ {isLeft ? 'L' : 'R'}
-
-
- )}
-
-
- {/* Height Value */}
-
- {h}
-
-
- {/* The Line (from (i,0) to (i,height[i])) */}
-
-
- );
- })}
-
-
- {/* Stats Grid */}
-
-
-
- width
-
- {currentStep.width}
-
-
-
- height
-
- {currentStep.currentHeight}
-
-
-
- area
-
- {currentStep.currentArea}
-
-
-
- best
-
- {currentStep.maxArea}
-
-
-
+ )}
-
-
-
- {currentStep.isNewMax ? : }
-
-
-
- Reasoning Insight
-
-
- {currentStep.message}
-
-
-
-
-
- }
- rightContent={
-
-
+ {/* Line height number */}
+
+ {h}
+
-
-
-
- Algorithm intuition
-
-
-
- {currentStep.insight ? (
-
-
-
-
-
- {currentStep.insight}
-
-
- ) : (
-
-
-
-
-
- Optimizing search space...
-
-
- )}
+ {/* Bar Pillar */}
+
-
-
- left height
- {heights[currentStep.left]}
-
-
- right height
- {heights[currentStep.right]}
+ {/* Index */}
+ [{i}]
+
+ );
+ })}
-
-
+
+
+
+
+
+
+ Step Explanation
+ {currentStep.explanation}
+
+
+
+
}
- controls={
-
setCurrentStepIndex(0)}
/>
}
/>
);
};
+
diff --git a/src/components/visualizations/algorithms/ContainsDuplicateVisualization.tsx b/src/components/visualizations/algorithms/ContainsDuplicateVisualization.tsx
index ca30f55..358a9e2 100644
--- a/src/components/visualizations/algorithms/ContainsDuplicateVisualization.tsx
+++ b/src/components/visualizations/algorithms/ContainsDuplicateVisualization.tsx
@@ -1,9 +1,10 @@
-import React, { useState, useMemo } from 'react';
+import React, { useState } from 'react';
import { Card } from '@/components/ui/card';
import { SimpleStepControls } from '../shared/SimpleStepControls';
import { VariablePanel } from '../shared/VariablePanel';
-import { AnimatedCodeEditor } from '../shared/AnimatedCodeEditor';
+import { VisualizationCodePanel } from '../shared/VisualizationCodePanel';
import { VisualizationLayout } from '../shared/VisualizationLayout';
+import type { StepLineNumberMap, VisualizationLanguageMap } from '@/types/visualization';
interface Step {
nums: number[];
@@ -11,111 +12,174 @@ interface Step {
seen: Set;
variables: Record;
explanation: string;
- highlightedLines: number[];
- lineExecution: string;
+ pseudoStep: string;
}
-export const ContainsDuplicateVisualization: React.FC = () => {
- const [currentStep, setCurrentStep] = useState(0);
+const languages: VisualizationLanguageMap = {
+ typescript: `function containsDuplicate(nums: number[]): boolean {
+ const seen = new Set();
+ for (const num of nums) {
+ if (seen.has(num)) {
+ return true;
+ }
+ seen.add(num);
+ }
+ return false;
+}`,
- const nums = [1, 2, 3, 1];
+ python: `def containsDuplicate(nums: List[int]) -> bool:
+ seen = set()
+ for num in nums:
+ if num in seen:
+ return True
+ seen.add(num)
+ return False`,
- const code = `function containsDuplicate(nums: number[]): boolean {
- const seen = new Set();
- for (let i = 0; i < nums.length; i++) {
- if (seen.has(nums[i])) {
- return true;
+ java: `public static class Solution {
+ public boolean containsDuplicate(int[] nums) {
+ Set seen = new HashSet<>();
+ for (int num : nums) {
+ if (seen.contains(num)) {
+ return true;
+ }
+ seen.add(num);
+ }
+ return false;
}
- seen.add(nums[i]);
- }
- return false;
-}`;
+}`,
- const steps = useMemo(() => {
- const stepsList: Step[] = [];
- const seen = new Set();
+ cpp: `class Solution {
+public:
+ bool containsDuplicate(vector& nums) {
+ unordered_set seen;
+ for (int num : nums) {
+ if (seen.count(num)) {
+ return true;
+ }
+ seen.insert(num);
+ }
+ return false;
+ }
+};`,
+};
- stepsList.push({
+function generateVisualizationData() {
+ const nums = [1, 2, 3, 1];
+ const steps: Step[] = [];
+ const seen = new Set();
+
+ const stepLineNumbers: StepLineNumberMap = {
+ typescript: [],
+ python: [],
+ java: [],
+ cpp: []
+ };
+
+ const addLines = (ts: number, py: number, java: number, cpp: number) => {
+ stepLineNumbers.typescript!.push(ts);
+ stepLineNumbers.python!.push(py);
+ stepLineNumbers.java!.push(java);
+ stepLineNumbers.cpp!.push(cpp);
+ };
+
+ // 1. Initial State
+ steps.push({
+ nums,
+ highlights: [],
+ seen: new Set(seen),
+ variables: { nums: `[${nums.join(', ')}]`, seen: '{}' },
+ explanation: "Given an array of integers, check if any value appears at least twice.",
+ pseudoStep: "CALL containsDuplicate(nums)"
+ });
+ addLines(1, 1, 2, 3);
+
+ // 2. Initialize Set
+ steps.push({
+ nums,
+ highlights: [],
+ seen: new Set(seen),
+ variables: { nums: `[${nums.join(', ')}]`, seen: '{}' },
+ explanation: "Initialize an empty set to keep track of the numbers we have seen so far.",
+ pseudoStep: "SET seen = {} (empty set)"
+ });
+ addLines(2, 2, 3, 4);
+
+ for (let i = 0; i < nums.length; i++) {
+ const num = nums[i];
+
+ // Loop header
+ steps.push({
nums,
- highlights: [],
+ highlights: [i],
seen: new Set(seen),
- variables: { nums: `[${nums.join(', ')}]` },
- explanation: "Given an array of integers, check if any value appears at least twice.",
- lineExecution: "function containsDuplicate(nums: number[]): boolean {",
- highlightedLines: [1]
+ variables: { i, num, seen: `{${Array.from(seen).join(', ')}}` },
+ explanation: `Examine the next number in the array: ${num} at index ${i}.`,
+ pseudoStep: `FOR num = ${num} in nums`
});
+ addLines(3, 3, 4, 5);
+
+ const isDuplicate = seen.has(num);
- stepsList.push({
+ // Check condition
+ steps.push({
nums,
- highlights: [],
+ highlights: [i],
seen: new Set(seen),
- variables: { seen: "{}" },
- explanation: "Initialize an empty Set to store numbers we encounter.",
- lineExecution: "const seen = new Set();",
- highlightedLines: [2]
+ variables: { i, num, seen: `{${Array.from(seen).join(', ')}}`, "seen.has(num)": isDuplicate },
+ explanation: `Check if ${num} is already present in our 'seen' set.`,
+ pseudoStep: `IF num (${num}) IN seen → ${isDuplicate ? 'YES ✓' : 'NO ✗'}`
});
+ addLines(4, 4, 5, 6);
- for (let i = 0; i < nums.length; i++) {
- stepsList.push({
+ if (isDuplicate) {
+ steps.push({
nums,
highlights: [i],
seen: new Set(seen),
- variables: { i, "nums[i]": nums[i], seen: `{${Array.from(seen).join(', ')}}` },
- explanation: `Iteration i = ${i}: Examining the value ${nums[i]}.`,
- lineExecution: "for (let i = 0; i < nums.length; i++) {",
- highlightedLines: [3]
- });
-
- const duplicate = seen.has(nums[i]);
- stepsList.push({
- nums,
- highlights: [i],
- seen: new Set(seen),
- variables: { "seen.has(nums[i])": duplicate },
- explanation: `Check if ${nums[i]} is already in our 'seen' set. ${duplicate ? "Yes, it is!" : "No, not yet."}`,
- lineExecution: "if (seen.has(nums[i])) {",
- highlightedLines: [4]
- });
-
- if (duplicate) {
- stepsList.push({
- nums,
- highlights: [i],
- seen: new Set(seen),
- variables: { result: true },
- explanation: `Found a duplicate! Since ${nums[i]} exists in the set, we return true.`,
- lineExecution: "return true;",
- highlightedLines: [5]
- });
- return stepsList;
- }
-
- seen.add(nums[i]);
- stepsList.push({
- nums,
- highlights: [i],
- seen: new Set(seen),
- variables: { seen: `{${Array.from(seen).join(', ')}}` },
- explanation: `Add ${nums[i]} to the 'seen' set and continue to the next index.`,
- lineExecution: "seen.add(nums[i]);",
- highlightedLines: [7]
+ variables: { i, num, seen: `{${Array.from(seen).join(', ')}}`, result: true },
+ explanation: `Found a duplicate! Since ${num} is already in the set, we return true.`,
+ pseudoStep: `RETURN true`
});
+ addLines(5, 5, 6, 7);
+ return { steps, stepLineNumbers };
}
- stepsList.push({
+ seen.add(num);
+
+ // Add to set
+ steps.push({
nums,
- highlights: [],
+ highlights: [i],
seen: new Set(seen),
- variables: { result: false },
- explanation: "Finished loop without finding any duplicates. Return false.",
- lineExecution: "return false;",
- highlightedLines: [10]
+ variables: { i, num, seen: `{${Array.from(seen).join(', ')}}` },
+ explanation: `${num} is not a duplicate. Add it to the 'seen' set and continue.`,
+ pseudoStep: `CALL seen.add(${num})`
});
+ addLines(7, 6, 8, 9);
+ }
- return stepsList;
- }, [nums]);
+ // Return false if no duplicates
+ steps.push({
+ nums,
+ highlights: [],
+ seen: new Set(seen),
+ variables: { seen: `{${Array.from(seen).join(', ')}}`, result: false },
+ explanation: "Finished iterating through the array without finding any duplicates. Return false.",
+ pseudoStep: "RETURN false"
+ });
+ addLines(9, 7, 10, 11);
- const step = steps[currentStep];
+ return { steps, stepLineNumbers };
+}
+
+export const ContainsDuplicateVisualization: React.FC = () => {
+ const [{ steps, stepLineNumbers }] = useState(generateVisualizationData);
+ const [currentStepIndex, setCurrentStepIndex] = useState(0);
+
+ if (steps.length === 0) return null;
+
+ const currentStep = steps[currentStepIndex];
+ const pseudoSteps = steps.map(s => s.pseudoStep);
return (
{
Input Array
- {nums.map((num, idx) => {
- const isCurrent = step.highlights.includes(idx);
+ {currentStep.nums.map((num, idx) => {
+ const isCurrent = currentStep.highlights.includes(idx);
return (
{
Seen Set Content
- {step.seen.size === 0 ? (
+ {currentStep.seen.size === 0 ? (
Empty Set
) : (
- Array.from(step.seen).map((val) => (
+ Array.from(currentStep.seen).map((val) => (
{val}
@@ -165,50 +229,33 @@ export const ContainsDuplicateVisualization: React.FC = () => {
-
-
-
-
-
- Current Execution
-
-
- {step.lineExecution}
-
-
-
-
- Commentary
-
-
- {step.explanation}
-
-
-
-
+
+
+
+ Commentary
+
+
+ {currentStep.explanation}
+
+
+
}
rightContent={
-
+
setCurrentStepIndex(0)}
+ />
}
controls={
}
/>
diff --git a/src/components/visualizations/algorithms/CountingBitsVisualization.tsx b/src/components/visualizations/algorithms/CountingBitsVisualization.tsx
index 7b7bef3..834fcdf 100644
--- a/src/components/visualizations/algorithms/CountingBitsVisualization.tsx
+++ b/src/components/visualizations/algorithms/CountingBitsVisualization.tsx
@@ -1,293 +1,254 @@
-import { useState } from 'react';
-import { SimpleArrayVisualization } from '../shared/SimpleArrayVisualization';
-import { StepControls } from '../shared/StepControls';
-import { VariablePanel } from '../shared/VariablePanel';
-import { AnimatedCodeEditor } from "../shared/AnimatedCodeEditor";
+import { useEffect, useState } from 'react';
import { Card } from '@/components/ui/card';
+import { VisualizationLayout } from '../shared/VisualizationLayout';
+import { SimpleStepControls } from '../shared/SimpleStepControls';
+import { VariablePanel } from '../shared/VariablePanel';
+import { VisualizationCodePanel } from '../shared/VisualizationCodePanel';
+import type { StepLineNumberMap, VisualizationLanguageMap } from '@/types/visualization';
interface Step {
array: number[];
- highlighting: number[];
+ highlights: number[];
variables: Record;
explanation: string;
- highlightedLines: number[];
+ pseudoStep: string;
}
+const languages: VisualizationLanguageMap = {
+ typescript: `function countBits(n: number): number[] {
+ const ans: number[] = new Array(n + 1).fill(0);
+ let offset = 1;
+ for (let i = 1; i <= n; i++) {
+ if (i === offset * 2) {
+ offset = i;
+ }
+ ans[i] = 1 + ans[i - offset];
+ }
+ return ans;
+}`,
+ python: `def countBits(n: int):
+ ans = [0] * (n + 1)
+ offset = 1
+ for i in range(1, n + 1):
+ if i == offset * 2:
+ offset = i
+ ans[i] = 1 + ans[i - offset]
+ return ans`,
+ java: `public int[] countBits(int n) {
+ int[] ans = new int[n + 1];
+ int offset = 1;
+ for (int i = 1; i <= n; i++) {
+ if (i == offset * 2) {
+ offset = i;
+ }
+ ans[i] = 1 + ans[i - offset];
+ }
+ return ans;
+}`,
+ cpp: `vector countBits(int n) {
+ vector ans(n + 1, 0);
+ int offset = 1;
+ for (int i = 1; i <= n; i++) {
+ if (i == offset * 2) {
+ offset = i;
+ }
+ ans[i] = 1 + ans[i - offset];
+ }
+ return ans;
+}`
+};
+
export const CountingBitsVisualization = () => {
- const [currentStep, setCurrentStep] = useState(0);
+ const [steps, setSteps] = useState([]);
+ const [stepLineNumbers, setStepLineNumbers] = useState({
+ typescript: [],
+ python: [],
+ java: [],
+ cpp: []
+ });
+ const [currentStepIndex, setCurrentStepIndex] = useState(0);
- const steps: Step[] = [
- {
- array: [0, 0, 0, 0, 0, 0, 0, 0, 0],
- highlighting: [],
- variables: { n: 8 },
- explanation: "Starting countBits with n = 8.",
- highlightedLines: [1]
- },
- {
- array: [0, 0, 0, 0, 0, 0, 0, 0, 0],
- highlighting: [],
- variables: { n: 8, dp: "[0,0,0,0,0,0,0,0,0]" },
- explanation: "Initialize dp array of size n + 1 (9) with zeros.",
- highlightedLines: [2]
- },
- {
- array: [0, 0, 0, 0, 0, 0, 0, 0, 0],
- highlighting: [],
- variables: { n: 8, dp: "[0,0,0,0,0,0,0,0,0]", offset: 1 },
- explanation: "Initialize offset = 1.",
- highlightedLines: [3]
- },
- {
- array: [0, 0, 0, 0, 0, 0, 0, 0, 0],
- highlighting: [1],
- variables: { n: 8, dp: "[0,...]", offset: 1, i: 1 },
- explanation: "Start loop at i = 1.",
- highlightedLines: [5]
- },
- {
- array: [0, 0, 0, 0, 0, 0, 0, 0, 0],
- highlighting: [1],
- variables: { n: 8, dp: "[0,...]", offset: 1, i: 1 },
- explanation: "Check power of 2: offset * 2 (2) === i (1)? False.",
- highlightedLines: [6]
- },
- {
- array: [0, 1, 0, 0, 0, 0, 0, 0, 0],
- highlighting: [1, 0],
- variables: { n: 8, dp: "[0,1,...]", offset: 1, i: 1 },
- explanation: "dp[1] = 1 + dp[1 - 1] = 1 + dp[0] = 1 + 0 = 1.",
- highlightedLines: [9]
- },
- {
- array: [0, 1, 0, 0, 0, 0, 0, 0, 0],
- highlighting: [2],
- variables: { n: 8, dp: "[0,1,...]", offset: 1, i: 2 },
- explanation: "Loop i = 2.",
- highlightedLines: [5]
- },
- {
- array: [0, 1, 0, 0, 0, 0, 0, 0, 0],
- highlighting: [2],
- variables: { n: 8, dp: "[0,1,...]", offset: 1, i: 2 },
- explanation: "Check power of 2: offset * 2 (2) === i (2)? True.",
- highlightedLines: [6]
- },
- {
- array: [0, 1, 0, 0, 0, 0, 0, 0, 0],
- highlighting: [2],
- variables: { n: 8, dp: "[0,1,...]", offset: 2, i: 2 },
- explanation: "Update offset = i = 2.",
- highlightedLines: [7]
- },
- {
- array: [0, 1, 1, 0, 0, 0, 0, 0, 0],
- highlighting: [2, 0],
- variables: { n: 8, dp: "[0,1,1,...]", offset: 2, i: 2 },
- explanation: "dp[2] = 1 + dp[2 - 2] = 1 + dp[0] = 1 + 0 = 1.",
- highlightedLines: [9]
- },
- {
- array: [0, 1, 1, 0, 0, 0, 0, 0, 0],
- highlighting: [3],
- variables: { n: 8, dp: "[0,1,1,...]", offset: 2, i: 3 },
- explanation: "Loop i = 3.",
- highlightedLines: [5]
- },
- {
- array: [0, 1, 1, 0, 0, 0, 0, 0, 0],
- highlighting: [3],
- variables: { n: 8, dp: "[0,1,1,...]", offset: 2, i: 3 },
- explanation: "Check power of 2: offset * 2 (4) === i (3)? False.",
- highlightedLines: [6]
- },
- {
- array: [0, 1, 1, 2, 0, 0, 0, 0, 0],
- highlighting: [3, 1],
- variables: { n: 8, dp: "[0,1,1,2,...]", offset: 2, i: 3 },
- explanation: "dp[3] = 1 + dp[3 - 2] = 1 + dp[1] = 1 + 1 = 2.",
- highlightedLines: [9]
- },
- {
- array: [0, 1, 1, 2, 0, 0, 0, 0, 0],
- highlighting: [4],
- variables: { n: 8, dp: "[0,1,1,2,...]", offset: 2, i: 4 },
- explanation: "Loop i = 4.",
- highlightedLines: [5]
- },
- {
- array: [0, 1, 1, 2, 0, 0, 0, 0, 0],
- highlighting: [4],
- variables: { n: 8, dp: "[0,1,1,2,...]", offset: 2, i: 4 },
- explanation: "Check power of 2: offset * 2 (4) === i (4)? True.",
- highlightedLines: [6]
- },
- {
- array: [0, 1, 1, 2, 0, 0, 0, 0, 0],
- highlighting: [4],
- variables: { n: 8, dp: "[0,1,1,2,...]", offset: 4, i: 4 },
- explanation: "Update offset = i = 4.",
- highlightedLines: [7]
- },
- {
- array: [0, 1, 1, 2, 1, 0, 0, 0, 0],
- highlighting: [4, 0],
- variables: { n: 8, dp: "[0,1,1,2,1,...]", offset: 4, i: 4 },
- explanation: "dp[4] = 1 + dp[4 - 4] = 1 + dp[0] = 1 + 0 = 1.",
- highlightedLines: [9]
- },
- {
- array: [0, 1, 1, 2, 1, 0, 0, 0, 0],
- highlighting: [5],
- variables: { n: 8, dp: "[0,1,1,2,1,...]", offset: 4, i: 5 },
- explanation: "Loop i = 5.",
- highlightedLines: [5]
- },
- {
- array: [0, 1, 1, 2, 1, 2, 0, 0, 0],
- highlighting: [5, 1],
- variables: { n: 8, dp: "[0,1,1,2,1,2,...]", offset: 4, i: 5 },
- explanation: "dp[5] = 1 + dp[5 - 4] = 1 + dp[1] = 1 + 1 = 2.",
- highlightedLines: [9]
- },
- {
- array: [0, 1, 1, 2, 1, 2, 0, 0, 0],
- highlighting: [6],
- variables: { n: 8, dp: "[0,1,1,2,1,2,...]", offset: 4, i: 6 },
- explanation: "Loop i = 6.",
- highlightedLines: [5]
- },
- {
- array: [0, 1, 1, 2, 1, 2, 2, 0, 0],
- highlighting: [6, 2],
- variables: { n: 8, dp: "[0,1,1,2,1,2,2,...]", offset: 4, i: 6 },
- explanation: "dp[6] = 1 + dp[6 - 4] = 1 + dp[2] = 1 + 1 = 2.",
- highlightedLines: [9]
- },
- {
- array: [0, 1, 1, 2, 1, 2, 2, 0, 0],
- highlighting: [7],
- variables: { n: 8, dp: "[0,1,1,2,1,2,2,...]", offset: 4, i: 7 },
- explanation: "Loop i = 7.",
- highlightedLines: [5]
- },
- {
- array: [0, 1, 1, 2, 1, 2, 2, 3, 0],
- highlighting: [7, 3],
- variables: { n: 8, dp: "[0,1,1,2,1,2,2,3,...]", offset: 4, i: 7 },
- explanation: "dp[7] = 1 + dp[7 - 4] = 1 + dp[3] = 1 + 2 = 3.",
- highlightedLines: [9]
- },
- {
- array: [0, 1, 1, 2, 1, 2, 2, 3, 0],
- highlighting: [8],
- variables: { n: 8, dp: "[0,1,1,2,1,2,2,3,...]", offset: 4, i: 8 },
- explanation: "Loop i = 8.",
- highlightedLines: [5]
- },
- {
- array: [0, 1, 1, 2, 1, 2, 2, 3, 0],
- highlighting: [8],
- variables: { n: 8, dp: "[0,1,1,2,1,2,2,3,...]", offset: 4, i: 8 },
- explanation: "Check power of 2: offset * 2 (8) === i (8)? True.",
- highlightedLines: [6]
- },
- {
- array: [0, 1, 1, 2, 1, 2, 2, 3, 0],
- highlighting: [8],
- variables: { n: 8, dp: "[0,1,1,2,1,2,2,3,...]", offset: 8, i: 8 },
- explanation: "Update offset = i = 8.",
- highlightedLines: [7]
- },
- {
- array: [0, 1, 1, 2, 1, 2, 2, 3, 1],
- highlighting: [8, 0],
- variables: { n: 8, dp: "[0,1,1,2,1,2,2,3,1]", offset: 8, i: 8 },
- explanation: "dp[8] = 1 + dp[8 - 8] = 1 + dp[0] = 1 + 0 = 1.",
- highlightedLines: [9]
- },
- {
- array: [0, 1, 1, 2, 1, 2, 2, 3, 1],
- highlighting: [],
- variables: { n: 8, dp: "[0,1,1,2,1,2,2,3,1]", offset: 8, i: 9 },
- explanation: "Loop condition i <= n (9 <= 8) is false. Exit loop.",
- highlightedLines: [5]
- },
- {
- array: [0, 1, 1, 2, 1, 2, 2, 3, 1],
- highlighting: [],
- variables: { n: 8, dp: "[0,1,1,2,1,2,2,3,1]" },
- explanation: "Return dp array.",
- highlightedLines: [11]
- }
- ];
+ useEffect(() => {
+ const n = 8;
+ const generatedSteps: Step[] = [];
+ const stepLines: StepLineNumberMap = {
+ typescript: [],
+ python: [],
+ java: [],
+ cpp: []
+ };
+
+ const addLines = (ts: number, py: number, java: number, cpp: number) => {
+ stepLines.typescript!.push(ts);
+ stepLines.python!.push(py);
+ stepLines.java!.push(java);
+ stepLines.cpp!.push(cpp);
+ };
+
+ const dp = new Array(n + 1).fill(0);
+
+ // Function Entry
+ generatedSteps.push({
+ array: [...dp],
+ highlights: [],
+ variables: { n, i: '-', offset: '-' },
+ explanation: `Count set bits for all numbers from 0 to ${n}.`,
+ pseudoStep: "FUNCTION countBits(n)"
+ });
+ addLines(1, 1, 1, 1);
+
+ // Array Init
+ generatedSteps.push({
+ array: [...dp],
+ highlights: [],
+ variables: { n, i: '-', offset: '-' },
+ explanation: `Initialize ans array of size ${n + 1} with 0s.`,
+ pseudoStep: "SET ans = [0] * (n + 1)"
+ });
+ addLines(2, 2, 2, 2);
- const code = `function countBits(n: number): number[] {
- const dp: number[] = new Array(n + 1).fill(0);
let offset = 1;
+ // Offset Init
+ generatedSteps.push({
+ array: [...dp],
+ highlights: [],
+ variables: { n, i: '-', offset },
+ explanation: "Initialize offset to 1 to track the latest power of 2.",
+ pseudoStep: "SET offset = 1"
+ });
+ addLines(3, 3, 3, 3);
for (let i = 1; i <= n; i++) {
- if (offset * 2 === i) {
- offset = i;
- }
- dp[i] = 1 + dp[i - offset];
+ // Loop Check i
+ generatedSteps.push({
+ array: [...dp],
+ highlights: [i],
+ variables: { n, i, offset },
+ explanation: `Processing number i = ${i}.`,
+ pseudoStep: `FOR i = ${i} to ${n}`
+ });
+ addLines(4, 4, 4, 4);
+
+ // Power of 2 check
+ generatedSteps.push({
+ array: [...dp],
+ highlights: [i],
+ variables: { n, i, offset },
+ explanation: `Check if i (${i}) is the next power of 2 (i === offset * 2 = ${offset * 2}).`,
+ pseudoStep: `IF i == offset * 2`
+ });
+ addLines(5, 5, 5, 5);
+
+ if (i === offset * 2) {
+ offset = i;
+ generatedSteps.push({
+ array: [...dp],
+ highlights: [i],
+ variables: { n, i, offset },
+ explanation: `New power of 2 detected. Update offset to ${offset}.`,
+ pseudoStep: "SET offset = i"
+ });
+ addLines(6, 6, 6, 6);
+ }
+
+ dp[i] = 1 + dp[i - offset];
+ generatedSteps.push({
+ array: [...dp],
+ highlights: [i, i - offset],
+ variables: { n, i, offset },
+ explanation: `Calculate set bits for ${i} using dynamic programming: ans[${i}] = 1 + ans[${i} - ${offset}] = 1 + ans[${i - offset}] = ${dp[i]}.`,
+ pseudoStep: `SET ans[i] = 1 + ans[i - offset]`
+ });
+ addLines(8, 7, 8, 8);
}
- return dp;
-}`;
- const step = steps[currentStep];
+ // Return
+ generatedSteps.push({
+ array: [...dp],
+ highlights: [],
+ variables: { n, i: '-', offset },
+ explanation: `Return the final computed ans array: [${dp.join(', ')}].`,
+ pseudoStep: "RETURN ans"
+ });
+ addLines(10, 8, 10, 10);
+
+ setSteps(generatedSteps);
+ setStepLineNumbers(stepLines);
+ }, []);
+
+ if (steps.length === 0) return null;
+
+ const currentStep = steps[currentStepIndex];
+ const pseudoSteps = steps.map(s => s.pseudoStep);
return (
-
-
{ }}
- onPause={() => { }}
- onStepForward={() => currentStep < steps.length - 1 && setCurrentStep(prev => prev + 1)}
- onStepBack={() => currentStep > 0 && setCurrentStep(prev => prev - 1)}
- onReset={() => setCurrentStep(0)}
- speed={1}
- onSpeedChange={() => { }}
- currentStep={currentStep}
- totalSteps={steps.length - 1}
- />
+
+ }
+ leftContent={
+
+
+
+
+ Counting Bits (DP)
+
-
-
-
-
-
+
+
+
DP Array (ans)
+
+ {currentStep.array.map((value, index) => {
+ const isHighlighted = currentStep.highlights.includes(index);
+ return (
+
+
+ {value}
+
+
[{index}]
+
+ );
+ })}
+
+
-
-
-
Explanation:
-
- {step.explanation}
+
+
DP Relation Logic:
+
• Any number i can be broken into: 1 + ans[i - offset].
+
• The 1 represents the most significant bit (the offset power of 2).
+
• ans[i - offset] retrieves the set bits for the remaining value.
+
-
-
-
-
- DP Strategy (Offset):
-
-
• Offset tracks the largest power of 2 less than or equal to i.
-
• Bits for i = 1 (most significant bit) + bits for (i - offset).
-
-
+
+
-
+
+
+
+ Step Explanation
+ {currentStep.explanation}
+
+
+
-
-
setCurrentStepIndex(0)}
/>
-
-
+ }
+ />
);
};
\ No newline at end of file
diff --git a/src/components/visualizations/algorithms/CyclicSortVisualization.tsx b/src/components/visualizations/algorithms/CyclicSortVisualization.tsx
index 6f964ab..26bf016 100644
--- a/src/components/visualizations/algorithms/CyclicSortVisualization.tsx
+++ b/src/components/visualizations/algorithms/CyclicSortVisualization.tsx
@@ -1,96 +1,209 @@
import { useEffect, useRef, useState } from 'react';
-
-import { AnimatedCodeEditor } from "../shared/AnimatedCodeEditor";
+import { VisualizationCodePanel } from '../shared/VisualizationCodePanel';
import { StepControls } from '../shared/StepControls';
import { VariablePanel } from '../shared/VariablePanel';
+import type { StepLineNumberMap, VisualizationLanguageMap } from '@/types/visualization';
interface Step {
array: number[];
i: number;
correctIndex: number;
- message: string;
- lineNumber: number;
+ explanation: string;
+ pseudoStep: string;
+ variables: Record
;
comparingIndices: number[];
isSwap?: boolean;
}
-export const CyclicSortVisualization = () => {
- const [steps, setSteps] = useState([]);
- const [currentStepIndex, setCurrentStepIndex] = useState(0);
- const [isPlaying, setIsPlaying] = useState(false);
- const [speed, setSpeed] = useState(1);
- const intervalRef = useRef(null);
+// ─── Hardcoded code per language (no comments) ──────────────────────────────
- const code = `function cyclicSort(nums) {
+const languages: VisualizationLanguageMap = {
+ typescript: `function cyclicSort(nums: number[]): number[] {
let i = 0;
-
while (i < nums.length) {
const correctIndex = nums[i] - 1;
-
if (nums[i] !== nums[correctIndex]) {
- // Swap to correct position
[nums[i], nums[correctIndex]] = [nums[correctIndex], nums[i]];
} else {
i++;
}
}
-
return nums;
-}`;
-
- const generateSteps = () => {
- const nums = [3, 1, 5, 4, 2];
- const newSteps: Step[] = [];
- let i = 0;
-
- const addStep = (arr: number[], msg: string, line: number, currentI: number, targetIdx: number, comparing: number[] = [], swap: boolean = false) => {
- newSteps.push({
- array: [...arr],
- i: currentI,
- correctIndex: targetIdx,
- message: msg,
- lineNumber: line,
- comparingIndices: comparing,
- isSwap: swap
- });
- };
+}`,
+
+ python: `def cyclic_sort(nums):
+ i = 0
+ while i < len(nums):
+ correct_index = nums[i] - 1
+ if nums[i] != nums[correct_index]:
+ nums[i], nums[correct_index] = nums[correct_index], nums[i]
+ else:
+ i += 1
+ return nums`,
+
+ java: `public static class Solution {
+ public int[] cyclicSort(int[] nums) {
+ int i = 0;
+ while (i < nums.length) {
+ int correctIndex = nums[i] - 1;
+ if (nums[i] != nums[correctIndex]) {
+ int temp = nums[i];
+ nums[i] = nums[correctIndex];
+ nums[correctIndex] = temp;
+ } else {
+ i++;
+ }
+ }
+ return nums;
+ }
+}`,
+
+ cpp: `class Solution {
+ public:
+ void cyclicSort(vector &nums) {
+ int i = 0;
+ while (i < nums.size()) {
+ int correctIdx = nums[i] - 1;
+ if (nums[i] != nums[correctIdx]) {
+ swap(nums[i], nums[correctIdx]);
+ } else {
+ i++;
+ }
+ }
+ }
+};`,
+};
+
+// ─── Step generator ──────────────────────────────────────────────────────────
+
+function generateVisualizationData() {
+ const nums = [3, 1, 5, 4, 2];
+ const steps: Step[] = [];
+ const array = [...nums];
+ let i = 0;
+
+ const stepLineNumbers: StepLineNumberMap = {
+ typescript: [],
+ python: [],
+ java: [],
+ cpp: []
+ };
+
+ const addLines = (ts: number, py: number, java: number, cpp: number) => {
+ stepLineNumbers.typescript!.push(ts);
+ stepLineNumbers.python!.push(py);
+ stepLineNumbers.java!.push(java);
+ stepLineNumbers.cpp!.push(cpp);
+ };
+
+ steps.push({
+ array: [...array],
+ i: 0,
+ correctIndex: -1,
+ explanation: 'Start cyclic sort: place each number at its correct index (value - 1).',
+ pseudoStep: 'START cyclicSort',
+ variables: { i: 0, 'nums[i]': array[0], correctIndex: '-', 'nums[correctIndex]': '-', array: [...array] },
+ comparingIndices: [],
+ isSwap: false
+ });
+ addLines(2, 2, 3, 4);
+
+ while (i < array.length) {
+ const val = array[i];
+ const correctIndex = val - 1;
- addStep(nums, 'Start cyclic sort: place each number at its correct index (value - 1)', 1, 0, -1);
+ steps.push({
+ array: [...array],
+ i,
+ correctIndex: -1,
+ explanation: `Check loop condition: i (${i}) < nums.length (${array.length}) is true.`,
+ pseudoStep: `WHILE i < nums.length → ${i} < ${array.length} → YES ✓`,
+ variables: { i, 'nums[i]': val, correctIndex: '-', 'nums[correctIndex]': '-', array: [...array] },
+ comparingIndices: [i],
+ isSwap: false
+ });
+ addLines(3, 3, 4, 5);
- while (i < nums.length) {
- const val = nums[i];
- const correctIndex = val - 1;
+ steps.push({
+ array: [...array],
+ i,
+ correctIndex,
+ explanation: `Value nums[i] = ${val}. Its correct index is value - 1 = ${correctIndex}.`,
+ pseudoStep: `SET correctIndex = nums[i] − 1 → ${val} − 1 = ${correctIndex}`,
+ variables: { i, 'nums[i]': val, correctIndex, 'nums[correctIndex]': array[correctIndex], array: [...array] },
+ comparingIndices: [i],
+ isSwap: false
+ });
+ addLines(4, 4, 5, 6);
- // Calculate correctIndex
- addStep(nums, `Index i=${i}, value=${val}. Should be at index ${val}-1 = ${correctIndex}`, 5, i, correctIndex, [i]);
+ steps.push({
+ array: [...array],
+ i,
+ correctIndex,
+ explanation: `Compare nums[i] (${val}) with nums[correctIndex] (${array[correctIndex]}).`,
+ pseudoStep: `IF nums[i] != nums[correctIndex] → ${val} != ${array[correctIndex]} → ${val !== array[correctIndex] ? 'YES ✓' : 'NO ✗'}`,
+ variables: { i, 'nums[i]': val, correctIndex, 'nums[correctIndex]': array[correctIndex], array: [...array] },
+ comparingIndices: [i, correctIndex],
+ isSwap: false
+ });
+ addLines(5, 5, 6, 7);
- // Highlight both for comparison
- addStep(nums, `Checking if nums[i] (${val}) is at its correct position nums[${correctIndex}] (${nums[correctIndex]})`, 7, i, correctIndex, [i, correctIndex]);
+ if (array[i] !== array[correctIndex]) {
+ const prevIVal = array[i];
+ const prevCorrectVal = array[correctIndex];
+ [array[i], array[correctIndex]] = [array[correctIndex], array[i]];
- if (nums[i] !== nums[correctIndex]) {
- // Prepare for swap
- addStep(nums, `Mismatch found! Swapping ${nums[i]} with ${nums[correctIndex]}`, 9, i, correctIndex, [i, correctIndex], true);
+ steps.push({
+ array: [...array],
+ i,
+ correctIndex,
+ explanation: `Mismatch found! Swap elements at indices ${i} (${prevIVal}) and ${correctIndex} (${prevCorrectVal}).`,
+ pseudoStep: `SWAP nums[i], nums[correctIndex] → swap index ${i} & ${correctIndex}`,
+ variables: { i, 'nums[i]': array[i], correctIndex, 'nums[correctIndex]': array[correctIndex], array: [...array] },
+ comparingIndices: [i, correctIndex],
+ isSwap: true
+ });
+ addLines(6, 6, 7, 8);
+ } else {
+ i++;
- // Finalize swap
- [nums[i], nums[correctIndex]] = [nums[correctIndex], nums[i]];
- addStep(nums, `Swapped elements. Now nums[${correctIndex}] = ${nums[correctIndex]}`, 9, i, correctIndex, [i, correctIndex]);
- } else {
- // Increment i
- addStep(nums, `${val} is already at index ${i}. Move to next index`, 11, i, correctIndex, [i]);
- i++;
- addStep(nums, `Incrementing i to ${i}`, 11, i, -1);
- }
+ steps.push({
+ array: [...array],
+ i,
+ correctIndex,
+ explanation: `${val} is already at its correct index (${val - 1}). Increment i to ${i}.`,
+ pseudoStep: 'ELSE: i++ (value is in place, move index forward)',
+ variables: { i: i < array.length ? i : 'done', 'nums[i]': i < array.length ? array[i] : '-', correctIndex, 'nums[correctIndex]': array[correctIndex], array: [...array] },
+ comparingIndices: [i - 1],
+ isSwap: false
+ });
+ addLines(8, 8, 11, 10);
}
+ }
- addStep(nums, 'Complete! All elements are at their correct indices', 15, i, -1);
+ steps.push({
+ array: [...array],
+ i,
+ correctIndex: -1,
+ explanation: 'Loop ends. All elements are sorted and placed at their correct indices.',
+ pseudoStep: 'RETURN nums',
+ variables: { i: 'done', 'nums[i]': '-', correctIndex: '-', 'nums[correctIndex]': '-', array: [...array], result: JSON.stringify(array) },
+ comparingIndices: [],
+ isSwap: false
+ });
+ addLines(11, 9, 14, 12);
- setSteps(newSteps);
- setCurrentStepIndex(0);
- };
+ return { steps, stepLineNumbers };
+}
- useEffect(() => {
- generateSteps();
- }, []);
+// ─── Component ───────────────────────────────────────────────────────────────
+
+export const CyclicSortVisualization = () => {
+ const [{ steps, stepLineNumbers }] = useState(generateVisualizationData);
+ const [currentStepIndex, setCurrentStepIndex] = useState(0);
+ const [isPlaying, setIsPlaying] = useState(false);
+ const [speed, setSpeed] = useState(1);
+ const intervalRef = useRef(null);
useEffect(() => {
if (isPlaying && currentStepIndex < steps.length - 1) {
@@ -113,17 +226,17 @@ export const CyclicSortVisualization = () => {
const handlePlay = () => setIsPlaying(true);
const handlePause = () => setIsPlaying(false);
- const handleStepForward = () => currentStepIndex < steps.length - 1 && setCurrentStepIndex(prev => prev + 1);
- const handleStepBack = () => currentStepIndex > 0 && setCurrentStepIndex(prev => prev - 1);
+ const handleStepForward = () => currentStepIndex < steps.length - 1 && setCurrentStepIndex(p => p + 1);
+ const handleStepBack = () => currentStepIndex > 0 && setCurrentStepIndex(p => p - 1);
const handleReset = () => {
setCurrentStepIndex(0);
setIsPlaying(false);
- generateSteps();
};
if (steps.length === 0) return null;
const currentStep = steps[currentStepIndex];
+ const pseudoSteps = steps.map(s => s.pseudoStep);
return (
@@ -141,33 +254,34 @@ export const CyclicSortVisualization = () => {
/>
+ {/* Left: visual state */}
-
+
{currentStep.array.map((value, index) => (
-
+
{value}
-
+
Idx {index}
-
+
{index === currentStep.i && (
- i
+ i
)}
{index === currentStep.correctIndex && (
- target
+ Target
)}
@@ -176,24 +290,40 @@ export const CyclicSortVisualization = () => {
-
-
{currentStep.message}
+
+
{currentStep.explanation}
-
-
= 0 ? currentStep.correctIndex : '-',
- 'nums[target]': currentStep.correctIndex >= 0 ? currentStep.array[currentStep.correctIndex] : '-',
- array: currentStep.array
- }}
- />
+
+
Cyclic Sort Strategy:
+
+
• Applicable when numbers are in a defined range (e.g. 1 to n)
+
• Iterate through array: value `x` should be at index `x - 1`
+
• If element is not at its correct index, swap it with the element at its correct index
+
• Do not increment `i` on swap; re-evaluate new element at index `i` until it is in place
+
• Time: O(n) · Space: O(1)
+
+
+ = 0 ? currentStep.correctIndex : '-',
+ 'nums[target]': currentStep.correctIndex >= 0 ? currentStep.array[currentStep.correctIndex] : '-',
+ array: JSON.stringify(currentStep.array)
+ }}
+ />
-
+ {/* Right: code / pseudocode panel */}
+
);
diff --git a/src/components/visualizations/algorithms/DFSInorderVisualization.tsx b/src/components/visualizations/algorithms/DFSInorderVisualization.tsx
index c457c46..76d8a28 100644
--- a/src/components/visualizations/algorithms/DFSInorderVisualization.tsx
+++ b/src/components/visualizations/algorithms/DFSInorderVisualization.tsx
@@ -1,8 +1,8 @@
import { useEffect, useRef, useState } from 'react';
-
-import { AnimatedCodeEditor } from "../shared/AnimatedCodeEditor";
import { StepControls } from '../shared/StepControls';
import { VariablePanel } from '../shared/VariablePanel';
+import { VisualizationCodePanel } from '../shared/VisualizationCodePanel';
+import type { StepLineNumberMap, VisualizationLanguageMap } from '@/types/visualization';
interface TreeNode {
val: number;
@@ -14,34 +14,71 @@ interface TreeNode {
interface Step {
visited: number[];
- current: number | null;
+ currentNode: number | null;
stack: number[];
message: string;
- lineNumber: number;
+ pseudoStep: string;
+ variables: Record
;
}
-export const DFSInorderVisualization = () => {
- const [steps, setSteps] = useState([]);
- const [currentStepIndex, setCurrentStepIndex] = useState(0);
- const [isPlaying, setIsPlaying] = useState(false);
- const [speed, setSpeed] = useState(1);
- const [tree, setTree] = useState(null);
- const intervalRef = useRef(null);
-
- const code = `function inorder(root) {
- const result = [];
-
- function traverse(node) {
+const languages: VisualizationLanguageMap = {
+ typescript: `function inorderTraversal(root: TreeNode | null): number[] {
+ const result: number[] = [];
+ function dfs(node: TreeNode | null) {
if (!node) return;
-
- traverse(node.left); // Visit left
- result.push(node.val); // Visit root
- traverse(node.right); // Visit right
+ dfs(node.left);
+ result.push(node.val);
+ dfs(node.right);
}
-
- traverse(root);
+ dfs(root);
return result;
-}`;
+}`,
+
+ python: `def inorderTraversal(root):
+ result = []
+ def dfs(node):
+ if not node:
+ return
+ dfs(node.left)
+ result.append(node.val)
+ dfs(node.right)
+ dfs(root)
+ return result`,
+
+ java: `public static class Solution {
+ private void dfs(TreeNode node, List result) {
+ if (node == null) {
+ return;
+ }
+ dfs(node.left, result);
+ result.add(node.val);
+ dfs(node.right, result);
+ }
+ public List inorderTraversal(TreeNode root) {
+ List result = new ArrayList<>();
+ dfs(root, result);
+ return result;
+ }
+ }`,
+
+ cpp: `class Solution {
+public:
+ vector < int > inorderTraversal(TreeNode * root) {
+ vector < int > result;
+ dfs(root, result);
+ return result;
+ }
+ void dfs(TreeNode * node, vector & result) {
+ if (!node) return;
+ dfs(node -> left, result);
+ result.push_back(node -> val);
+ dfs(node -> right, result);
+ }
+};`,
+};
+
+export const DFSInorderVisualization = () => {
+ const [tree, setTree] = useState(null);
const createTree = (): TreeNode => {
return {
@@ -72,77 +109,88 @@ export const DFSInorderVisualization = () => {
calculatePositions(root, 200, 50, 80);
setTree(root);
- const newSteps: Step[] = [];
+ const steps: Step[] = [];
const visited: number[] = [];
const stack: number[] = [];
- const traverse = (node: TreeNode | null) => {
- if (!node) return;
+ const stepLineNumbers: StepLineNumberMap = {
+ typescript: [],
+ python: [],
+ java: [],
+ cpp: []
+ };
- stack.push(node.val);
- newSteps.push({
- visited: [...visited],
- current: node.val,
+ const addLines = (ts: number, py: number, java: number, cpp: number) => {
+ stepLineNumbers.typescript!.push(ts);
+ stepLineNumbers.python!.push(py);
+ stepLineNumbers.java!.push(java);
+ stepLineNumbers.cpp!.push(cpp);
+ };
+
+ const addStep = (currentNode: number | null, msg: string, pseudo: string, ts_l: number, py_l: number, java_l: number, cpp_l: number) => {
+ steps.push({
+ currentNode,
stack: [...stack],
- message: `Visit node ${node.val}, go left first`,
- lineNumber: 7 // traverse(node.left)
+ visited: [...visited],
+ message: msg,
+ pseudoStep: pseudo,
+ variables: {
+ currentNode: currentNode ?? 'null',
+ stack: `[${stack.join(', ')}]`,
+ visited: `[${visited.join(', ')}]`,
+ 'inorder result': visited.join(' → ') || 'empty'
+ }
});
+ addLines(ts_l, py_l, java_l, cpp_l);
+ };
- traverse(node.left);
+ // 1. Initialize result
+ addStep(null, 'Initialize result array.', 'result = []', 2, 2, 11, 4);
+ // 2. Call dfs(root)
+ addStep(null, 'Invoke dfs(root) to start traversal.', 'dfs(root)', 9, 9, 12, 5);
+
+ const dfs = (node: TreeNode | null) => {
+ if (!node) {
+ addStep(null, 'Node is null. Backtrack.', 'IF node is null → return', 4, 4, 3, 9);
+ addStep(null, 'Return from null node.', 'return', 4, 5, 4, 9);
+ return;
+ }
+
+ // Check if (!node) evaluates to false
+ stack.push(node.val);
+ addStep(node.val, `Check if node is null: node (${node.val}) is not null.`, 'IF node is null → NO', 4, 4, 3, 9);
+
+ // Recurse left
+ addStep(node.val, `Recurse into left subtree of node ${node.val}.`, `dfs(node.left)`, 5, 6, 6, 10);
+ dfs(node.left);
+
+ // Visit node (inorder)
visited.push(node.val);
- newSteps.push({
- visited: [...visited],
- current: node.val,
- stack: [...stack],
- message: `Process node ${node.val} (left done, process root)`,
- lineNumber: 8 // result.push(node.val)
- });
+ addStep(node.val, `Visit node ${node.val}. Append it to the result.`, `result.push(${node.val})`, 6, 7, 7, 11);
- newSteps.push({
- visited: [...visited],
- current: node.val,
- stack: [...stack],
- message: `Traverse right for node ${node.val}`,
- lineNumber: 9 // traverse(node.right)
- });
- traverse(node.right);
+ // Recurse right
+ addStep(node.val, `Recurse into right subtree of node ${node.val}.`, `dfs(node.right)`, 7, 8, 8, 12);
+ dfs(node.right);
+ // Backtrack
stack.pop();
- newSteps.push({
- visited: [...visited],
- current: node.val,
- stack: [...stack],
- message: `Node ${node.val} complete`,
- lineNumber: 10 // function end
- });
+ addStep(node.val, `Finished visiting node ${node.val}. Pop from stack and backtrack.`, 'End dfs(node) → Backtrack', 8, 8, 9, 13);
};
- newSteps.push({
- visited: [],
- current: null,
- stack: [],
- message: 'Start DFS Inorder: Left → Root → Right',
- lineNumber: 12 // traverse(root)
- });
-
- traverse(root);
-
- newSteps.push({
- visited,
- current: null,
- stack: [],
- message: `Complete! Inorder: [${visited.join(', ')}]`,
- lineNumber: 13 // return result
- });
-
- setSteps(newSteps);
- setCurrentStepIndex(0);
+ dfs(root);
+
+ // Return result
+ addStep(null, `Traversal complete. Return inorder result: [${visited.join(', ')}]`, 'return result', 10, 10, 13, 6);
+
+ return { steps, stepLineNumbers };
};
- useEffect(() => {
- generateSteps();
- }, []);
+ const [{ steps, stepLineNumbers }] = useState(generateSteps);
+ const [currentStepIndex, setCurrentStepIndex] = useState(0);
+ const [isPlaying, setIsPlaying] = useState(false);
+ const [speed, setSpeed] = useState(1);
+ const intervalRef = useRef(null);
useEffect(() => {
if (isPlaying && currentStepIndex < steps.length - 1) {
@@ -170,16 +218,20 @@ export const DFSInorderVisualization = () => {
const handleReset = () => {
setCurrentStepIndex(0);
setIsPlaying(false);
- generateSteps();
};
if (steps.length === 0 || !tree) return null;
const currentStep = steps[currentStepIndex];
+ const pseudoSteps = steps.map(s => s.pseudoStep);
const renderTree = (node: TreeNode | null): JSX.Element | null => {
if (!node || node.x === undefined || node.y === undefined) return null;
+ const isVisited = currentStep.visited.includes(node.val);
+ const isCurrent = currentStep.currentNode === node.val;
+ const isInStack = currentStep.stack.includes(node.val);
+
return (
{node.left && node.left.x !== undefined && node.left.y !== undefined && (
@@ -191,12 +243,14 @@ export const DFSInorderVisualization = () => {
@@ -205,7 +259,7 @@ export const DFSInorderVisualization = () => {
y={node.y}
textAnchor="middle"
dy=".3em"
- className={`font- ${currentStep.visited.includes(node.val) || currentStep.current === node.val ? 'fill-white' : 'fill-foreground'}`}
+ className={`text-sm font-semibold ${isVisited || isCurrent ? 'fill-white' : 'fill-foreground'}`}
>
{node.val}
@@ -231,40 +285,62 @@ export const DFSInorderVisualization = () => {
/>
-
+ {/* Left: visual tree + commentary box + variable panel */}
+
-
-
{currentStep.message}
-
+
+
+
Call Stack
+
+ {currentStep.stack.map((val, i) => (
+
+ dfs({val})
+
+ ))}
+ {currentStep.stack.length === 0 &&
Empty
}
+
+
-
-
Visited Order:
-
- {currentStep.visited.map((val, idx) => (
-
- {val}
-
- ))}
+
+
Visited
+
+ {currentStep.visited.map((val, i) => (
+
+ {val}
+
+ ))}
+ {currentStep.visited.length === 0 &&
Empty
}
+
-
-
+
+
{currentStep.message}
+
+
-
+
+ {/* Right: code / pseudocode panel */}
+
);
diff --git a/src/components/visualizations/algorithms/DFSPostorderVisualization.tsx b/src/components/visualizations/algorithms/DFSPostorderVisualization.tsx
index 36b9db2..853d309 100644
--- a/src/components/visualizations/algorithms/DFSPostorderVisualization.tsx
+++ b/src/components/visualizations/algorithms/DFSPostorderVisualization.tsx
@@ -1,8 +1,8 @@
import { useEffect, useRef, useState } from 'react';
-
-import { AnimatedCodeEditor } from "../shared/AnimatedCodeEditor";
import { StepControls } from '../shared/StepControls';
import { VariablePanel } from '../shared/VariablePanel';
+import { VisualizationCodePanel } from '../shared/VisualizationCodePanel';
+import type { StepLineNumberMap, VisualizationLanguageMap } from '@/types/visualization';
interface TreeNode {
val: number;
@@ -14,34 +14,71 @@ interface TreeNode {
interface Step {
visited: number[];
- current: number | null;
+ currentNode: number | null;
stack: number[];
message: string;
- lineNumber: number;
+ pseudoStep: string;
+ variables: Record
;
}
-export const DFSPostorderVisualization = () => {
- const [steps, setSteps] = useState([]);
- const [currentStepIndex, setCurrentStepIndex] = useState(0);
- const [isPlaying, setIsPlaying] = useState(false);
- const [speed, setSpeed] = useState(1);
- const [tree, setTree] = useState(null);
- const intervalRef = useRef(null);
-
- const code = `function postorder(root) {
- const result = [];
-
- function traverse(node) {
+const languages: VisualizationLanguageMap = {
+ typescript: `function postorderTraversal(root: TreeNode | null): number[] {
+ const result: number[] = [];
+ function dfs(node: TreeNode | null) {
if (!node) return;
-
- traverse(node.left); // Visit left
- traverse(node.right); // Visit right
- result.push(node.val); // Visit root
+ dfs(node.left);
+ dfs(node.right);
+ result.push(node.val);
}
-
- traverse(root);
+ dfs(root);
return result;
-}`;
+}`,
+
+ python: `def postorderTraversal(root):
+ result = []
+ def dfs(node):
+ if not node:
+ return
+ dfs(node.left)
+ dfs(node.right)
+ result.append(node.val)
+ dfs(root)
+ return result`,
+
+ java: `public static class Solution {
+ private void dfs(TreeNode node, List result) {
+ if (node == null) {
+ return;
+ }
+ dfs(node.left, result);
+ dfs(node.right, result);
+ result.add(node.val);
+ }
+ public List postorderTraversal(TreeNode root) {
+ List result = new ArrayList<>();
+ dfs(root, result);
+ return result;
+ }
+ }`,
+
+ cpp: `class Solution {
+ public:
+ vector < int > postorderTraversal(TreeNode * root) {
+ vector < int > result;
+ dfs(root, result);
+ return result;
+ }
+ void dfs(TreeNode * node, vector