diff --git a/src/App.jsx b/src/App.jsx index 5cbaaa8..02b7f41 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -10,6 +10,7 @@ import Searchingpage from "./pages/searching/searchingPage"; import RecursionPage from "./pages/Recursion/RecursionPage"; import Treepage from "./pages/Tree/Treepage"; import SlidingWindowPage from "./pages/sliding-window/SlidingWindowPage"; +import GreedyPage from "./pages/greedy/GreedyPage"; function App() { return ( @@ -24,6 +25,7 @@ function App() { }/> } /> }/> + } /> ); diff --git a/src/algorithms/greedy/fractionalKnapsack.js b/src/algorithms/greedy/fractionalKnapsack.js new file mode 100644 index 0000000..29911f0 --- /dev/null +++ b/src/algorithms/greedy/fractionalKnapsack.js @@ -0,0 +1,91 @@ +export function* fractionalKnapsack(weights, values, capacity) { + const n = weights.length; + const items = []; + for (let i = 0; i < n; i++) { + items.push({index: i, weight: weights[i], value: values[i], ratio: values[i] / weights[i]}); + } + + yield { + type: "calculate_ratios", + message: "Calculating value-to-weight ratio for each item", + items: items.map(item => ({ ...item })), + capacity, + selectedItems: [], + totalValue: 0, + remainingCapacity: capacity + }; + + items.sort((a, b) => b.ratio - a.ratio); + + yield { + type: "sort_items", + message: "Sorting items by value-to-weight ratio (descending) - Greedy approach", + items: items.map(item => ({ ...item })), capacity, selectedItems: [], totalValue: 0, remainingCapacity: capacity + }; + + const selectedItems = []; + let totalValue = 0; + let remainingCapacity = capacity; + + for (let i = 0; i < items.length; i++) { + const item = items[i]; + + yield { + type: "consider_item", + message: `Considering item ${item.index + 1}: weight=${item.weight}, value=${item.value}, ratio=${item.ratio.toFixed(2)}`, + items: items.map(item => ({ ...item })), + currentItem: { ...item }, capacity, selectedItems: selectedItems.map(si => ({ ...si })), totalValue, remainingCapacity + }; + + if (remainingCapacity <= 0) { + yield { + type: "skip_item", + message: `Skipping item ${item.index + 1}: No remaining capacity`, + items: items.map(item => ({ ...item })), + currentItem: { ...item },capacity, selectedItems: selectedItems.map(si => ({ ...si })), totalValue, remainingCapacity + }; + break; + } + + if (item.weight <= remainingCapacity) { + const fraction = 1.0; + const itemValue = item.value; + const itemWeight = item.weight; + + selectedItems.push({index: item.index, weight: itemWeight, value: itemValue, ratio: item.ratio, fraction: fraction, actualWeight: itemWeight, actualValue: itemValue}); + + totalValue += itemValue; + remainingCapacity -= itemWeight; + + yield { + type: "take_full", + message: `Taking full item ${item.index + 1}: ${itemWeight}kg (value: ${itemValue})`, + items: items.map(item => ({ ...item })), + currentItem: { ...item }, + capacity, + selectedItems: selectedItems.map(si => ({ ...si })), + totalValue, + remainingCapacity + }; + } else { + const fraction = remainingCapacity / item.weight; + const actualWeight = remainingCapacity; + const actualValue = item.value * fraction; + + selectedItems.push({index: item.index, weight: item.weight, value: item.value, ratio: item.ratio, fraction: fraction, actualWeight: actualWeight, actualValue: actualValue}); + + totalValue += actualValue; + remainingCapacity = 0; + + yield { + type: "take_fraction", + message: `Taking ${(fraction * 100).toFixed(1)}% of item ${item.index + 1}: ${actualWeight.toFixed(2)}kg (value: ${actualValue.toFixed(2)})`, + items: items.map(item => ({ ...item })), currentItem: { ...item }, capacity, selectedItems: selectedItems.map(si => ({ ...si })), totalValue, remainingCapacity, fraction + }; + break; + } + } + + yield {type: "complete", message: `Knapsack filled! Total value: ${totalValue.toFixed(2)}`, items: items.map(item => ({ ...item })), capacity, selectedItems: selectedItems.map(si => ({ ...si })), totalValue, remainingCapacity}; + return { selectedItems, totalValue, remainingCapacity}; +} diff --git a/src/components/greedy/FractionalKnapsackVisualizer.jsx b/src/components/greedy/FractionalKnapsackVisualizer.jsx new file mode 100644 index 0000000..5a09c93 --- /dev/null +++ b/src/components/greedy/FractionalKnapsackVisualizer.jsx @@ -0,0 +1,161 @@ +import React from "react"; + +export default function FractionalKnapsackVisualizer({items = [], currentStep = null, capacity = 0}) { + if (!currentStep) { + return ( +
+
+
No data to visualize
+
+
+ ); + } + const step = currentStep; + const selectedItems = step.selectedItems || []; + const totalValue = step.totalValue || 0; + const remainingCapacity = step.remainingCapacity !== undefined ? step.remainingCapacity : capacity; + const usedCapacity = capacity - remainingCapacity; + + const getItemStatus = (item) => { + const selected = selectedItems.find(si => si.index === item.index); + + if (selected) { + return selected.fraction === 1.0 ? "full" : "partial"; + } + + if (step.currentItem && step.currentItem.index === item.index) { + if (step.type === "consider_item") { + return "considering"; + } else if (step.type === "skip_item") { + return "skipped"; + } + } + + return "not_selected"; + }; + + const getItemClass = (status) => { + switch (status) { + case "full": + return "bg-green-600 border-green-500"; + case "partial": + return "bg-yellow-600 border-yellow-500"; + case "considering": + return "bg-blue-600 border-blue-500"; + case "skipped": + return "bg-gray-600 border-gray-500 opacity-50"; + default: + return "bg-gray-700 border-gray-600"; + } + }; + return ( +
+
+

Items (Sorted by Value/Weight Ratio)

+
+ {step.items?.map((item, idx) => { + const status = getItemStatus(item); + const selected = selectedItems.find(si => si.index === item.index); + return ( +
+
+
Item {item.index + 1}
+
+ {item.ratio.toFixed(2)} +
+
+
+
+ W: + {item.weight} +
+
+ V: + {item.value} +
+ {selected && ( +
+
+ {selected.fraction === 1.0 + ? "100%" + : `${(selected.fraction * 100).toFixed(0)}%`} +
+
+ {selected.actualWeight.toFixed(1)}kg +
+
+ )} +
+
+ ); + })} +
+
+
+

Knapsack

+
+
+
+ Capacity + + {usedCapacity.toFixed(1)} / {capacity} kg + +
+
+
+ {capacity > 0 ? ((usedCapacity / capacity) * 100).toFixed(0) : 0}% +
+
+
+ {selectedItems.length > 0 && ( +
+

Selected Items:

+
+ {selectedItems.map((item, idx) => ( +
+
+
Item {item.index + 1}
+
+ {item.fraction === 1.0 ? "Full" : `${(item.fraction * 100).toFixed(0)}%`} +
+
+ {item.actualWeight.toFixed(1)}kg +
+
+ V: {item.actualValue.toFixed(1)} +
+
+
+ ))} +
+
+ )} +
+
+ Total Value: + {totalValue.toFixed(2)} +
+ {remainingCapacity > 0 && step.type === "complete" && ( +
+ Remaining: {remainingCapacity.toFixed(1)} kg +
+ )} +
+
+
+
+ ); +} + diff --git a/src/pages/Homepage.jsx b/src/pages/Homepage.jsx index f643a23..aaf662a 100644 --- a/src/pages/Homepage.jsx +++ b/src/pages/Homepage.jsx @@ -89,6 +89,15 @@ const sections = [ link: "/tree", flag: false, }, + { + title: "Greedy Algorithms", + description: + "Watch how greedy choices lead to optimal solutions in problems like Fractional Knapsack.", + phase: "Phase 2", + img: "", + link: "/greedy", + flag: false, + }, ]; const Homepage = () => { diff --git a/src/pages/greedy/FractionalKnapsack.jsx b/src/pages/greedy/FractionalKnapsack.jsx new file mode 100644 index 0000000..353fae1 --- /dev/null +++ b/src/pages/greedy/FractionalKnapsack.jsx @@ -0,0 +1,235 @@ +import React, { useState, useEffect, useRef } from "react"; +import { Toaster, toast } from "react-hot-toast"; +import { ArrowLeft, Play, Pause, StepForward, RotateCcw } from "lucide-react"; +import FractionalKnapsackVisualizer from "../../components/greedy/FractionalKnapsackVisualizer"; +import { fractionalKnapsack } from "../../algorithms/greedy/fractionalKnapsack"; + +export default function FractionalKnapsackPage() { + const [weights, setWeights] = useState([10, 20, 30]); + const [values, setValues] = useState([60, 100, 120]); + const [capacity, setCapacity] = useState(50); + const [weightsInput, setWeightsInput] = useState("10,20,30"); + const [valuesInput, setValuesInput] = useState("60,100,120"); + const [capacityInput, setCapacityInput] = useState("50"); + const [speed, setSpeed] = useState(1000); + const [isPlaying, setIsPlaying] = useState(false); + const [currentStep, setCurrentStep] = useState(null); + const [steps, setSteps] = useState([]); + const [stepIndex, setStepIndex] = useState(-1); + const timerRef = useRef(null); + + useEffect(() => { + if (weights.length === 0 || values.length === 0 || capacity <= 0) { + setSteps([]); + setStepIndex(-1); + setCurrentStep(null); + return; + } + if (weights.length !== values.length) { + toast.error("Weights and Values arrays must have the same length!"); + return; + } + setIsPlaying(false); + clearTimeout(timerRef.current); + const generator = fractionalKnapsack(weights, values, capacity); + setSteps(Array.from(generator)); + setStepIndex(0); + setCurrentStep(steps[0] || null); + }, [weights, values, capacity]); + + useEffect(() => { + if (isPlaying && stepIndex < steps.length - 1) { + timerRef.current = setTimeout(() => { + setStepIndex((prev) => + prev + 1 < steps.length ? prev + 1 : steps.length - 1 + ); + }, speed); + } else if (stepIndex >= steps.length - 1) { + setIsPlaying(false); + } + return () => clearTimeout(timerRef.current); + }, [isPlaying, stepIndex, steps.length, speed]); + + useEffect(() => { + if (stepIndex >= 0 && stepIndex < steps.length) { + setCurrentStep(steps[stepIndex]); + } + }, [stepIndex, steps]); + + const handleWeightsChange = (e) => { + const value = e.target.value; + setWeightsInput(value); + try { + const parsed = value + .split(",") + .map((p) => p.trim()) + .filter((p) => p !== "") + .map((p) => Number(p)) + .filter((p) => !isNaN(p) && p > 0); + if (parsed.length > 0) { + setWeights(parsed); + } + } catch (err) { + console.error("Error parsing weights:", err); + } + }; + const handleValuesChange = (e) => { + const value = e.target.value; + setValuesInput(value); + try { + const parsed = value + .split(",") + .map((p) => p.trim()) + .filter((p) => p !== "") + .map((p) => Number(p)) + .filter((p) => !isNaN(p) && p > 0); + if (parsed.length > 0) { + setValues(parsed); + } + } catch (err) { + console.error("Error parsing values:", err); + } + }; + + const handleCapacityChange = (e) => { + const value = e.target.value; + setCapacityInput(value); + const parsed = Number(value); + if (!isNaN(parsed) && parsed > 0) { + setCapacity(parsed); + } + }; + const loadDemo = () => { + setWeights([10, 20, 30]); + setValues([60, 100, 120]); + setCapacity(50); + setWeightsInput("10,20,30"); + setValuesInput("60,100,120"); + setCapacityInput("50"); + }; + const togglePlay = () => { + if (steps.length === 0) return; + if (stepIndex >= steps.length - 1) { + setStepIndex(0); + setIsPlaying(true); + } else { + setIsPlaying(!isPlaying); + } + }; + const reset = () => { + setIsPlaying(false); + clearTimeout(timerRef.current); + setSteps([]); + setStepIndex(-1); + setCurrentStep(null); + }; + + return ( +
+ +
+
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ + setSpeed(Number(e.target.value))} + className="accent-blue-500 w-24" + /> + {speed}ms +
+ +
+ + +
+
+ +
+
+

+ Fractional Knapsack Problem +

+
+ {currentStep && ( +
+
+ Step {stepIndex + 1} / {steps.length} +
+
+ {currentStep.type?.replace(/_/g, " ").toUpperCase()} +
+
+ )} +
+ + {currentStep && currentStep.message && ( +
+
+ {currentStep.message} +
+
+ )} + +
+ {currentStep ? ( + + ) : ( +
Enter weights, values, and capacity to start
+ )} +
+
+ ); +} + diff --git a/src/pages/greedy/GreedyPage.jsx b/src/pages/greedy/GreedyPage.jsx new file mode 100644 index 0000000..4ea17c8 --- /dev/null +++ b/src/pages/greedy/GreedyPage.jsx @@ -0,0 +1,64 @@ +import React, { useState } from "react"; +import { Target } from "lucide-react"; +import FractionalKnapsackPage from "./FractionalKnapsack"; + +export default function GreedyPage() { + const [selectedAlgo, setSelectedAlgo] = useState(""); + const renderAlgorithm = () => { + switch (selectedAlgo) { + case "fractional-knapsack": + return ; + default: + return ( +
+
+
+
+ +
+

+ Greedy Algorithms Visualizer +

+

+ Select a greedy algorithm from the sidebar to begin + visualization. Watch how greedy choices lead to optimal solutions! +

+
+
+
+ ); + } + }; + return ( +
+ {/* Sidebar */} +
+

+ Greedy Panel +

+ + + + + ← Back to Home + +
+
+
+ {renderAlgorithm()} +
+
+
+ ); +} +