From 4641cd502908d32add6ff013ecb8bb8512af1e6e Mon Sep 17 00:00:00 2001 From: Mrlionbyte Date: Thu, 2 Jul 2026 18:47:16 +0530 Subject: [PATCH] feat: add Doubly and Circular Linked List visualizations - Add visual representation options for insert, search, and delete operations - Replace Index/Idx with Pointer in UI labels, placeholders, and animation status messages for theoretical accuracy --- .../dsa/CircularLinkedListVisualizer.jsx | 243 ++++++++++++++++++ .../dsa/DoublyLinkedListVisualizer.jsx | 231 +++++++++++++++++ src/components/dsa/LinkedListVisualizer.jsx | 151 ++++++++--- src/lib/dsaAdvancedUtils.js | 189 ++++++++++++-- src/pages/DsaVisualization.jsx | 32 ++- 5 files changed, 772 insertions(+), 74 deletions(-) create mode 100644 src/components/dsa/CircularLinkedListVisualizer.jsx create mode 100644 src/components/dsa/DoublyLinkedListVisualizer.jsx diff --git a/src/components/dsa/CircularLinkedListVisualizer.jsx b/src/components/dsa/CircularLinkedListVisualizer.jsx new file mode 100644 index 0000000..f5857c8 --- /dev/null +++ b/src/components/dsa/CircularLinkedListVisualizer.jsx @@ -0,0 +1,243 @@ +import React, { useState, useEffect } from 'react'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { ArrowRight, Plus, Trash2, Play, Pause, StepForward, StepBack, Search, RotateCcw } from 'lucide-react'; +import { cn } from '@/lib/utils'; +import { generateCircularLinkedListSteps } from '@/lib/dsaAdvancedUtils'; + +const CircularLinkedListVisualizer = ({ darkMode }) => { + const [list, setList] = useState([ + { id: 1, value: 10 }, + { id: 2, value: 20 }, + { id: 3, value: 30 } + ]); + const [inputValue, setInputValue] = useState(''); + const [indexValue, setIndexValue] = useState('0'); + const [activeMode, setActiveMode] = useState('insert'); + + const [history, setHistory] = useState([]); + const [currentStep, setCurrentStep] = useState(-1); + const [isPlaying, setIsPlaying] = useState(false); + + // Animation loop + useEffect(() => { + let interval; + if (isPlaying && currentStep < history.length - 1) { + interval = setInterval(() => { + setCurrentStep(prev => prev + 1); + }, 800); + } else if (currentStep >= history.length - 1) { + setIsPlaying(false); + } + return () => clearInterval(interval); + }, [isPlaying, currentStep, history]); + + const handleInsert = () => { + if (!inputValue) return; + const val = parseInt(inputValue); + const idx = parseInt(indexValue) || 0; + + const steps = generateCircularLinkedListSteps(list, 'insert', { value: val, index: idx }); + setHistory(steps); + setCurrentStep(-1); + setIsPlaying(true); + setInputValue(''); + }; + + const handleDelete = () => { + const idx = parseInt(indexValue) || 0; + const steps = generateCircularLinkedListSteps(list, 'delete', { index: idx }); + setHistory(steps); + setCurrentStep(-1); + setIsPlaying(true); + }; + + const handleSearch = () => { + if (!inputValue) return; + const val = parseInt(inputValue); + const steps = generateCircularLinkedListSteps(list, 'search', { value: val }); + setHistory(steps); + setCurrentStep(-1); + setIsPlaying(true); + setInputValue(''); + }; + + const handlePlayPause = () => { + if (history.length === 0 || currentStep >= history.length - 1) { + const steps = generateCircularLinkedListSteps(list, 'traverse'); + setHistory(steps); + setCurrentStep(-1); + setIsPlaying(true); + } else { + setIsPlaying(!isPlaying); + } + }; + + const handleStepForward = () => { + if (history.length === 0 || currentStep >= history.length - 1) { + const steps = generateCircularLinkedListSteps(list, 'traverse'); + setHistory(steps); + setCurrentStep(0); + } else { + setCurrentStep(p => Math.min(history.length - 1, p + 1)); + } + }; + + // Determine current display state + const displayList = currentStep >= 0 && history[currentStep] && history[currentStep].list ? history[currentStep].list : list; + const activeIndex = currentStep >= 0 && history[currentStep] ? history[currentStep].highlightedIndex ?? history[currentStep].index : -1; + const message = currentStep >= 0 && history[currentStep] ? history[currentStep].message : "Ready"; + const currentStepData = currentStep >= 0 && history[currentStep] ? history[currentStep] : null; + const isCurveHighlighted = currentStepData?.type === 'highlight-curve'; + + // Update actual state when animation finishes + useEffect(() => { + if (currentStep === history.length - 1 && history.length > 0) { + if (history[currentStep] && history[currentStep].list) { + setList(history[currentStep].list); + } + } + }, [currentStep, history]); + + return ( +
+
+ +
+
+ + + +
+ +
+ + + +
+
+ +
+ {activeMode === 'insert' && ( + <> +

Val :

+ setInputValue(e.target.value)} + /> +

Pointer :

+ setIndexValue(e.target.value)} + /> + + + )} + {activeMode === 'delete' && ( + <> +

Pointer :

+ setIndexValue(e.target.value)} + /> + + + )} + {activeMode === 'search' && ( + <> +

Val :

+ setInputValue(e.target.value)} + /> + + + )} +
+
+ +
{message}
+ +
+ {displayList.length > 1 && ( + + + + + + + + + + + + )} + + {displayList.map((node, idx) => ( +
+
+ {node.value} +
+ {idx < displayList.length - 1 && ( + + )} +
+ ))} + {displayList.length === 0 &&
List is empty
} +
+
+ ); +}; + +export default CircularLinkedListVisualizer; diff --git a/src/components/dsa/DoublyLinkedListVisualizer.jsx b/src/components/dsa/DoublyLinkedListVisualizer.jsx new file mode 100644 index 0000000..8918de2 --- /dev/null +++ b/src/components/dsa/DoublyLinkedListVisualizer.jsx @@ -0,0 +1,231 @@ +import React, { useState, useEffect } from 'react'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { ArrowRight, ArrowLeft, ArrowLeftRight, Plus, Trash2, Play, Pause, StepForward, StepBack, Search } from 'lucide-react'; +import { cn } from '@/lib/utils'; +import { generateDoublyLinkedListSteps } from '@/lib/dsaAdvancedUtils'; + +const DoublyLinkedListVisualizer = ({ darkMode }) => { + const [list, setList] = useState([ + { id: 1, value: 10 }, + { id: 2, value: 20 }, + { id: 3, value: 30 } + ]); + const [inputValue, setInputValue] = useState(''); + const [indexValue, setIndexValue] = useState('0'); + const [activeMode, setActiveMode] = useState('insert'); + + const [history, setHistory] = useState([]); + const [currentStep, setCurrentStep] = useState(-1); + const [isPlaying, setIsPlaying] = useState(false); + + // Animation loop + useEffect(() => { + let interval; + if (isPlaying && currentStep < history.length - 1) { + interval = setInterval(() => { + setCurrentStep(prev => prev + 1); + }, 800); + } else if (currentStep >= history.length - 1) { + setIsPlaying(false); + } + return () => clearInterval(interval); + }, [isPlaying, currentStep, history]); + + const handleInsert = () => { + if (!inputValue) return; + const val = parseInt(inputValue); + const idx = parseInt(indexValue) || 0; + + const steps = generateDoublyLinkedListSteps(list, 'insert', { value: val, index: idx }); + setHistory(steps); + setCurrentStep(-1); + setIsPlaying(true); + setInputValue(''); + }; + + const handleDelete = () => { + const idx = parseInt(indexValue) || 0; + const steps = generateDoublyLinkedListSteps(list, 'delete', { index: idx }); + setHistory(steps); + setCurrentStep(-1); + setIsPlaying(true); + }; + + const handleSearch = () => { + if (!inputValue) return; + const val = parseInt(inputValue); + const steps = generateDoublyLinkedListSteps(list, 'search', { value: val }); + setHistory(steps); + setCurrentStep(-1); + setIsPlaying(true); + setInputValue(''); + }; + + const handlePlayPause = () => { + if (history.length === 0 || currentStep >= history.length - 1) { + const steps = generateDoublyLinkedListSteps(list, 'traverse'); + setHistory(steps); + setCurrentStep(-1); + setIsPlaying(true); + } else { + setIsPlaying(!isPlaying); + } + }; + + const handleStepForward = () => { + if (history.length === 0 || currentStep >= history.length - 1) { + const steps = generateDoublyLinkedListSteps(list, 'traverse'); + setHistory(steps); + setCurrentStep(0); + } else { + setCurrentStep(p => Math.min(history.length - 1, p + 1)); + } + }; + + // Determine current display state + const displayList = currentStep >= 0 && history[currentStep].list ? history[currentStep].list : list; + const activeIndex = currentStep >= 0 ? history[currentStep].highlightedIndex ?? history[currentStep].index : -1; + const message = currentStep >= 0 ? history[currentStep].message : "Ready"; + + // Update actual state when animation finishes + useEffect(() => { + if (currentStep === history.length - 1 && history.length > 0) { + if (history[currentStep].list) { + setList(history[currentStep].list); + } + } + }, [currentStep, history]); + + return ( +
+
+
+
+ + + +
+ +
+ + + +
+
+ +
+ {activeMode === 'insert' && ( + <> +

Val :

+ setInputValue(e.target.value)} + /> +

Pointer :

+ setIndexValue(e.target.value)} + /> + + + )} + {activeMode === 'delete' && ( + <> +

Pointer :

+ setIndexValue(e.target.value)} + /> + + + )} + {activeMode === 'search' && ( + <> +

Val :

+ setInputValue(e.target.value)} + /> + + + )} +
+
+ +
{message}
+ +
+ {displayList.length > 0 && ( +
+
NULL
+ +
+ )} + {displayList.map((node, idx) => ( +
+
+ {node.value} +
+ {idx < displayList.length - 1 && ( + + )} + {idx === displayList.length - 1 && ( +
+ +
NULL
+
+ )} +
+ ))} + {displayList.length === 0 &&
List is empty
} +
+
+ ); +}; + +export default DoublyLinkedListVisualizer; diff --git a/src/components/dsa/LinkedListVisualizer.jsx b/src/components/dsa/LinkedListVisualizer.jsx index 8bdba1c..fd60ac2 100644 --- a/src/components/dsa/LinkedListVisualizer.jsx +++ b/src/components/dsa/LinkedListVisualizer.jsx @@ -1,7 +1,7 @@ import React, { useState, useEffect, useRef } from 'react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; -import { ArrowRight, Plus, Trash2, Play, Pause, StepForward, StepBack, RotateCcw } from 'lucide-react'; +import { ArrowRight, Plus, Trash2, Play, Pause, StepForward, StepBack, RotateCcw, Search } from 'lucide-react'; import { cn } from '@/lib/utils'; import { generateLinkedListSteps } from '@/lib/dsaAdvancedUtils'; import { Card } from '@/components/ui/card'; @@ -14,11 +14,12 @@ const LinkedListVisualizer = ({ darkMode }) => { ]); const [inputValue, setInputValue] = useState(''); const [indexValue, setIndexValue] = useState('0'); - + const [activeMode, setActiveMode] = useState('insert'); + const [history, setHistory] = useState([]); const [currentStep, setCurrentStep] = useState(-1); const [isPlaying, setIsPlaying] = useState(false); - + // Animation loop useEffect(() => { let interval; @@ -36,7 +37,7 @@ const LinkedListVisualizer = ({ darkMode }) => { if (!inputValue) return; const val = parseInt(inputValue); const idx = parseInt(indexValue) || 0; - + const steps = generateLinkedListSteps(list, 'insert', { value: val, index: idx }); setHistory(steps); setCurrentStep(-1); @@ -52,6 +53,16 @@ const LinkedListVisualizer = ({ darkMode }) => { setIsPlaying(true); }; + const handleSearch = () => { + if (!inputValue) return; + const val = parseInt(inputValue); + const steps = generateLinkedListSteps(list, 'search', { value: val }); + setHistory(steps); + setCurrentStep(-1); + setIsPlaying(true); + setInputValue(''); + }; + // Determine current display state const displayList = currentStep >= 0 && history[currentStep].list ? history[currentStep].list : list; const activeIndex = currentStep >= 0 ? history[currentStep].highlightedIndex ?? history[currentStep].index : -1; @@ -60,48 +71,104 @@ const LinkedListVisualizer = ({ darkMode }) => { // Update actual state when animation finishes useEffect(() => { if (currentStep === history.length - 1 && history.length > 0) { - if (history[currentStep].list) { - setList(history[currentStep].list); - } + if (history[currentStep].list) { + setList(history[currentStep].list); + } } }, [currentStep, history]); return (
-
-
- setInputValue(e.target.value)} - /> - setIndexValue(e.target.value)} - /> - - +
+
+
+ + + +
+ +
+ + + +
-
- - - +
+ {activeMode === 'insert' && ( + <> +

Val :

+ setInputValue(e.target.value)} + /> +

Pointer :

+ setIndexValue(e.target.value)} + /> + + + )} + {activeMode === 'delete' && ( + <> +

Pointer :

+ setIndexValue(e.target.value)} + /> + + + )} + {activeMode === 'search' && ( + <> +

Val :

+ setInputValue(e.target.value)} + /> + + + )}
@@ -112,8 +179,8 @@ const LinkedListVisualizer = ({ darkMode }) => {
{node.value} @@ -122,7 +189,7 @@ const LinkedListVisualizer = ({ darkMode }) => { )} {idx === displayList.length - 1 && ( -
NULL
+
NULL
)}
))} diff --git a/src/lib/dsaAdvancedUtils.js b/src/lib/dsaAdvancedUtils.js index 83f91a3..b2444df 100644 --- a/src/lib/dsaAdvancedUtils.js +++ b/src/lib/dsaAdvancedUtils.js @@ -5,9 +5,9 @@ export const generateLinkedListSteps = (currentList, operation, params) => { const steps = []; // list is array of { id, value } // operations: 'insert', 'delete', 'search' - + const newList = [...currentList]; - + if (operation === 'insert') { const { value, index } = params; const validIndex = Math.max(0, Math.min(index, newList.length)); @@ -15,33 +15,170 @@ export const generateLinkedListSteps = (currentList, operation, params) => { // Step 1: Visualize finding position for (let i = 0; i < validIndex; i++) { - steps.push({ type: 'highlight', index: i, message: `Traversing to index ${i}...` }); + steps.push({ type: 'highlight', index: i, message: `Traversing to Pointer ${i}...` }); } // Step 2: Insert newList.splice(validIndex, 0, newNode); - steps.push({ type: 'update', list: [...newList], highlightedIndex: validIndex, message: `Inserted ${value} at index ${validIndex}` }); - } + steps.push({ type: 'update', list: [...newList], highlightedIndex: validIndex, message: `Inserted node with value ${value} at Pointer ${validIndex}` }); + } else if (operation === 'delete') { const { index } = params; if (index < 0 || index >= newList.length) return []; for (let i = 0; i < index; i++) { - steps.push({ type: 'highlight', index: i, message: `Traversing to index ${i}...` }); + steps.push({ type: 'highlight', index: i, message: `Traversing to Pointer ${i}...` }); + } + + const removedVal = newList[index].value; + newList.splice(index, 1); + steps.push({ type: 'update', list: [...newList], message: `Deleted node with value ${removedVal}` }); + } + else if (operation === 'search') { + const { value } = params; + let found = false; + for (let i = 0; i < newList.length; i++) { + steps.push({ type: 'highlight', index: i, message: `Checking if node value ${newList[i].value} matches ${value}...` }); + if (newList[i].value === value) { + steps.push({ type: 'found', index: i, highlightedIndex: i, message: `Found value ${value} at Pointer ${i}!` }); + found = true; + break; + } + } + if (!found) { + steps.push({ type: 'not-found', highlightedIndex: -1, message: `Value ${value} not found in the list.` }); + } + } + + return steps; +}; + +export const generateDoublyLinkedListSteps = (currentList, operation, params) => { + const steps = []; + const newList = [...currentList]; + + if (operation === 'insert') { + const { value, index } = params; + const validIndex = Math.max(0, Math.min(index, newList.length)); + const newNode = { id: Date.now(), value }; + + for (let i = 0; i < validIndex; i++) { + steps.push({ type: 'highlight', index: i, direction: 'forward', message: `Traversing forward to Pointer ${i}...` }); + } + + newList.splice(validIndex, 0, newNode); + steps.push({ type: 'update', list: [...newList], highlightedIndex: validIndex, message: `Inserted ${value} and connected prev and next arrows` }); + } + else if (operation === 'delete') { + const { index } = params; + if (index < 0 || index >= newList.length) return []; + + for (let i = 0; i < index; i++) { + steps.push({ type: 'highlight', index: i, direction: 'forward', message: `Traversing forward to Pointer ${i}...` }); + } + + const removedVal = newList[index].value; + + steps.push({ type: 'highlight', index, message: `Disconnecting prev and next arrows for node ${removedVal}` }); + + newList.splice(index, 1); + steps.push({ type: 'update', list: [...newList], message: `Deleted node with value ${removedVal}` }); + } + else if (operation === 'search') { + const { value } = params; + let found = false; + for (let i = 0; i < newList.length; i++) { + steps.push({ type: 'highlight', index: i, direction: 'forward', message: `Checking if node value ${newList[i].value} matches ${value}...` }); + if (newList[i].value === value) { + steps.push({ type: 'found', index: i, highlightedIndex: i, message: `Found value ${value} at Pointer ${i}!` }); + found = true; + break; + } + } + if (!found) { + steps.push({ type: 'not-found', highlightedIndex: -1, message: `Value ${value} not found in the list.` }); + } + } + else if (operation === 'traverse') { + if (newList.length === 0) return steps; + for (let i = 0; i < newList.length; i++) { + steps.push({ type: 'highlight', index: i, direction: 'forward', message: `Traversing forward at Pointer ${i}` }); + } + for (let i = newList.length - 1; i >= 0; i--) { + steps.push({ type: 'highlight', index: i, direction: 'backward', message: `Traversing backward at Pointer ${i}` }); } + steps.push({ type: 'done', message: 'Traversal complete' }); + } + + return steps; +}; + +export const generateCircularLinkedListSteps = (currentList, operation, params) => { + const steps = []; + const listLength = currentList.length; + let newList = [...currentList]; + + if (operation === 'insert') { + const { value, index } = params; + const newNode = { id: Date.now(), value }; + + if (index === 0) { + newList.unshift(newNode); + } else if (index >= listLength) { + newList.push(newNode); + } else { + newList.splice(index, 0, newNode); + } + steps.push({ type: 'update', list: [...newList], message: `Inserted node with value ${value}` }); + } + else if (operation === 'delete') { + const { index } = params; + if (index < 0 || index >= listLength || listLength === 0) return steps; const removedVal = newList[index].value; newList.splice(index, 1); steps.push({ type: 'update', list: [...newList], message: `Deleted node with value ${removedVal}` }); } - + else if (operation === 'search') { + const { value } = params; + let found = false; + for (let i = 0; i < newList.length; i++) { + steps.push({ type: 'highlight', index: i, message: `Checking if node value ${newList[i].value} matches ${value}...` }); + if (newList[i].value === value) { + steps.push({ type: 'found', index: i, highlightedIndex: i, message: `Found value ${value} at Pointer ${i}!` }); + found = true; + break; + } + } + if (!found) { + steps.push({ type: 'not-found', highlightedIndex: -1, message: `Value ${value} not found in the list.` }); + } + } + else if (operation === 'traverse') { + if (newList.length === 0) return steps; + const loops = 5; + const limit = newList.length * loops; + let currentIdx = 0; + for (let i = 0; i < limit; i++) { + steps.push({ type: 'highlight', index: currentIdx, message: `Traversing node at Pointer ${currentIdx}` }); + + let nextIdx = (currentIdx + 1) % newList.length; + if (nextIdx === 0 && i !== limit - 1) { + steps.push({ type: 'highlight-curve', index: currentIdx, message: `Following circular link back to head` }); + } + currentIdx = nextIdx; + } + steps.push({ type: 'done', message: 'Circular traversal complete' }); + } + return steps; }; + // BST Logic export const generateBSTSteps = (root, operation, value) => { const steps = []; - + // Deep clone helper const clone = (node) => node ? { ...node, left: clone(node.left), right: clone(node.right) } : null; let newRoot = clone(root); @@ -56,7 +193,7 @@ export const generateBSTSteps = (root, operation, value) => { let curr = newRoot; while (true) { steps.push({ type: 'visit', activeId: curr.id, root: clone(newRoot), message: `Comparing ${value} with ${curr.value}` }); - + if (value < curr.value) { if (!curr.left) { curr.left = { id: Date.now(), value }; @@ -86,13 +223,13 @@ export const generateHeapSteps = (heap, operation, value) => { if (operation === 'insert') { newHeap.push(value); steps.push({ type: 'update', heap: [...newHeap], activeIdx: newHeap.length - 1, message: `Added ${value} at the end` }); - + // Heapify Up let idx = newHeap.length - 1; while (idx > 0) { let parentIdx = Math.floor((idx - 1) / 2); steps.push({ type: 'compare', heap: [...newHeap], indices: [idx, parentIdx], message: `Comparing ${newHeap[idx]} with parent ${newHeap[parentIdx]}` }); - + if (newHeap[idx] > newHeap[parentIdx]) { [newHeap[idx], newHeap[parentIdx]] = [newHeap[parentIdx], newHeap[idx]]; steps.push({ type: 'swap', heap: [...newHeap], indices: [idx, parentIdx], message: 'Child is larger, swapping' }); @@ -110,7 +247,7 @@ export const generateHeapSteps = (heap, operation, value) => { // DP: Steps Generator export const generateDPSteps = (type, params) => { const steps = []; - + if (type === 'LCS') { const { s1, s2 } = params; // Two Pointers Approach for "Is Subsequence" / Greedy LCS check @@ -118,10 +255,10 @@ export const generateDPSteps = (type, params) => { let i = 0; let j = 0; const matchedIndicesS1 = []; // Indices in s1 that matched - + steps.push({ type: 'init', - i: 0, j: 0, + i: 0, j: 0, matchedIndicesS1: [], s1, s2, message: 'Initialized pointers. Looking for characters of Pattern in String.' @@ -131,7 +268,7 @@ export const generateDPSteps = (type, params) => { // Visualize comparison steps.push({ type: 'compare', - i, j, + i, j, matchedIndicesS1: [...matchedIndicesS1], s1, s2, message: `Comparing String[${i}] ('${s1[i]}') with Pattern[${j}] ('${s2[j]}').` @@ -142,7 +279,7 @@ export const generateDPSteps = (type, params) => { matchedIndicesS1.push(i); steps.push({ type: 'match', - i, j, + i, j, matchedIndicesS1: [...matchedIndicesS1], s1, s2, message: `Match! Found '${s2[j]}' at index ${i}. Moving both pointers.` @@ -153,7 +290,7 @@ export const generateDPSteps = (type, params) => { // No match steps.push({ type: 'mismatch', - i, j, + i, j, matchedIndicesS1: [...matchedIndicesS1], s1, s2, message: `Mismatch. '${s1[i]}' is not '${s2[j]}'. Moving String pointer.` @@ -161,15 +298,15 @@ export const generateDPSteps = (type, params) => { i++; } } - + // Final state steps.push({ type: 'finish', - i, j, + i, j, matchedIndicesS1: [...matchedIndicesS1], s1, s2, - message: j === s2.length - ? `Success! Found full subsequence "${s2}".` + message: j === s2.length + ? `Success! Found full subsequence "${s2}".` : `Finished. Found partial subsequence: "${s2.substring(0, j)}".` }); @@ -180,11 +317,11 @@ export const generateDPSteps = (type, params) => { memo[0] = 0; memo[1] = 1; steps.push({ type: 'init', table: [...memo], message: 'Base cases: Fib(0)=0, Fib(1)=1' }); - - for(let i=2; i<=n; i++) { - steps.push({ type: 'calc', i, table: [...memo], message: `Calculating Fib(${i}) = Fib(${i-1}) + Fib(${i-2})` }); - memo[i] = memo[i-1] + memo[i-2]; - steps.push({ type: 'update', i, val: memo[i], table: [...memo], message: `Fib(${i}) = ${memo[i]}` }); + + for (let i = 2; i <= n; i++) { + steps.push({ type: 'calc', i, table: [...memo], message: `Calculating Fib(${i}) = Fib(${i - 1}) + Fib(${i - 2})` }); + memo[i] = memo[i - 1] + memo[i - 2]; + steps.push({ type: 'update', i, val: memo[i], table: [...memo], message: `Fib(${i}) = ${memo[i]}` }); } } diff --git a/src/pages/DsaVisualization.jsx b/src/pages/DsaVisualization.jsx index 5caefa3..18a0745 100644 --- a/src/pages/DsaVisualization.jsx +++ b/src/pages/DsaVisualization.jsx @@ -3,10 +3,8 @@ import React, { useState, useEffect, useRef, useMemo } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { Helmet } from 'react-helmet'; import { - Play, Pause, RotateCcw, Settings, ChevronRight, ChevronLeft, - LayoutGrid, List, BarChart2, Activity, Moon, Sun, - GitCompare, SplitSquareHorizontal, ArrowRight, Network, - PlusCircle, Move, MousePointer2, Trash2, StepForward, + Play, Pause, RotateCcw, List, Activity, Moon, Sun, + GitCompare, Network, PlusCircle, Move, Trash2, StepForward, StepBack, Binary, BoxSelect, Layers, Search, Github } from 'lucide-react'; import { Button } from '@/components/ui/button'; @@ -28,6 +26,8 @@ import { useToast } from '@/components/ui/use-toast'; // Import new visualizers import LinkedListVisualizer from '@/components/dsa/LinkedListVisualizer'; +import DoublyLinkedListVisualizer from '@/components/dsa/DoublyLinkedListVisualizer'; +import CircularLinkedListVisualizer from '@/components/dsa/CircularLinkedListVisualizer'; import TreeVisualizer from '@/components/dsa/TreeVisualizer'; import HeapVisualizer from '@/components/dsa/HeapVisualizer'; import DPVisualizer from '@/components/dsa/DPVisualizer'; @@ -408,6 +408,7 @@ const DsaVisualization = () => { const [darkMode, setDarkMode] = useState(true); const [customInput, setCustomInput] = useState(''); const [searchTarget, setSearchTarget] = useState(42); // Default target + const [llType, setLlType] = useState('singly'); const [array, setArray] = useState([]); @@ -675,9 +676,28 @@ const DsaVisualization = () => {
-

Linked List Visualizer

+

+ Linked List Visualizer ({llType.charAt(0).toUpperCase() + llType.slice(1)}) +

+
+ Type + +
- + + {llType === 'singly' && } + {llType === 'doubly' && } + {llType === 'circular' && } +
) : activeTab === 'trees' ? (