diff --git a/public/searching.png b/public/searching.png new file mode 100644 index 0000000..565f4d5 Binary files /dev/null and b/public/searching.png differ diff --git a/src/App.jsx b/src/App.jsx index 84c2c94..29babb2 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -6,7 +6,7 @@ import GraphPage from "./pages/graph/GraphPage"; import Homepage from "./pages/Homepage.jsx"; import DSPage from "./pages/dataStructure/datastructurePage.jsx" import DynamicProgrammingPage from "./pages/dynamic-programming/DyanmicProgrammingPage.jsx"; - +import Searchingpage from "./pages/searching/searchingPage"; function App() { return ( @@ -14,6 +14,7 @@ function App() { } /> {/* } /> */} } /> + } /> }/> } /> } /> diff --git a/src/algorithms/searching/BinarySearch.js b/src/algorithms/searching/BinarySearch.js new file mode 100644 index 0000000..d2c8162 --- /dev/null +++ b/src/algorithms/searching/BinarySearch.js @@ -0,0 +1,64 @@ +/** + * A generator function that performs Binary Search and yields visualization steps. + * @param {number[]} array - The array to search through (must be sorted). + * @param {number} target - The value to find. + * @yields {{lowIndex: number, highIndex: number, midIndex: number | null, foundIndex: number | null, message?: string}} + */ +export function* binarySearch(array, target) { + const targetNumber = parseInt(target); + let low = 0; + let high = array.length - 1; + let foundIndex = null; + + // Initial state: show the full search range + yield { lowIndex: low, highIndex: high, midIndex: null, foundIndex: null }; + + while (low <= high) { + let mid = Math.floor((low + high) / 2); + + // Step 1: Highlight the middle element being compared (yellow) + yield { + lowIndex: low, + highIndex: high, + midIndex: mid, + foundIndex: null + }; + + // Step 2: Check for the target + if (array[mid] === targetNumber) { + foundIndex = mid; + // Target found, yield the final state (green) and stop + yield { + lowIndex: low, + highIndex: high, + midIndex: mid, + foundIndex: foundIndex, + message: `Success! Found target ${targetNumber} at index ${mid}` + }; + return; // Exit the generator + } else if (array[mid] < targetNumber) { + // Target is greater, search the right half. Discard the lower half by moving low. + low = mid + 1; + } else { + // Target is smaller, search the left half. Discard the upper half by moving high. + high = mid - 1; + } + + // Step 3: Yield the updated boundaries for visualization before the next comparison + yield { + lowIndex: low, + highIndex: high, + midIndex: null, // Clear the mid highlight + foundIndex: null + }; + } + + // If the loop finishes without finding the target + yield { + lowIndex: low, + highIndex: high, + midIndex: null, + foundIndex: null, + message: `Target ${targetNumber} was not found in the array.` + }; +} \ No newline at end of file diff --git a/src/algorithms/searching/LinearSearch.js b/src/algorithms/searching/LinearSearch.js new file mode 100644 index 0000000..7af3328 --- /dev/null +++ b/src/algorithms/searching/LinearSearch.js @@ -0,0 +1,38 @@ +/** + * A generator function that performs Linear Search and yields visualization steps. + * @param {number[]} array - The array to search through. + * @param {number} target - The value to find. + * @yields {{highlightIndex: number | null, foundIndex: number | null, message?: string}} + */ +export function* linearSearch(array, target) { + const targetNumber = parseInt(target); // Ensure target is a number + let foundIndex = null; + + // Iterate through the array + for (let i = 0; i < array.length; i++) { + // 1. Yield the current index being compared (yellow highlight) + yield { + highlightIndex: i, + foundIndex: foundIndex + }; + + // 2. Check for the target + if (array[i] === targetNumber) { + foundIndex = i; + // Target found, yield the final state (green) and return + yield { + highlightIndex: i, + foundIndex: foundIndex, + message: `Success! Found target ${targetNumber} at index ${i}` + }; + return; // Stop the generator + } + } + + // If the loop finishes without finding the target + yield { + highlightIndex: null, // Clear highlight + foundIndex: null, // Not found + message: `Target ${targetNumber} was not found in the array.` + }; +} \ No newline at end of file diff --git a/src/components/searching/BinarySearchVisualizer.jsx b/src/components/searching/BinarySearchVisualizer.jsx new file mode 100644 index 0000000..3ba2a1b --- /dev/null +++ b/src/components/searching/BinarySearchVisualizer.jsx @@ -0,0 +1,53 @@ +import React from "react"; + +const COLORS = { + default: "bg-blue-800", // Bar outside the search range (eliminated) + activeRange: "bg-blue-500 shadow-[0_0_10px_#3b82f6]", // Bar inside the current search range + comparing: "bg-yellow-400 shadow-[0_0_12px_#facc15]", // Element currently being checked (mid) + found: "bg-green-500 shadow-[0_0_12px_#22c55e]", // Target element found +}; + +export default function BinarySearchVisualizer({ + array, + lowIndex, + highIndex, + midIndex, + foundIndex +}) { + const maxValue = Math.max(...array, 1); + const containerHeight = 288; // px (matches h-72) + + return ( +
+ {array.map((value, idx) => { + let color = COLORS.default; + + // 1. Check if the element is inside the current search range + if (idx >= lowIndex && idx <= highIndex) { + color = COLORS.activeRange; + } + + // 2. Check if the element is currently being compared (mid) + if (idx === midIndex) { + color = COLORS.comparing; + } + + // 3. Check if the element has been found (Highest priority) + if (idx === foundIndex) { + color = COLORS.found; + } + + // Normalize height relative to the maximum value + const height = Math.max((value / maxValue) * containerHeight, 15); + + return ( +
+ ); + })} +
+ ); +} \ No newline at end of file diff --git a/src/components/searching/LinearSearchVisualizer.jsx b/src/components/searching/LinearSearchVisualizer.jsx new file mode 100644 index 0000000..612c873 --- /dev/null +++ b/src/components/searching/LinearSearchVisualizer.jsx @@ -0,0 +1,46 @@ +import React from "react"; + +// Updated colors for searching visualization +const COLORS = { + default: "bg-blue-500 shadow-[0_0_10px_#3b82f6]", // Default bar color + comparing: "bg-yellow-400 shadow-[0_0_12px_#facc15]", // Element currently being checked + found: "bg-green-500 shadow-[0_0_12px_#22c55e]", // Target element found + miss: "bg-red-500 shadow-[0_0_12px_#ef4444]", // Optional: To highlight elements that were skipped (e.g., in Binary Search) +}; + +export default function LinearSearchVisualizer({ + array, + highlightIndex, + foundIndex +}) { + const maxValue = Math.max(...array, 1); + const containerHeight = 288; // px (matches h-72) + + return ( +
+ {array.map((value, idx) => { + let color = COLORS.default; + + // 1. Check if the element has been found (Highest priority for the final result) + if (foundIndex === idx) { + color = COLORS.found; + } + // 2. Check if the element is currently being compared + else if (highlightIndex === idx) { + color = COLORS.comparing; + } + + // Normalize height relative to the maximum value + const height = Math.max((value / maxValue) * containerHeight, 15); + + return ( +
+ ); + })} +
+ ); +} \ No newline at end of file diff --git a/src/pages/Homepage.jsx b/src/pages/Homepage.jsx index 942aa8e..c9df667 100644 --- a/src/pages/Homepage.jsx +++ b/src/pages/Homepage.jsx @@ -3,6 +3,7 @@ import { ArrowRight, Github } from "lucide-react"; import sort from "../assets/sorting.png" import { useNavigate } from "react-router-dom"; + const sections = [ { title: "Sorting Algorithms", @@ -18,9 +19,9 @@ const sections = [ description: "Understand linear and binary search methods through live visualization.", phase: "Phase 1 (MVP)", - img: "", + img: "/searching.png", link: "/searching", - flag: true + flag: false }, { title: "Pathfinding Algorithms", @@ -116,7 +117,7 @@ const Homepage = () => {
section.link && navigate(section.link)} - className="relative group bg-white/10 border border-white/10 rounded-2xl overflow-hidden shadow-2xl hover:shadow-indigo-500/40 transition-all duration-300 backdrop-blur-md hover:-translate-y-2" + className="relative group bg-white/10 border border-white/10 rounded-2xl overflow-hidden shadow-2xl hover:shadow-indigo-500/40 cursor-pointer transition-all duration-300 backdrop-blur-md hover:-translate-y-2" > {/* Image */} { {/* Explore Button */} diff --git a/src/pages/searching/BinarySearch.jsx b/src/pages/searching/BinarySearch.jsx new file mode 100644 index 0000000..6687d4b --- /dev/null +++ b/src/pages/searching/BinarySearch.jsx @@ -0,0 +1,160 @@ +import React, { useState } from "react"; +import { Toaster, toast } from "react-hot-toast"; +// --- IMPORTS --- +import BinarySearchVisualizer from "../../components/searching/BinarySearchVisualizer"; +import { binarySearch } from "../../algorithms/searching/BinarySearch"; +// ---------------- +function ArrayValues({ array }) { + return ( +
+ {array.map((value, idx) => ( +
+ {value} +
+ ))} +
+ ); +} +export default function BinarySearch() { + const [array, setArray] = useState([]); + const [input, setInput] = useState(""); + + + + // --- BINARY SEARCH STATES --- + const [target, setTarget] = useState(""); + const [lowIndex, setLowIndex] = useState(0); + const [highIndex, setHighIndex] = useState(0); + const [midIndex, setMidIndex] = useState(null); + const [foundIndex, setFoundIndex] = useState(null); + // ---------------------------- + + const [isRunning, setIsRunning] = useState(false); + + const handleStart = async () => { + if (isRunning || array.length === 0 || isNaN(parseInt(target))) { + toast.error("Please ensure the array and target are valid."); + return; + } + + setIsRunning(true); + setMidIndex(null); + setFoundIndex(null); + + const targetNumber = parseInt(target); + + // Ensure initial range is set correctly before running + setLowIndex(0); + setHighIndex(array.length - 1); + + const gen = binarySearch(array, targetNumber); + + for (let step of gen) { + // Update the four search-specific states + setLowIndex(step.lowIndex); + setHighIndex(step.highIndex); + setMidIndex(step.midIndex); + setFoundIndex(step.foundIndex); + + if (step.message) { + if (step.foundIndex !== null) { + toast.success(step.message); + } else { + toast.error(step.message); + } + } + + await new Promise((r) => setTimeout(r, 500)); + } + + setIsRunning(false); + }; + + const handleReset = () => { + setArray([]); + setInput(""); + setTarget(""); + setLowIndex(0); + setHighIndex(0); + setMidIndex(null); + setFoundIndex(null); + setIsRunning(false); + }; + + const handleInput = (e) => { + setInput(e.target.value); + const numbers = e.target.value + .split(",") + .map((n) => parseInt(n.trim())) + .filter((n) => !isNaN(n)); + + // 🌟 CRITICAL FOR BINARY SEARCH: Automatically sort the input array + const sortedNumbers = numbers.sort((a, b) => a - b); + setArray(sortedNumbers); + setLowIndex(0); + setHighIndex(sortedNumbers.length - 1); + setMidIndex(null); // Ensure mid is also cleared + }; + + const handleTargetChange = (e) => { + setTarget(e.target.value); + }; + + return ( +
+ +

+ Binary Search Visualizer +

+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ ); +} \ No newline at end of file diff --git a/src/pages/searching/SearchingPage.jsx b/src/pages/searching/SearchingPage.jsx new file mode 100644 index 0000000..22b9346 --- /dev/null +++ b/src/pages/searching/SearchingPage.jsx @@ -0,0 +1,65 @@ +// src/pages/SortingPage.jsx +import React, { useState } from "react"; +import LinearSearch from "./linearSearch"; +import BinarySearch from "./BinarySearch"; + +export default function SortingPage() { + const [selectedAlgo, setSelectedAlgo] = useState(""); + + const renderAlgorithm = () => { + switch (selectedAlgo) { + case "LinearSearch": + return ; + case "BinarySearch": + return ; + + default: + return ( +
+ Select an algorithm to visualize 👆 +
+ ); + } + }; + + return ( +
+ {/* Left Sidebar */} +
+

+ Searching Panel +

+ + + + + + + + ← Back to Home + +
+ + {/* Right Visualization Area */} +
+ {renderAlgorithm()} +
+
+ ); +} diff --git a/src/pages/searching/linearSearch.jsx b/src/pages/searching/linearSearch.jsx new file mode 100644 index 0000000..fdbaa70 --- /dev/null +++ b/src/pages/searching/linearSearch.jsx @@ -0,0 +1,143 @@ +import React, { useState } from "react"; +import { Toaster, toast } from "react-hot-toast"; +import LinearSearchVisualizer from "../../components/searching/LinearSearchVisualizer"; +import { linearSearch } from "../../algorithms/searching/LinearSearch"; + +function ArrayValues({ array }) { + return ( +
+ {array.map((value, idx) => ( +
+ {value} +
+ ))} +
+ ); +} +export default function LinearSearch() { + const [array, setArray] = useState([]); + const [input, setInput] = useState(""); + + // --- SEARCH STATES --- + const [target, setTarget] = useState(""); + const [highlightIndex, setHighlightIndex] = useState(null); + const [foundIndex, setFoundIndex] = useState(null); + // --------------------- + + const [isRunning, setIsRunning] = useState(false); + + const handleStart = async () => { + if (isRunning || array.length === 0 || isNaN(parseInt(target))) { + toast.error("Please ensure the array and target are valid."); + return; + } + + setIsRunning(true); + setHighlightIndex(null); + setFoundIndex(null); + + const targetNumber = parseInt(target); + + // Pass the array AND the target to the generator + const gen = linearSearch(array, targetNumber); + + for (let step of gen) { + // Update search-specific states + setHighlightIndex(step.highlightIndex); + setFoundIndex(step.foundIndex); + + if (step.message) { + if (step.foundIndex !== null) { + toast.success(step.message); + } else { + toast.error(step.message); + } + } + + // Wait for 500ms for visualization (adjust this speed later) + await new Promise((r) => setTimeout(r, 500)); + } + + setIsRunning(false); + }; + + const handleReset = () => { + setArray([]); + setInput(""); + setTarget(""); // Reset target input + setHighlightIndex(null); + setFoundIndex(null); + setIsRunning(false); + }; + + const handleInput = (e) => { + setInput(e.target.value); + const numbers = e.target.value + .split(",") + .map((n) => parseInt(n.trim())) + .filter((n) => !isNaN(n)); + setArray(numbers); + }; + + const handleTargetChange = (e) => { + setTarget(e.target.value); + }; + + return ( +
+ +

+ Linear Search Visualizer +

+ + {/* INPUTS: ARRAY AND TARGET */} +
+ + +
+ +
+ + +
+ +
+ {/* USE THE NEW VISUALIZER AND PASS SEARCH STATES */} + + +
+ +
+ ); +} \ No newline at end of file